8

I understand that the [L] following a rewrite rule means that it's the "last" but I'm not clear what the scope is

For example, when there are multiple .htaccess files, some with [L], which ones will apply?

Example:

root.com/subdirectory1/subdirectory2

  ^           ^            ^      
  |           |            |
  A           B            C

If there is an .htaccess file in each directory...

  1. In what order will they apply?
  2. If they contradict each other which takes precedence?
  3. Do they apply in serial? Are the results of the first passed to the next?
  4. Will matches in an earlier (or the same) file that RESULT from a later rule get applied?
  5. If there is an [L] in an earlier file will other files get considered?
Konerak
  • 39,272
  • 12
  • 98
  • 118
emersonthis
  • 32,822
  • 59
  • 210
  • 375
  • 4
    @casperOne: This is a programming question and not off topic IMHO. htacess files, and more specifically the rewrite rules the OP asks about, influence the control flow in a web application. "Execution precedence" surely sounds like a programmer's problem, does it not? – hashchange Jun 07 '13 at 08:00

1 Answers1

15

The [L] flag does indeed mean "last", but it only applies to the rules in the current scope. From your question, it only applies to the rules in the htaccess file. If there are rules in a subdirectory, any rules in the parent directory are thrown out the window. They are not applied at all, unless you use the RewriteOptions Inherit directive (which will append to the end of the rules any rules from the parent htaccess file).

Given your example:

root.com/subdirectory1/subdirectory2
  ^           ^            ^      
  |           |            |
  A           B            C

If there are htaccess files in A, B, and C, and rewrite rules in all 3, if one requests http://root.com (your "root.com" directory), only the rules in A get applied. If someone requests http://root.com/subdirectory1, then only the rules in B get applied, and any rules in A are ignored (without the Inherit option). Likewise, if someone goes to http://root.com/subdirectory1/subdirectory2, then only the rules in C get applied, if there are no inheriting options.

The [L] flag has no play in any of this, as the scope here is only within the rules of an htaccess file. Also note that [L] doesn't necessarily mean "stop rewriting HERE", as the rewrite engine will loop until the URI going into the engine stops changing. The [L] just means to stop rewriting in the current iteration of the rewrite engine's looping.


A bit more detail on the looping stuff:

During the URL processing pipeline, apache attempts to map a URL to a file or resource. Lots of different modules have a part to play in the processing pipeline, like mod_rewrite or mod_proxy or mod_alias. At any point, this URI can change, be flagged to be redirected, be flagged to be proxied, be flagged to throw an error, etc. When the URI gets to mod_rewrite, the rewrite engine collects a bunch of rules from the vhost config and the appropriate htaccess file; note, 2 different scopes here. Each scope of rules are applied to the URI and if none of the rules match, then mod_rewrite is done. If one of the rules match, there is an internal redirect, meaning the URI is changed and then redirected back to the processing pipeline and mod_rewrite. Thus the same scope of rules get applied again, and if one of the rules match and gets applied, the same thing happens again the the rules loop again. There's a directive that you can set in the vhost/server config called LimitInternalRecursion which sets the limit of these internal redirects. If the number of times the rewrite engine loops (i.e. redirects back to itself) exceeds this limit (which by default is 10, I think), then you get a 500 Internal Server error.

This might sound kind of weird but there's a lot of instances for wanting to do this. Example: remove all _ from URI and replace with -:

RewriteRule ^(.*)_(.*)$ /$1-$2 [L]

If the URI is /a_b_c_d_foo then the first time, the URI gets changed to /a_b_c_d-foo, then it loops and gets changed to `/a_b_c-d-foo, then again /a_b-c-d-foo and by the 5th time around you get /a-b-c-d-foo. It'll loop once more but since the ^(.*)_(.*)$ pattern doesn't match, the URI passes through the rewrite engine and the looping stops.

The problem arises when people create rules that don't take into account the looping, for example: rewrite /<anything> to /foo/<anything>:

RewriteRule ^(.*)$ /foo/$1 [L]

If the URI is /bar then the first time the URI gets rewritten to /foo/bar, and this is the desired result. But the URI gets internally redirected back into the rewrite engine and the same rule is matched again, resulting in: /foo/foo/bar, then again: /foo/foo/foo/bar, and again: /foo/foo/foo/foo/bar, until the internal recursion limit is reached and you get a 500 Server Error.

Jon Lin
  • 142,182
  • 29
  • 220
  • 220
  • +1 Very well writeup. – Olaf Dietsche Apr 08 '13 at 07:48
  • This is great. Can you clarify the last sentence in your answer? I learned a lot from your answer, but I don't understand the manner in which the rewrite engine "loops". – emersonthis Apr 08 '13 at 20:47
  • @Emerson See the additional explanation – Jon Lin Apr 08 '13 at 21:33
  • AHA! And in the final problematic example, the [L] won't help because it only prevents further rewrites in that specific loop, not a future loop from happening. Am I getting it? – emersonthis Apr 08 '13 at 21:55
  • @Emerson Correct. If there was a rule after, the `[L]` would prevent the other rule from being evaluated, but the `[L]` doesn't prevent the internal redirect. – Jon Lin Apr 08 '13 at 22:25
  • Thanks. Can I up vote twice!!? – emersonthis Apr 09 '13 at 02:54
  • 1
    @JonLin One more question: In the course of the looping, if an external rewrite is encountered, does this immediately break the loop? For example, if the .htaccess in `A` has something like `RewriteRule ^(.*)$ http://www.google.com [R=301,L]` will all the lower .htaccess become irrelevant? – emersonthis Apr 09 '13 at 15:28
  • @Emerson Yes, because of the `L`, none of the rules below will be evaluated, and because of the redirect going to somewhere *not* on your server, the request is never coming back to you – Jon Lin Apr 09 '13 at 16:30