Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.htaccess RewriteRule - DOCUMENT_ROOT and RewriteBase

I have a secure/ sub-directory with several files that I want to perform some simple RewriteRules on, just defaulting a PHP extension. I had a hard time getting these to work and after some trial and error stumbled upon the following.

RewriteEngine On
RewriteBase /secure

# Force PHP extension if not a directory
RewriteCond %{DOCUMENT_ROOT}/secure/%{REQUEST_URI} -d
RewriteRule ^(.*)$ - [L]

RewriteCond %{DOCUMENT_ROOT}/secure/$1.php -f
RewriteRule ^((.*/)*[^./]+)/*$ $1.php [L]

My lack of understanding is around %{DOCUMENT_ROOT} and appending /secure/. I believed either %{DOCUMENT_ROOT} or using the RewriteBase would handle this. However, each of these pieces seems to be required. I'd like to know why and what each achieves in my case.

like image 452
Jason McCreary Avatar asked Nov 08 '10 18:11

Jason McCreary


1 Answers

The RewriteBase directive is used by mod_rewrite exclusively during redirection steps, and is ignored during all other processing. Contrary to what the documentation states, it's often not necessary, since (as in your case) it's fairly common for the URL to map to subdirectories in the file system beyond the DOCUMENT_ROOT.

So, what does the directive do? When your rules are being evaluated in a per-directory context as they are when using a .htaccess file, the URL is passed to mod_rewrite very late in Apache's request handling chain. This means that the URL may have already been partially translated into a file system path, so a path in /directory/subdirectory/file may have actually been accessed through the web server via /location/subdirectory/file.

This doesn't seem like much of an issue, but the fact that the /location/ piece has been lost poses an issue for mod_rewrite. To understand why, you have to know how mod_rewrite makes per-directory context rewriting possible.

When you perform a rewrite in a .htaccess file, mod_rewrite reinjects the modified request into Apache as an internal redirect, as if it was a URL. This is problematic, since the request path might not be an appropriate URL to pass. For instance, if the request ended up at /directory/subdirectory/file (which we'll assume is outside the DOCUMENT_ROOT), we might have this rule in /directory/.htaccess:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php

This would examine /subdirectory/file, decide it wasn't an actual file, and rewrite it to index.php. At this point a new URL has to be given back to Apache for the internal redirect to finalize the rewrite. Since it has no point of reference, it ends up sending the whole path—that is, it sends /directory/index.php. This isn't what you want, since the URL to access this location would actually be /location/index.php. That's where RewriteBase comes in. By specifying

RewriteBase /location/

in the .htaccess file, the directory prefix /directory/ would be swapped out for /location/ as part of this post-rewrite processing, resulting in an internal redirect to the anticipated URL /location/index.php.

This also has implications with external redirects as well. If you attempt to externally redirect using the [R] flag with just a path, and the path doesn't have a leading forward slash, the entire directory will be sent back to the client in the manner described above unless it's replaced with the value given in RewriteBase.

Neither of these points appear relevant to your situation though, so you should be fine without specifying the RewriteBase.

The %{DOCUMENT_ROOT} variable is just the the value of Apache's internal DOCUMENT_ROOT variable, which is set in your server/virtual host configuration. It always corresponds to the directory that a request to / resolves to. The -f and -d checks require a full file system path, which is why %{DOCUMENT_ROOT} needs to prepend to the relative path when using them.

For resolving the path of the current request however, mod_rewrite takes care of this for you with the %{REQUEST_FILENAME} variable. For instance, assuming that the .htaccess file lives in your /secure subdirectory, you could modify your rule set as follows:

RewriteEngine On

# Force PHP extension if not a directory
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^.*$ $0.php [L]
like image 192
Tim Stone Avatar answered Oct 18 '22 20:10

Tim Stone