I am writing a CMS type application and I am trying to restrict how the server gets accessed.
- Any content (in js/, css/ or images/ directories) should be served as is, as long as there is an index.php file at the same level as the content directory
- All other paths should run through index.php to determine what content gets displayed
- The executed index.php file must not appear in the URL, although other instances must not get removed, in case of embedding another URL within the parameter part of the URL
- There must be only one .htaccess file
- The .htaccess file must work without modification not matter which directory it is placed in
I don't have access to the httpd.conf file on the hosted server. The .htaccess file is below. It sets REWRITE_BASE
to the directory the .htaccess file is in, making it the "virtual" web root. It also sets INDEX_PATH
and INDEX_PARAMS
which are the bits before and after where the index.php
would be in the URL if it hadn't been taken out.
For example: http://somedomain.net/testsite/foo/bar/baz/qux/
The .htaccess file is in testsite/
. There is an index.php file in testsite/
and another one in testsite/foo/bar/
. When the above URL is requested the script in testsite/foo/bar/
will get executed. PHP will have access to three additional fields in $_SERVER
: REWRITE_BASE => '/testsite/'
, INDEX_PATH => 'foo/bar/'
& INDEX_PARAMS => 'baz/qux/'
.
It seems to behave properly. Can the code/rules be simplified? Am I using any unnecessary flags? Have I missed any flags that would really help? Is there a better way of serving the final matched file other than setting E=FINAL:true
and testing for it in the next iteration?
Options -Indexes
DirectoryIndex
RewriteEngine On
# Find directory that this .htaccess file is saved in
# relative to DOCUMENT_ROOT and store the value in ENV:REWRITE_BASE
# The value will always begin and end with a slash: e.g. /, /dir1/, /dir1/dir2/, etc.
# Leave RewriteBase set to /. This file can now be moved without needing to be edited
# Use %{ENV:REWRITE_BASE} at the start of every path replacement pattern
RewriteBase /
RewriteCond $0#%{REQUEST_URI} ([^#]*)#(.*)\1$
RewriteRule ^.*$ - [E=REWRITE_BASE:%2]
# Remove REDIRECT_.* copies of variables
RewriteRule ^ - [E=INDEX_PATH:%{ENV:REDIRECT_INDEX_PATH},E=!REDIRECT_INDEX_PATH,E=INDEX_PARAMS:%{ENV:REDIRECT_INDEX_PARAMS},E=!REDIRECT_INDEX_PARAMS,E=!REDIRECT_REWRITE_BASE,E=!REDIRECT_STATUS]
# If FINAL was set in the last iteration (now REDIRECT_FINAL)
# don't redirect again, stop processing ([L]ast rule) and unset REDIRECT_FINAL
RewriteCond %{ENV:REDIRECT_FINAL} true
RewriteRule ^ - [L,E=!REDIRECT_FINAL]
# If index.php and one of js/, css/, images/ exist at the same level serve the static file
RewriteCond %{DOCUMENT_ROOT}%{ENV:REWRITE_BASE}$0 -d
RewriteCond %{DOCUMENT_ROOT}%{ENV:REWRITE_BASE}$1index.php -f
RewriteRule ^(.*/)?(js|css|images)/$ %{ENV:REWRITE_BASE}$0%{ENV:INDEX_PARAMS} [L,E=FINAL:true]
# If there is an index.php file in the current directory, and the end of the URI is /index.php
# redirect (302 test, 301 production) to without /index.php, adding the INDEX_PARAMS back on
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*/)?index.php/?$ %{ENV:REWRITE_BASE}$1%{ENV:INDEX_PARAMS} [L,R=302]
# If there is an index.php file in the current directory, serve it
RewriteCond %{REQUEST_FILENAME}/index.php -f
RewriteRule ^.*$ %{ENV:REWRITE_BASE}$0index.php [L,NS,E=FINAL:true]
# If the current directory doesn't contain index.php
# record the path to the current directory and the path removed afterwards
# Restart the rules, trying one directory further down
RewriteCond %{REQUEST_FILENAME}/index.php !-f
RewriteRule (.*/)?(.+)$ %{ENV:REWRITE_BASE}$1 [L,QSA,E=INDEX_PATH:$1,E=INDEX_PARAMS:$2%{ENV:INDEX_PARAMS}]
# If there is no index.php file at the same level as this .htaccess file
# reset the URI to the original request and allow Apache to process it (e.g. 404)
RewriteRule ^$ %{ENV:REWRITE_BASE}%{ENV:INDEX_PARAMS} [L,E=!INDEX_PATH,E=!INDEX_PARAMS,E=!REWRITE_BASE,E=FINAL:true]