Search code examples
phpapache.htaccessmod-rewrite

issues with .htaccess rewrite rules


Having issues with .htaccess rewrite rules. Passing a URL to the browser from users email that includes a token for password reset.

Current .htaccess rules:

Options +FollowSymLinks -MultiViews
RewriteEngine On
RewriteBase /

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.DOMAIN\.com [NC]
RewriteRule ^(.*)$ https://DOMIAN.com/$1 [L,R=301]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.*)$ $1.php [L]

RewriteCond %{THE_REQUEST} ^(?:GET|POST)\ /.*\.php\ HTTP.*$ [NC]
RewriteRule ^(.*)\.php$ $1 [R=301,L]

when user trying to go to

example.com/activate/00803e6632236414ebcdc34c7e7690d764e567083fac6

produces these errors in debug

example.com/activate/00803e6632236414ebcdc34c7e7690d764e567083fac6.php.php.php.php.php.php.php.php.php.php

So I update .htaccess per your notes below like this:

Options +FollowSymLinks -MultiViews

RewriteEngine On
RewriteBase /

RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.EXAMPLE\.com [NC]
RewriteRule ^(.*)$ https://EXAMPLE.com/$1 [L,R=301]

RewriteCond %{THE_REQUEST} ^(?:GET|POST)\ /.*\.php\ HTTP.*$ [NC]
RewriteRule ^(.*)\.php$ $1 [R=301,L]

# Rewrite extensionless ".php" URLs
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule (.+) $1.php [L]

# Rewrite "/<file>/<code>" to "/<file>.php/<code>"
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^([^/]+)/([a-f0-9])$ $1.php/$2 [L]

And I get the following error:

AH00128: File does not exist: /var/www/html/htdocs/activate/00803e6632236414ebcdc34c7e7690d764e567083fac6


Solution

  • RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME}\.php -f
    RewriteRule ^(.*)$ $1.php [L]
    

    The problem with this rule is that you are checking one filesystem path (ie. %{REQUEST_FILENAME}\.php) and rewriting to another ($1.php). These do not necessarily refer to the same thing. For a request to /activate/foo, where activate is not a filesystem directory then REQUEST_FILENAME is of the form /var/www/html/htdocs/activate (so the file check is successful since /var/www/html/htdocs/activate.php exists), but it rewrites the request to activate/foo.php (using the $1 backreference) - which does not exist. It will do this repeatedly, appending .php each time (until it reaches an internal rewrite limit; default 10).

    Aside#1: There is no need to check that the request does not map to a directory and does not map to a file before checking that the request+.php does map to a file. That's 3 (expensive) filesystem checks when only one is required.

    Aside#2: You also don't need to backslash escape the literal dot in the TestString (first argument), since this is an "ordinary" string, not a regex.

    This rule needs to be corrected, so that you are testing the same file-path that you are ultimately rewriting to. For example:

    # Rewrite extensionless ".php" URLs
    RewriteCond %{DOCUMENT_ROOT}/$1.php -f
    RewriteRule (.+) $1.php [L]
    

    You then need an additional rule to rewrite the request /activate/<code> to /activate.php/<code> (passing /<code> to your script as path-info). This could be "hardcoded" if this is a one-off. For example:

    # Rewrite "/activate/<code>" to "/activate.php/<code>"
    RewriteRule ^(activate)/([a-f0-9]+)$ $1.php/$2 [L]
    

    (I'm assuming <code> is a hexadecimal sequence, which appears to be the case in your example.)

    Or make it more generic, if you have similar requests. eg. /<file>/<code> to /<file>.php/<code>. For example:

    # Rewrite "/<file>/<code>" to "/<file>.php/<code>"
    RewriteCond %{DOCUMENT_ROOT}/$1.php -f
    RewriteRule ^([^/]+)/([a-f0-9]+)$ $1.php/$2 [L]
    

    Aside:

    RewriteCond %{THE_REQUEST} ^(?:GET|POST)\ /.*\.php\ HTTP.*$ [NC]
    RewriteRule ^(.*)\.php$ $1 [R=301,L]
    

    This rule (external redirect) should be before the above rewrites.