Is it possible to chain RewriteCond in htaccess? - apache

I am going to be doing some basic %{HTTP_HOST} work in my .htaccess file and was wondering if it would be possible to do something similar to this:
RewriteCond %{HTTP_HOST} ((foo|bar|baz).com)$
RewriteCond %{DOCUMENT_ROOT}/apps/%1/webroot%{REQUEST_URI} -d [OR]
RewriteCond %{DOCUMENT_ROOT}/apps/%1/webroot%{REQUEST_URI} -f
RewriteRule %{DOCUMENT_ROOT}/apps/%1/webroot%{REQUEST_URI} [L]
RewriteRule ^(.*)$ index.php?uri=$1 [QSA,L]
basically, if someone visits foo.com on any sub-domain, I want them to be served files directly from that folder but also have any requests that aren't for specific files sent to my index.php file for processing (which will do the routing)
The reason I am asking is because what I have written above does not actually work, so is there a way to do it? (also if this SHOULD work then it'll obviously be a problem with the rest of my .htaccess file, but it all works when dealing with just one application folder)
The other (messy IMO) way would be to route everything to the folders and have a second .htaccess file for each domain, but I'd rather not do this if it can be done in one file!

Your first rewrite rule is missing a regex, which makes the rewrite engine thing you are trying to match (as a regular expression) %{DOCUMENT_ROOT}/apps/%1/webroot%{REQUEST_URI} and you want to rewrite that to [L]. I suspect you want that to look like:
RewriteRule ^ /apps/%1/webroot%{REQUEST_URI} [L]
The ^ matches anything, since you've already vetted the request with your 3 conditions.
Now you need to add a few conditions to your last rule so that it doesn't blindly get applied to everything. You probably want something like:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?uri=$1 [QSA,L]

Related

Why doesn't my .htaccess work when the URL includes a real file name?

I'm trying to pass all requests within a certain subdirectory to a file called "handler.php". The .htaccess I have in place works if the URL is not an actual file name, but not if I enter the name of a real file; it instead loads that file directly, never hitting handler.php.
Could someone explain to me what I'm doing wrong here? The .htaccess file looks like this:
RewriteEngine On
RewriteRule ^$ handler.php?url=$1
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /handler.php?url=$1 [L,QSA]
Is there something I'm doing wrong here? I want all requests to pass the url into handler.php.
The rule doesn't match real files, because the conditions say so
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
These conditions match, if the request does not (!) match a real file (-f) and if it doesn't match a real directory (-d).
If you want to handle all requests no matter what, remove these conditions. You can also remove the first rule, because it is handled by this one too. This leaves just the second rule, but you must prevent a rewrite loop with another condition
RewriteCond %{REQUEST_URI} !^/handler\.php
RewriteRule ^(.*)$ /handler.php?url=$1 [L,QSA]

htaccess and folders does it affect?

i have folders and another php files with an id.
What i want to do is to be able to view those php files in the following format: www.website.com/cakedetails in the browser url and site-wide rather than www.website.com/cakes.php?id=chocolate-cake
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
RewriteRule ^http://www.%{HTTP_HOST}/([a-z.*0-9_-]+)/$ http://www.%{HTTP_HOST}/cakes.php?id=$1
i managed to put the above in my htaccess file, but so far to no avail. the first part of the code converts to www. if it does not have that prefix, and the second part of the code to gather the cake details.
Can you tell me if the above code is correct, as im no expert in htaccess language and i cannot get the above code to work.
Your input is welcome :)
You can't have stuff like this: http://www.%{HTTP_HOST}/ in your rule's regex. The string that the regex matches against doesn't include the protocol/domain name, just the URI, so just the /cakedetails part.
Problem with that is you don't want to blindly match everything, so you need some conditions to make sure someone's not requesting an existing file.
Try:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)$ /cakes.php?id=$1 [L,QSA]

RewriteCond Being Ignored?

I am trying to use mod_rewrite on a Ubuntu 12.04 server to make my URLs more readable, however I want to add an exception for images and css files.
My input URLs are in the format \controller\action which is then re-written to index.php?controller=controller&action=action. I want to add an exception so that if an image or css file is specified, the URL is not re-written, e.g. \images\image.jpg would not be re-written.
My .htaccess code is as follows:
RewriteEngine on
RewriteCond %{REQUEST_URI} !(\.gif|\.jpg|\.png|\.css)$ [NC]
RewriteRule ^([a-zA-z]+)/([a-zA-z]+)$ test.php?controller=$1&action=$2 [L]
RewriteRule ^([a-zA-z]+)/([a-zA-z]+)/([^/]*)$ test.php?controller=$1&action=$2&$3 [L]
My re-write code is working fine and the URLs are coming out as intended, however even if I request an image, the URL is still being re-written. It appears that my RewriteCond is being ignored, anyone any suggestions as to why this might be?
The RewriteCond only applies to your first RewriteRule, it should be reproduced for the second rule. However, I think that is better to add a non-rewriting rule, before, to exclude existing stuffs.
# Do nothing for files which physically exist
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]
# your MVC rules
RewriteRule ^([a-zA-z]+)/([a-zA-z]+)$ test.php?controller=$1&action=$2 [L]
RewriteRule ^([a-zA-z]+)/([a-zA-z]+)/([^/]*)$ test.php?controller=$1&action=$2&$3 [L]
The rewriteCond rule is only applied for the next RewriteRule.
So you need to at least repeat the rewriteCond for your seconde RewriteRule.
No there is certainly better things to do.
For example a usual way of doing it is to test that the url is matching a real static ressource. If all your php code is outside the web directory (in libraries directory, except for index.php) then all styatic ressources available directly on the the document root can only be js files, css files, or image files.
So this is the usual way of doing it:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([a-zA-z]+)/([a-zA-z]+)$ test.php?controller=$1&action=$2 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([a-zA-z]+)/([a-zA-z]+)/([^/]*)$ test.php?controller=$1&action=$2&$3 [L]
But this is a starting point. We could certainly find something to avoid doing 2 rules for this (maybe I'll have a look later)

Whitelist in .htaccess

Instead of blacklisting inaccessible directories (like with deny all) I want to use a whitelist. Basically, I need this functionality:
If the uri requests a file that exists in /public directory, display it;
Otherwise route the request to /public/index.php;
'public' string is not needed in request string: http://site.com/flower.jpg displays DOCUMENT_ROOT/public/flower.jpg file from the file system;
Example:
Directory structure:
public\
flower.jpg
index.php
data\
secret_file.crt
Request string and expected result:
site.com/flower.jpg
flower.jpg is displayed
site.com/data/secret_file.crt
site.com/public/flower.jpg
site.com/public
site.com/data
site.com/any/random_url
request is routed to public/index.php
What I have now:
(and even that with outside help)
# the functionality described in #1 above
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* public%{REQUEST_URI} [L]
# I'd like to take out the following line so ALL other requests route to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php
If I remove the
RewriteCond %{REQUEST_FILENAME} !-f
line, it seizes to work, I've experimented countless configurations, read the modRewrite docs but can't figure out why this simple thing refuses to simply function.
Can anyone help me out or point in the right direction?
Complete final solution for reference
RewriteEngine On
# following line stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule .* /public%{REQUEST_URI} [L]
# don't apply this rule if the first rule has been applied
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule .* /public/index.php [L]
It's a little more complicated when the application is in a subdirectory, like http://site.com/uk/, but this works great.
Ok, this is going to be a little confusing to explain. The problem you are having is that when mod_rewrite rewrites something, without the [R] or [P], it redirects internally, and all the rewrite rules get applied again. This keeps happening until the rewritten uri is the same as the un-rewritten uri. So the first rule you have is getting rewritten by the second rule. You need to prevent that from happening.
First, let's look at the first rule. What you had is totally fine, except you need to add a condition for the caveat site.com/public/flower.jpg rerouted to public/index.php. This means if the request itself has a /public/ in it, it will not serve the request (and let the 2nd rule handle things). An additional caveat here is if you have a directory "public" inside "/public", as in DOCUMENT_ROOT/public/public/, it will be inaccessible.
# Make sure the request itself isn't for /public/
RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
# Make sure the filename exists.
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule ^ /public%{REQUEST_URI} [L]
Here we've done the extra check for a request starting with something like GET /public/flower.jpg, if it matches, we skip this rule entirely. Also, this rule will break if you try to access a directory in /public/. For example, if you have a directory "stuff" inside "/public" and try to access it via the request site.com/stuff/, this rule will not allow you to see the contents (even if there is an index.html file in /stuff/) because you are not checking if directories exist. You can do that by adding this condition for -d, like so:
# Make sure the request itself isn't for /public/
RewriteCond %{THE_REQUEST} !^[A-Z]+\ /public/
# Make sure the filename/directory exists.
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -d
RewriteRule ^ /public%{REQUEST_URI} [L]
The -d condition along with the [OR] of the -f says: if %{DOCUMENT_ROOT}/public%{REQUEST_URI} is a regular file OR a directory. (See the RewriteCond docs)
Now for the second rule, and this is going to look a bit confusing because we have to handle the negation of the first rule's conditions. If the first rule passes and the URI is rewritten, 2 things happen:
The request doesn't start with something like: GET /public/
The uri got rewritten to "/public/[something]"
So we'll have 2 conditions to deal with that. If the first rule rewrote the URI, we don't want to touch it again. This solves the problem that I mentioned in the first paragraph. Additionally, we don't want to URI to get re-rewritten, causing a rewrite loop. So we need to add a condition to stop rewriting if the 2nd rule has already been applied, which means the URI is now /public/index.php. Here are the combination of those conditions:
# stops mod_rewrite from looping because this rule has already been applied
RewriteCond %{REQUEST_URI} !^/public/index.php
# don't apply this rule if the first rule has been applied
RewriteCond %{THE_REQUEST} ^[A-Z]+\ /public/ [OR]
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^ /public/index.php [L]
This may work:
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_FILENAME} -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f
RewriteRule (.*) public$1 [QSA,L]
RewriteRule .* public/index.php
The optimized version may work too but I'm not sure:
RewriteCond %{DOCUMENT_ROOT}(/public|public|)%{REQUEST_FILENAME} -f
RewriteRule (.*) public$1 [QSA,L]
RewriteRule .* public/index.php
By the way your logic is weird: the following rule:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* public/index.php
Means: "if the request is not a file, rewrite to public/index.php". The problem is here: if it's a file, what's going on? Nothing. The RewriteRule is ignored. This is not safe, imagine if it's a file that you may not want the user to access? Just remove this rule, it's useless, and without it, it's safer (from my point of view).
May I ask you to tell me if the optimized version worked?
Please try to use the RewriteLog directive: it helps you to track down such problems:
# Trace:
# (!) file gets big quickly, remove in prod environments:
RewriteLog "/web/logs/mywebsite.rewrite.log"
RewriteLogLevel 9
RewriteEngine On
Tell me if it works.
I'm a bit confused with your first set of rules, since %{REQUEST_URI} would be /public/flower.jpg if I'm not mistaking. I would have done it this way :
RewriteCond public/%{REQUEST_FILENAME} -f
RewriteRule ^.*$ public/%{REQUEST_FILENAME} [L]
RewriteCond public/%{REQUEST_FILENAME} !-f
RewriteRule ^.*$ public/index.php [L]
I'm not sure of the behaviour if %{REQUEST_FILENAME} is empty but basically the rules says:
If the filename exists in public, rewrite all URI to that file, if it does not rewrite to index.php
Would that work for you?
Have you considered programmatically creating your .htaccess file to blacklist anything that isn't on a whitelist that you set in whatever file you use to create it? If you ask me, you can't get much simpler.

How do you combine these 2 .htaccess RewriteRules into one?

Ok I have another question and I'm a beginner at this.
I have this RewriteRule, it redirects the query correctly but doesn't allow me to use the other directories:
RewriteRule ^([0-9A-Za-z]+)/?$ /query.php?id=$1 [L]
and now this RewriteRule to skip all these directories but now the rule above needs to be commented out for this to work.
RewriteRule ^(css|js|admin|pages|includes|images)(/|$) - [L]
Can I combine the two? If so, how?
RewriteRules are checked in the order they occur in the file, so if you put the css|js|admin|pages|includes|images rule first, it will match first and stop the rewriting process before the other rule is reached. Just make sure to keep the [L] flag at the end of that rule.
There's also this neat trick:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+) query.php?id=$1 [L]
That is, if the file path is not an existent file or directory, send the request to a PHP script (so that you may load some module dynamically or show a useful 404 page).