Apache, mod_rewrite, The [N] Flag And An Infinite Loop

When I renamed Utterson to Newcomen, I installed some rewrite rules to make sure the old Utterson URLs redirect properly to their Newcomen counterparts.

In theory, this should have been pretty easy, mod_rewrite should allow some equivalent to Perl’s s/…/…/g for its rewrite rules. Checking the mod_rewrite documentation, the [N] flag seemed to be exactly what I was looking for.

The [N] flag causes the ruleset to start over again from the top, using the result of the ruleset so far as a starting point. Use with extreme caution, as it may result in loop.

The [Next] flag could be used, for example, if you wished to replace a certain string or letter repeatedly in a request. The example shown here will replace A with B everywhere in a request, and will continue doing so until there are no more As to be replaced.

RewriteRule (.*)A(.*) $1B$2 [N]

You can think of this as a while loop: While this pattern still matches (i.e., while the URI still contains an A), perform this substitution (i.e., replace the A with a B).

I had to replace utterson with newcomen and Utterson with Newcomen (the capitalized names occur in the API documentation). Basically, what I did was the following (don’t try this at home):

RewriteRule ^/?(.*)utterson(.*) /$1newcomen$2 [N]
RewriteRule ^/?(.*)Utterson(.*) /$1Newcomen$2 [N]

This, for some reason, did not work. And it didn’t even result in a 500 server error. It did, however, cause an infinite rewrite loop (unaffected by any LimitInternalRecursion setting), until the process finally ran out of memory. (By the way, any of these two individual rules by itself seems to work fine.)

Well, to make this short, you can find the reason in this bug report (it’s actually a duplicate of this one, but the former one explains what’s going on in the first post). And the “fix” for this behaviour: they introduced the [DPI] flag (in version 2.2.12).

The documentation for [N] only says: “Use with extreme caution, as it may result in loop.” Mentioning the [DPI] flag would be too easy, I guess. Of course, it would not prevent all the possible loops. But it works pretty well for loops that should not occur in the first place.

Anyway, what I’m using now is the following (note that I shortened the lines a bit, but you should get the idea):

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .? - [S=2]

RewriteRule ^/?(.*)utt…(.*) /$1new…$2 [N,DPI,E=redir:1]
RewriteRule ^/?(.*)Utt…(.*) /$1New…$2 [N,DPI,E=redir:1]

RewriteCond %{ENV:redir} ^1$
RewriteRule (.*) $1 [R=301]

For any existing files or directories the first two rewrite rules will be skipped entirely. If one of them matches, the URL will be rewritten as required and the environment variable redir will be set to 1. The last condition checks this variable, and if it is set to 1 (meaning there was a rewrite) it will send a proper 301 redirect to the new location.