Dealing with multiple, optional parameters with mod_rewrite - apache

I'm using apache's mod_rewrite to make my application's URL's pretty. I have the basics of mod_rewrite down pat - several parts of my application use simple and predictable rewrites.
However, I've written a blog function, which use several different parameters.
http://www.somedomain.com/blog/
http://www.somedomain.com/blog/tag/
http://www.somedomain.com/blog/page/2/
I have the following rules in my .htaccess:
RewriteRule ^blog/ index.php?action=blog [NC]
RewriteRule ^blog/(.*) index.php?action=blog&tag=$1 [NC]
RewriteRule ^blog/page/(.*) index.php?action=blog&page=$1 [NC]
However, the rules do not work together. The computer matches the first rule, and then stops processing - even though to my way of thinking, it should not match. I'm telling the machine to match ^blog/ and it goes ahead and matches ^blog/tag/ and ^blog/page/2/ which seems wrong to me.
What's going wrong with my rules? Why are they not being evaluated in the way I'm intending?
Edit: The answer was to terminate the input using $, and re-order the rules, ever so slightly:
RewriteRule ^blog/$ index.php?action=blog [NC,L]
RewriteRule ^blog/page/(.*)$ index.php?action=blog&page=$1 [NC,L]
RewriteRule ^blog/(.*)$ index.php?action=blog&tag=$1 [NC,L]
These rules produced the desired effect.

If you don't want ^blog/ to match anything more than that, specify the end of the input in the match as well:
^blog/$
However, the way many apps do it is to just have a single page that all URLs redirect to, that then processes the rest of the URL internally in the page code. Usually most web languages have a way to get the URI of the original request, which can be parsed out to determine what "variables" were specified, even though Apache points all of them to the same page. Then via includes or some other framework/templating engine you can load the proper logic.
As another note - usually the "more general" rewrite rules are put last, so that things which match a more specific redirect will be processed first. This, coupled with the [L] option after the rule, will ensure that if a more specific rule matches, more general ones won't be evaluated.

I think you need to add an [L] after the [NC] statements otherwise it'll carry on even if its already been matched

Related

htaccess URL rewriting PHP for posts and offers

i've searched for a long time an answer for my issue, I found a lot of ideas, but I can't figure it out and make it work as I expect..
So, I've a website with
"/index.php"
"/posts.php"
What I want is to rewrite the url in order to :
redirect "/index.php", "/index.php/" and "/index/" to "/"
and also :
redirect "/posts/slug-post-1/" to "offers.php" but still display "/posts/slug-post-1/" in which I would split the url to get the slug of the post.
Thanks
What I've tried is :
## To internally redirect /dir/foo to /dir/foo.php
RewriteCond %{REQUEST_FILENAME}.php -f [NC]
RewriteRule ^ %{REQUEST_URI}.php [L]
It actually display "/offers/" and redirect to "/offers.php"
but when i add a post slug, it doesn't work.
So I've also tried :
RewriteRule ^posts/([^/]*)$ /posts.php?slug=$1 [L]
It works only with ctrl+F5 and not a simple refresh.. I don't understand why. This is the same with different brwoser and computer.
I'll give it a shot :-)
To redirect "/index.php", "/index.php/" and "/index/" to "/".
RewriteRule index(.*)$ / [NC, L]
Edit: I really urge you not to change the default behaviour of a crucial file like index.php in main folder. There will be unforeseen consequences.
And the other one:
RewriteRule posts/slug-post-1(.*)$ offers.php [QSA, NC, L]
In your example:
Your RewriteCond %{REQUEST_FILENAME}.php -f [NC] checks if "requests filename" with .php added is a real existing file.
Use RewriteCond ${REQUEST_FILENAME} !-f to make sure the targeted file is not skipped, although it is existing. !-d would check for directories.
If you want slugs to be added dynamically to the new target, use flag QSA, so [QSA, NC, L] instead of [NC, L], for example. Use the condition before the rule.
This is really a comment - but space is limited.
It looks as if you haven't thought through what you are trying to achieve before trying to implement it.
What I want is to rewrite the url in order to : redirect "/index.php", "/index.php/" and "/index/" to "/"
I think you need to do a lot more searching. Webservers don't serve up directories, they typically have a lot of machinery in place to service up content when presented with a request where the path maps to a directory to change that to a file, a script, or a special handler.
I suspect you want to rewrite /, /index.php/ and /index/ to /index.php
But I suspect there's more to what you are trying to achieve here - that you also want to deal with any string after the pattern you are seeking to match which is implied in your attempts to deal with /posts.
So it looks as if you are trying to implement 2 front controller patterns. Implementing a single front controller pattern appears to be a bit of a stretch for you. Implementing 2 at the same time is unlikely to turn out well and whatever you do finally implement will likely be very fragile. You're going to need a router in /index.php so that is the right place to handle the /posts/ requests.
But this is only PART of the problem you need to solve. Having your PHP code intercepting all requests is rather expensive in terms of CPU and memory (unless you have a really good caching policy implemented on your server and it is sitting behind a caching reverse proxy).

Htaccess Match Random 6 characters, with exceptions?

Alright, so I've been trying to wrap my head around (what I believe to be) a simple mod_rewrite case. Maybe it's not, but I'm hoping you can help me with that, Stack Overflow.
So what I want is this: there are several folders that need to be ignored (ie, "css", "js", "bootstrap", etc). If the url string doesn't match those, I want to check if it's a string of exactly six letters and numbers, and redirect that to one url. Otherwise, it gets redirected to another url.
This is what I have:
RewriteCond %{REQUEST_URI} !^/(index\.php|images|robots\.txt|bootstrap|phpmyadmin|css|js|font|recaptchalib.php|uploads)/
RewriteRule ^(a-z0-9+){6}$ /index.php/download/index/$1 [L]
RewriteRule ^(.*)$ /index.php/$1 [L]
If I take out the middle line, it works fine except I don't get the "match 6 random characters" functionality. With the middle line, I get a 500 error on every page.
Could someone help me out please?
You need to repeat the condition. A RewriteCond only applies to the immediately following RewriteRule. So you're second rule doesn't exclude all those folders in the pattern that you have in your condition. Try:
RewriteCond %{REQUEST_URI} !^/(index\.php|images|robots\.txt|bootstrap|phpmyadmin|css|js|font|recaptchalib.php|uploads)/
RewriteRule ^(a-z0-9+){6}$ /index.php/download/index/$1 [L]
RewriteCond %{REQUEST_URI} !^/(index\.php|images|robots\.txt|bootstrap|phpmyadmin|css|js|font|recaptchalib.php|uploads)/
RewriteRule ^(.*)$ /index.php/$1 [L]
You should let CodeIgniter handle this. Keep your .htaccess for routing to the index.php front controller, and use route(s) to handle the URI and where it should go from there.
This is especially true because now ANY six-letter URI is going to be defaulted. What if you have a controller like example.com/bloggers? It will always be assumed to be a download item, even if it's a real controller URI.
The "easiest" option (read: option that does not conflict with existing controllers/routes) is to utilize the 404_controller to check the URI and see if it's a valid download URL. Then you can run the appropriate code.
To explain a likely reason why your .htaccess code is not working: your regular expression for matching six alpha-numeric characters is wrong. Here's what you need:
^([a-zA-Z0-9]{6})$
This regex can be used as a CodeIgniter route, also, if you go that route (heh). Just remove the ^$ beginning/end characters, as CI puts them there for you.
As mentioned by Jon Lin, you also need to duplicate RewriteCond conditionals, as they are only good for one RewriteRule. After one, the conditionals reset.

Shorten URLs with mod_rewrite

I am currently trying to make a URL shortener feature for one of my projects; what I want to do if a user visits the site with a URL that does not contain any slashes (for directories) or file extensions, it should redirect to a PHP script that will serve up the correct file. For example:
http://example.com/A123 would be rewritten as http://example.com/view.php?id=A123
but
http://example.com/A123/ would not be rewritten, and
http://example.com/A123.png would not be rewritten either. I have been messing with mod_rewrite for a few hours now and for the life of me I cannot get this to work...
With no way to identify the URI that needs to be shortened you need to exclude all other possibilities. This will likely require you to build a lengthy list of exclusions. Below is a starting point. Each of these conditions verifies the requesting URI does NOT match (signified by the !). When it doesn't match all conditions the rule is run.
RewriteCond %{REQUEST_URI} !^/view.php
RewriteCond %{REQUEST_URI} !.html$
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^/(.*)$ http://example.com/view.php?id=$1 [QSA]
The above also requires you (as you have requested) to break a standard practice rule, which is to handle directory requests without a trailing slash. You are likely to come across other issues, as the rules above break your Apache server side directory rules.
Rethinking the logic. If you had some way to identify the URL that is to be shortened it would be much easier. For example 's', http://example.com/s/A123.
RewriteCond %{REQUEST_URI} ^/s/
RewriteRule ^/s/(.*)$ http://example.com/view.php?id=$1 [QSA]
I'm definitely no guru at this, but its similar to what I'm trying to accomplish (see my yet unanswered question)
However, if I understand correctly, this (untested) RewriteRule may work:
RewriteRule ^([^\.\/]*)$ view.php?id=$1 [L]
The magic part is the [^\.\/]* which says: 1 or more (*) instances of a charactor ([]) which is not ([^ ]) a period or a slash (\ escapes these charactors).
Like I said, I haven't tested this, nor am I an expert, but perhaps this will help.

simple 301 redirect with variable not working, why?

Here's what I got so far. The first part works but not the redirect itself.
What do I need to do to make it work?
RewriteEngine On
RewriteRule ^([^/\.]+)/?$ page.php?name=$1 [L]
RewriteRule ^page.php?name=([^/\.]+)/?$ /$1 [R=301,L]
Also if I have multiple of these rules do I leave the [L] only on the last one?
Besides the first rule overriding the second one, your second rule also won't work because you're trying to match the query string in a RewriteRule. Try something like this instead:
RewriteEngine On
RewriteBase /
RewriteCond %{QUERY_STRING} ^name=([^/.&]+)/?$
RewriteCond %{ENV:REDIRECT_LOOP} !1
RewriteRule ^page\.php$ /%1? [NS,R=301,L]
RewriteRule ^([^/.]+)/?$ page.php?name=$1 [NS,QSA,E=LOOP:1]
(I included the QSA flag so that an URL like /foobar?foo=bar will be rewritten to /page.php?name=foobar&foo=bar instead of just /page.php?name=foobar. If you don't want that, leave it out.)
Note: The second RewriteCond is there to keep the first rule from matching again after the second one has matched. The problem is that, in .htaccess context, mod_rewrite acts more or less as if all rules had the PT flag, causing the ruleset to be rerun from the start after every rewrite, even internal ones. Or, to quote the documentation:
"If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of how the rules are processed. The simplified form of this is that once the rules have been processed, the rewritten request is handed back to the URL parsing engine to do what it may with it. It is possible that as the rewritten request is handled, the .htaccess file or <Directory> section may be encountered again, and thus the ruleset may be run again from the start. Most commonly this will happen if one of the rules causes a redirect - either internal or external - causing the request process to start over."
The workaround I'm using is to set a custom environment variable with E=LOOP:1 when the internal rewrite triggers, and check for it before doing the external rewrite. Note that, when the request processing restarts after the internal rewrite, Apache prepends REDIRECT_ to the names of all environment variables set during the previous pass, so even though the variable we set is named just LOOP, the one we need to check for is REDIRECT_LOOP.

.htaccess pretty url problem (mod_rewrite)

I have a directory that lists products by categories. if a _GET variable exists, it is used in a query. I would like to use "pretty url's", like: example/a/1/b/2/c/3/d/4 becomes example/index.html?a=1&b=2&c=3&d=4
most .htaccess examples I see only use variables to replace the _GET values, but I can use rules like this:
Options +FollowSymlinks
RewriteEngine on
RewriteRule ([^/]+)/([^/]+)/([^/]+)/([^/]+)/([^/]+)/([^/]+)$ index.html?$1=$2&$3=$4&$5=$6 [L]
RewriteRule ([^/]+)/([^/]+)/([^/]+)/([^/]+)$ index.html?$1=$2&$3=$4 [L]
RewriteRule ([^/]+)/([^/]+)$ index.html?$1=$2 [L]
And it works... However, the when I add longer and longer RewriteRules (like out to &17=$18), it stops working. The last variables in the chain turn into some sort of array based on earlier values (in above it would build index.html?a0=a1&a3=a4)...
Is there a better way to do this?
It seems inefficient?
Is there a limit to the number of variables in .htaccess
How long a rule can be?
Thanks!
mod_rewrite only supports up to $9 and %9.
I recommend you either modify your script to use $_SERVER['PATH_INFO'], or you use RewriteMap to invoke a script to transform the path into a querystring.
mod_rewrite only allows for you to have ten back-references, one of which is the whole matchable part (which ends up leaving you with only nine definable capture groups), so you're definitely limited by that.
However, to me it would make much more sense to examine the server's REQUEST_URI/SCRIPT_NAME/PATH_INFO variable in your script file, and parse that to get the key-value pairs from the URL. Then, you'd simply have this in your .htaccess:
RewriteRule On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.html [L]
And then your script would take care of the rest. I do have to wonder though, if you have that many GET variables, is it actually more readable if they're all made into a "pretty" URL? After all, if you have twenty-some forward slashes in the URL, you may be equally well off just passing a normal query string at that point. It depends on your application though and how users interface with these URLs, so you may have good reason for wanting to do it this way.