too long

I am using MAMP, with a virtual-host based setup creating a '.dev' tld for working on. My site's .htaccess uses mod_rewrite like so:

RewriteEngine on
RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.php -f

RewriteRule ^(.*)$ $1.php [L]
RewriteRule ^(butchers|news|recipes)/([a-zA-Z0-9-/]+)$ /$1.php?s=$2 [L,QSA]

My website features a directory of butcher's shops. The list view is displayed by butchers.php accessible at butchers. The detail view is displayed by butchers.php?s=some-butcher-name accessible at butchers/some-butcher-name. The same mechanism works for the News and Recipes sections, as you can see.

Ok here's the thing: this used to work. It worked fine, and then I rebooted my system some time after upgrading MAMP and now it gives me an Internal server error. I assume I've gone from Apache 1.x to 2.x; I had to redo my vhost config after upgrading but everything else is vanilla (same as last time) except that I have disabled MultiViews. The mod_rewrite log looks like this (first few columns truncated)

[perdir /Users/myusername/Sites/dev/sitename/] add path info postfix: /Users/myusername/Sites/dev/sitename/butchers -> /Users/myusername/Sites/dev/sitename/butchers/example-butcher
[perdir /Users/myusername/Sites/dev/sitename/] strip per-dir prefix: /Users/myusername/Sites/dev/sitename/butchers/example-butcher -> butchers/example-butcher
[perdir /Users/myusername/Sites/dev/sitename/] applying pattern '^(.*)$' to uri 'butchers/example-butcher'
[perdir /Users/myusername/Sites/dev/sitename/] RewriteCond: input='/Users/myusername/Sites/dev/sitename/butchers' pattern='!-d' => matched
[perdir /Users/myusername/Sites/dev/sitename/] RewriteCond: input='/Users/myusername/Sites/dev/sitename/butchers' pattern='!-f' => matched
[perdir /Users/myusername/Sites/dev/sitename/] RewriteCond: input='/Users/myusername/Sites/dev/sitename/butchers.php' pattern='-f' => matched
[perdir /Users/myusername/Sites/dev/sitename/] rewrite 'butchers/example-butcher' -> 'butchers/example-butcher.php'
[perdir /Users/myusername/Sites/dev/sitename/] add per-dir prefix: butchers/example-butcher.php -> /Users/myusername/Sites/dev/sitename/butchers/example-butcher.php
[perdir /Users/myusername/Sites/dev/sitename/] trying to replace prefix /Users/myusername/Sites/dev/sitename/ with /
strip matching prefix: /Users/myusername/Sites/dev/sitename/butchers/example-butcher.php -> butchers/example-butcher.php
add subst prefix: butchers/example-butcher.php -> /butchers/example-butcher.php
[perdir /Users/myusername/Sites/dev/sitename/] internal redirect with /butchers/example-butcher.php [INTERNAL REDIRECT]

Here's my understanding of what's happening:

  • My UA asks for butchers/example-butcher
  • When REQUEST_FILENAME is used in the RewriteCond, it outputs the path with only butchers, not butchers/example-butcher
  • This passes, because butchers.php is real
  • Rewrite is allowed to append '.php', as per the RewriteRule
  • When it appends, it appends it to the original request, butchers/example-butcher
  • The internal redirect restarts the loop with butchers/example-butcher.php

Then process starts again with the same problem - that the RewriteCond tests this weird, incomplete version of the path, but operates on the correct path. This happens ten times, accumulating more and more .php's, before apache hits the limit and issues a 500.

  • request butchers/example-butcher
  • is butchers.php a real file? yes!
  • append .php
  • request butchers/example-butcher.php
  • is butchers.php a real file? yes!
  • append .php
  • request butchers/example-butcher.php.php
  • ... and so on

So, I can see why there is a loop, and why it wants to keep adding .php to itself, I just don't understand why REQUEST_FILENAME only ever gives the first level of the path. I can't find any documentation that says that this is prescribed behaviour.

Any ideas? I've already burned a whole workday on this :/

Thanks!

The reason for this is because the %{REQUEST_FILENAME} variable isn't simply the requested URI mapped to a single file/directory. When you have a request like:

/foo/bar/something

And you have either the files:

/foo.php
/foo/bar.php
/foo/bar/something.php

the condition:

RewriteCond %{REQUEST_FILENAME}.php -f

will be true because mod_rewrite also accounts for PATH_INFO. If you have one of those php files, then mod_rewrite will attempt to be clever and figure out if a requested URI is actually within the requested URI path and add the .php to that path node instead of at the very end. To strictly check if the request + .php exists, you need to do:

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.php -f