Apache – Fix Rewrite Causes Infinite Recursion if Subdirectory is Requested

apache-2.4mod-rewrite

I have a rewrite rule that works most of the time. However, if I request a page with a subdirectory that doesn't exist, and the subdirectory matches an existing file, it causes infinite recursion.

    Options +FollowSymLinks
    AllowOverride None
    Require all granted

    RewriteEngine on

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

This works if I request:

  • test.php
  • test (it serves test.php)
  • badname.php or badname (gives 404 correctly)
  • directory/file_that_exists or directory/file_that_exists.php

All work. But if test.php exists, and I request test/test, it causes infinite recursion. The error log looks like this:

[Wed May 23 08:16:28.176564 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test.php.php.php
[Wed May 23 08:16:28.176566 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test.php.php
[Wed May 23 08:16:28.176568 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test.php
[Wed May 23 08:16:28.176570 2018] [core:debug] [pid 27054] core.c(3620): [client 0.0.0.0:57764] AH00122: redirected from r->uri = /test/test

What am I doing wrong? Shouldn't it fail when it sees that test/test.php doesn't exist? (RewriteCond %{REQUEST_FILENAME}\.php -f) I get the same result if I simply append a slash, like test/.

Best Answer

Shouldn't it fail when it sees that test/test.php doesn't exist? (RewriteCond %{REQUEST_FILENAME}\.php -f) I get the same result if I simply append a slash, like test/

It would fail if that was the check being performed, but it's not.

The REQUEST_FILENAME server variable contains the filesystem path after the URL has been mapped to the filesystem. This is not necessarily the same as the URL-path that the RewriteRule pattern matches against.

For example, in your case, when requesting /test/test.php where there is no physical subdirectory called /test, the REQUEST_FILENAME server variable contains a string of the form /absolute/filesystem/path/to/test (ie. the requested URL up to the last known directory + the first path segment /test). And the URL-path matched by the RewriteRule pattern will be /test/test.php (to which you will repeatedly append .php because /test.php does exist, as checked by the preceding RewriteCond directive).

You can resolve this by checking the URL-path instead (building an absolute filesystem path to check), much like @shanew suggests. For example:

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

An optimisation, to avoid all those filesystem checks for every request would be to include a check for the .php extension (and .css, .js, etc.) first, before checking to see if the files exist:

RewriteCond %{REQUEST_URI} !\.(php|css|js|jpg|png)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/$1\.php -f
RewriteRule ^(.*)$ $1.php [L]

This avoids unnecessary checks when the requested URL already ends with .php, .css, etc.

Related Topic