Search code examples
phpregexapache.htaccess

How can I add a condition to RewriteRule that would exclude files that do not exist in a subdirectory?


I am looking to exclude a rule from being called if the page does not exist. In my existing htaccess, this rule works as intended when the user accesses a page that does not exist from the root. Oddly enough, it does not work if the user tries to access a subdirectory of a valid page that does not exist.

The current behavior of my htaccess:

example.com/valid-page/ -> example.com/valid-page/ (good)
example.com/valid-page -> example.com/valid-page/ (good)
example.com/valid-page/valid-page -> example.com/valid-page/valid-page/ (good)
example.com/does-not-exist -> example.com/does-not-exist (good)
example.com/valid-page/does-not-exist -> example.com/valid-page/does-not-exist/ (boo!)

With the bad example above, the desired outcome should be not to included the forward slash:

example.com/valid-page/does-not-exist -> example.com/valid-page/does-not-exist

Here is the .htaccess:

# Redirects for errors
ErrorDocument 404 /error.php
ErrorDocument 403 /error.php

# Hides directory file listings
Options -Indexes

# Turn mod_rewrite on
RewriteEngine On
RewriteBase /

# Force slash at end of URL
RewriteCond %{REQUEST_URI} /+[^\.]+$
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]

# To externally redirect /dir/foo.php to /dir/foo
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s([^.]+)\.php [NC]
RewriteRule ^ %1 [R,L]

# To internally forward /dir/foo to /dir/foo.php
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^([^.]+)/$ $1.php [NC,L]

Solution

  • # Force slash at end of URL
    RewriteCond %{REQUEST_URI} /+[^\.]+$
    RewriteCond %{REQUEST_FILENAME}.php -f
    RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]
    

    You need to change the 2nd condition that checks against REQUEST_FILENAME, because when you request /valid-page/does-not-exist, REQUEST_FILENAME is .../valid-page, not .../valid-page/does-not-exist as you are expecting.

    Instead of REQUEST_FILENAME, use REQUEST_URI (or the $1 backreference) and construct the absolute filename instead. For example:

    # Force slash at end of URL
    RewriteCond %{REQUEST_URI} /+[^\.]+$
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.php -f
    RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]
    

    You will need to clear your browser cache before testing since the erroneous 301 (permanent) redirect will have been cached by the browser. Test with 302 (temporary) redirects to avoid caching issues.

    Now, when you request /valid-page/does-not-exist you are checking whether .../valid-page/does-not-exist.php exists as a file, not .../valid-page.php as before.

    See my answer to the following question on ServerFault which goes into more detail about what REQUEST_FILENAME is actually set to. https://serverfault.com/questions/989333/using-apache-rewrite-rules-in-htaccess-to-remove-html-causing-a-500-error