Apache rewrite exception for / index but not second and deeper indexes? - apache

I want to have an exception for...
http ://localhost/
...while rewriting the index of any directories underneath...
http ://localhost/directory1/
http ://localhost/directory2/
By having an empty item below (the first item which is between the characters (| on the third line) it creates an exception for ALL indexes so how can I make the exception NOT apply to the localhost/ itself using this copy of .htaccess?
http ://localhost/.htaccess
RewriteEngine on
RewriteRule ^(|directory2/|directory2/) - [L]
RewriteRule !\.(css|xhtml|xml|zip)$ rewrite.php
...and I can not mess with server configuration. Additionally this question is not redirect related.

Try using a RewriteCond to match your directory, and then apply the RewriteRule to anything that matches.
EDIT: Also, I think your ! line might be causing some problems. I tested with the rewrite rule tester and tweaked my suggested fix to look like this:
RewriteCond %{REQUEST_URI} ^/(directory1|directory2)/
RewriteRule .(css|xhtml|xml|zip)$ - [L]
RewriteRule .* rewrite.php
This is generally how I match things -- if you have some things you don't want to process, match them and stop processing rules with the [L] directive, then continue ahead for anything else.

This rule makes an exception for the root index. Since nothing is between ^ (^ = starts with) and $ ($ = ends with) the requested URI this matches http:// localhost/ exactly. Having [space] index.php (or change the extension to what you want) forces the file to rewrite to itself. It does not appear to loop.
RewriteRule ^$ index.php [QSA]

Related

How to avoid loops in the exceptions of generic regex rules?

Need to use flags? RewriteCond? The mod_rewrite is enabled and wroking, my problem is how to express my needs: there are a generic rule and some exceptions to the rule, the exceptions not working, and solution must avoid loops.
My /var/www before to use mod_rewrite, was working with usual folders like /var/www/wiki, /var/www/foo, /var/www/bar (each with its index). I need to preserve this behaviour.
Now, with mod_rewrite, I am using a /var/www/index.php to redirect some special strings, that match ^([a-z0-9]+), such as http://myDomain/foo123 or http://myDomain/wiikii. But it matches also "wiki" and "foo", that need exception handling.
My /var/www/.htaccess is
RewriteEngine on
RewriteRule ^/?(foo|bar)([0-9]+) index.php?$1=$2
RewriteRule ^/?([a-z0-9]+) index.php?foobar=$1
so, something like http://myDomain/wiki is redirect to index.php?foobar=wiki, but I need it with no redirection, need Apache going to /wiki.
PS: I try some variations, and some changes into index.php (working with redirections internally), but need elegant and secure Apache Rewrite solution, avoiding loops.
Usually conditions are the way to go, checking against REQUEST_URI.
RewriteCond %{REQUEST_URI} !^/(wiki|foo)$ <-- added
RewriteRule ^([a-z0-9]+)$ index.php?foobar=$1 [L]
An alternative is using e.g. negative lookahead in your regex. For example:
RewriteRule ^(?!(?:foo|wiki)$)([a-z0-9]+) ...
But I find any of the zero width assertions are quite complicated/fragile/unintuitive even if normal regexes come quite easy to you.
?! is negative lookahead
?: is just non-capturing parens for the "or"
The most confusing part about these is that pcre tries hard to find a way to NOT satisfy the negative lookahead, in other words it tries hard to have a succesful overall match.
If you don't want any redirection for wiki folder (and others), you can add a condition
RewriteEngine on
# If requested url is an existing file or folder, don't touch it
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule . - [L]
# If we reach here, this means it's not a file or folder, we can rewrite...
RewriteRule ^(foo|bar)([0-9]+)$ index.php?$1=$2 [L]
RewriteRule ^([a-z0-9]+)$ index.php?foobar=$1 [L]
Also, you can use sort of exception instead, if you don't want it to be apply on all folders/files
RewriteEngine on
# If requested url is a folder/file in exception list, don't touch it
RewriteCond %{REQUEST_URI} ^/(foo|bar|example_file\.html|wiki)$
RewriteRule . - [L]
# If we reach here, this means it's not a file or folder in exception list, we can rewrite...
RewriteRule ^(foo|bar)([0-9]+)$ index.php?$1=$2 [L]
RewriteRule ^([a-z0-9]+)$ index.php?foobar=$1 [L]

What does the following (simple) .htaccess rule do?

I'm learning a lot about .htaccess, I found the following line in a project, but i really can't make out what it does
RewriteRule ^$ index.php [L]
I know that ^ indicated the beginning of the url to match and
that $ indicated the end of the string. But there's nothing in between so maybe this one is redundant?
This one is not redundant rule it is matching ^$ (empty pattern which means home/landing URI /) and forwarding it to index.php thus when you open http://domain.com it is showing http://domain.com/index.php
Though same thing can be achieved using:
DirectoryIndex index.php

How to add "everything else" rule to mod_rewrite

How can I make mod_rewrite redirect to a certain page or probably just throw 404 if no other rules have been satisfied? Here's what I have in my .htaccess file:
RewriteEngine on
RewriteRule ^\. / [F,QSA,L]
RewriteRule ^3rdparty(/.*)$ / [F,QSA,L]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^((images|upload)/.+|style.css)$ $1 [L]
RewriteRule ^$ special [QSA]
RewriteRule ^(special|ready|building|feedback)/?$ $1.php [QSA,L]
RewriteRule ^(ready|building)/(\d+)/?$ show_property.php?type=$1&property_id=$2 [QSA,L]
RewriteRule . error.php?code=404 [QSA,L]
This is supposed, among other things, to send user to error.php if he tries to access anything that was not explicitly specified here (by the way, what is the proper way to throw 404?). However, instead it sends user from every page to error.php. If I remove the last rule, everything else works.
What am I doing wrong?
What is happening is that when you are doing a rewrite, you then send the user to the new URL, where these rewrite rules are then evaluated again. Eventually no other redirectoin rules will be triggered and it will get to the final rule and always redirect to the error.php page.
So you need to put some rewrite conditions in place to make this not happen.
The rewrite engine loops, so you need to pasthrough successful rewrites before finally rewriting to error.php. Maybe something like:
RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_URI} !^/(special|ready|building|feedback|show_property)\.php
RewriteCond %{REQUEST_URI} !^/((images|upload)/.+|style.css)$
RewriteRule ^ error.php?code=404 [QSA,L,R=404]
Each condition makes sure the URI isn't one of the ones your other rules have rewritten to.
The R=404 will redirect to the error.php page as a "404 Not Found".
Unfortunatelly, it didn't work - it allows access to all files on the server (presumably because all conditions need to be satisfied). I tried an alternate solution:
Something else must be slipping through, eventhough when I tested your rules plus these at the end in a blank htaccess file, it seems to work. Something else you can try which is a little less nice but since you don't actually redirect the browser anywhere, it would be hidden from clients.
You have a QSA flag at the end of all your rules, you could add a unique param to the query string after you've applied a rule, then just check against that. Example:
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^((images|upload)/.+|style.css)$ $1?_ok [L,QSA]
then at the end:
RewriteCond %{QUERY_STRING} !_ok
RewriteRule ^ error.php?code=404&_ok [QSA,L,R=404]
In theory if none of the rules are matched (and the requested URL does not exist), it's already a 404. So I think the simplest solution is to use an ErrorDocument, then rewrite it:
RewriteEngine On
ErrorDocument 404 /404.php
RewriteRule ^404.php$ error.php?code=404 [L]
# All your other rules here...
You can do the same for any other HTTP error code.
The problem here is that after the mod_rewrite finishes rewriting the URL, it is resubmitted to the mod_rewrite for another pass. So, the [L] flag only makes the rule last for the current pass. As much better explained in this question, mod_rewrite starting from Apache version 2.3.9, now supports another flag - [END], that makes the current mod_rewrite pass the last one. For Apache 2.2 a number of solutions are offered, but since one of them was a bit clumsy and another didn't work, my current solution is to add another two rules that allow a specific set of files to be accessed while sending 404 for everything else:
RewriteRule ^((images|upload)/.+|style.css|(special|ready|building|feedback|property).php)$ - [QSA,L]
RewriteRule .* - [QSA,L,R=404]
I think your last rule should be
RewriteRule ^(.*)$ error.php?code=404&query=$1 [QSA,L]
You could leave out the parenthesis and the $1 parameter, but maybe it's useful to know, what the user tried to achieve.
Hope, this does the trick!

mod_rewrite - stop repeating for each directory

I have spent hours trying to get a simple rewrite working, there must be an error in my fundamental understanding of mod_rewrite:
I want a rule that does the following substitution:
www.example.com/fr/ -> www.example.com/?lang=fr
which I have working, but for subdirectories:
www.example.com/fr/other/directories/ -> www.example.com/other/directories/?lang=fr&lang=fr&lang=fr
It seems the rule is being applied once for every sub-directory (it should only be applied once).
Also, a request without a trailing slash causes another lang=fr to be appended to the query string
The rule is located in the < VirtualHost > and not within a < Directory > tag
RewriteRule ^/(en|fr|zh|gr|it)/(.*)$ /$2?lang=$1 [QSA]
I am also using the DocumentIndex /index.php index.php directive
Many thanks.
Could try adding a / in your second rewrite just after $s2 to experiment with the extra addition.
And try adding [L] to the conditions, might stop it repeating over every directory.
RewriteRule ^/(en|fr|zh|gr|it)/(.*)$ /$2/?lang=$1 [QSA,L]
However, I've heard [L] behaves differently when in htaccess opposed to being in the httpd.conf - I'm no expert on it I'm afraid.
To be certain a RewriteRule will only be applied once add this condition before:
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^/(en|fr|zh|gr|it)/(.*)$ /$2?lang=$1 [QSA,L]
Because any internal redirection made by mod-rewrite (and you'll have a lot, even with the [L] tag) will have the REDIRECT_STATUS envirronement variable altered.
The [L] tag means stop the chain of rewriteRule in case you have some other rules there. But the result of the mod-rewrite rewrite in your directory will always be tested against his new destination (and here the new destination is the same directory), and rules will be applied for the rewritten content as if it was a new one (except this env variable is set).

Apache URL Rewriting,

I am trying to get URL rewriting to work on my website. Here is the contents of my .htaccess:
RewriteEngine On
RewriteRule ^blog/?$ index.php?page=blog [L]
RewriteRule ^about/?$ index.php?page=about [L]
RewriteRule ^portfolio/?$ index.php?page=portfolio [L]
#RewriteRule ^.*$ index.php?page=blog [L]
Now the 3 uncommented rewrite rules work perfectly, if I try http://www.mysite.com/blog/, I get redirected to http://www.mysite.com/index.php?page=blog, the same for "about" and "portfolio". However, if I mistype blog, say I try http://www.mysite.com/bloh/, then obviously I get a 404 error. The last rule, the commented one, was to help prevent that. Any URL should get redirected to the blog, but of course this rule is still parsed even if we have successfully used a previous one, so I used the "last" flag ([L]). If I uncomment my last rule, anything, including blog, about, and portfolio, redirect to blog. Shouldn't the "last" flag stop the execution as soon as it finds a matching rule?
Thanks.
Yes, the Last flag means it won't apply any of the rules following this rule in this request.
After rewriting the URL, it makes an internal request using the new rewritten URL which would match your last RewriteRule and thus your redirects go into an infinite loop.
Use the RewriteCond directive to limit rewriting to URLs that don't start with index.php, and you should be fine.
You could add a condition like:
RewriteCond %{REQUEST_URI} !^index\.php
I'll also mention that using RewriteRule ^.*$ is a good way to break all of your media requests (css, js, images) as well. You might want to add some conditions like:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
To make sure you're not trying to rewrite actual files or directories that exist on your server. Otherwise they'll be unreachable unless index.php serves those too!
From apache's mod_rewrite docs
'last|L' (last rule)
Stop the rewriting process here and don't apply any more rewrite
rules. This corresponds to the Perl
last command or the break command in
C. Use this flag to prevent the
currently rewritten URL from being
rewritten further by following rules.
Remember, however, that if the
RewriteRule generates an internal
redirect (which frequently occurs when
rewriting in a per-directory context),
this will reinject the request and
will cause processing to be repeated
starting from the first RewriteRule.
You could use
ErrorDocument 404 /index.php?page=blog
but you should be aware of the fact that it doesn't return 404 error code, but a redirect one and I don't know if that is such a good practice.
After you [L]eave processing for the request, the whole processing runs again for the new (rewritten) URL. You could get out of that loop by using this before your other rules:
RewriteRule ^index.php - [L]
which means "for index.php, don't rewrite and leave processing."