35
php_flag display_errors 1
php_value auto_prepend_file init.php
RewriteEngine on 
RewriteRule ^$  /id/authenticate [R]
RewriteRule ^login_openid$  /id/login_openid.php [QSA,L]
RewriteRule ^authenticate$  /id/authenticate.php [QSA,L]
RewriteRule ^facebook$  /id/facebook.php [QSA,L]
RewriteRule ^createfromopenid$  /id/createfromopenid.php [QSA,L]

RewriteRule .* - [L,R=403]

This is my .htaccess file. In the serverconfig I just have AllowOVerride all.

If I request the URL http://mydomain.com/id/authenticate I get a 403 Error. If I remove the last rule, it works. Shouldnt the [L] flat prevent any further rules from happening?

Edit:

My htaccess file is in the subfolder "id", so the rules work.

The Surrican
  • 29,118
  • 24
  • 122
  • 168
  • 1
    Your statements that none of the rules except the last one match is a valid thought, but `If I remove the last rule, it works.` implies that they do work. Also you did assume that my htaccess file lies in the web root folder and not in the subfolder, which was not the case and not contextual from the question. However I don't need your help because you are wrong again. I just found the correct answer to the problem at serverfault: http://serverfault.com/questions/241907/rewriterules-not-stopping-with-last-flag turns out the L rules do NOT work altough the rewriterules match. – The Surrican Jul 23 '11 at 03:29
  • 11
    [You might be looking for the `END` flag (Available in 2.3.9 and later)](http://stackoverflow.com/a/12106419/367456) – hakre Aug 24 '12 at 10:05

2 Answers2

87

The [L] rule works fine -- you just do not know how it actually works.

When Apache sees the [L] flag and rule matches (rewrite occurs), Apache will go to next iteration and will start matching all rules again from top. The [L] flag means "do not process any rules below in this iteration".

Yes, the Apache documentation is not 100% clear on this (which means it can be improved), but provides enough info to figure it out eventually.


Apache will stop rewrite cycle in few situations:

  1. No rules matched at all (no rewrite occurred);

  2. "exit now" rule matched (e.g. RewriteRule .* - [L]);

  3. Rewrite occurs, but input URL and final URLs are the same (happens on 2nd-3rd iteration when "badly" written rule rewrites the same URL to the same URL.

    For example RewriteRule (.*) /index.php?page=$1 [L]:

    • /hello => /index.php?page=hello
    • on next iteration it will rewrite /index.php => /index.php?page=index.php
    • and on 3rd iteration it will be /index.php => /index.php?page=index.php .. which makes no sense now);
  4. Rewrite iteration limit is reached (by default = 10) -- that's if you entered infinite rewrite cycle (the value is controlled by LimitInternalRecursion Directive).


With all aforementioned information I can say that your current rules do work as expected. This means that you have to change the logic and get rid of the last rule (maybe handle this moment in parent .htaccess .. or handle it differently -- all depends on how your application is built, I do not want to make wild guesses).

LazyOne
  • 158,824
  • 45
  • 388
  • 391
  • 1
    OK, I think understand that and it makes sense. However does the 2. point "exit now" really exit the rewrite cycle? Because an abortion of the rewrite cycle is just what not happens? Well, from what I read here I think probably the C|chain flag is the best way to go for me! – The Surrican Jul 23 '11 at 18:23
  • 3
    @Joe The `-` as target URL means no rewrite, which is pretty much the same as #3 (URL on start of iteration = URL at the end of iteration). Since URL is not changed **at all**, the Apache will exit the cycle. – LazyOne Jul 23 '11 at 18:30
  • 3
    Depending on your app/rewrite logic you may use this rule: `# do not do anything for already existing files` `RewriteCond %{REQUEST_FILENAME} -f [OR]` `RewriteCond %{REQUEST_FILENAME} -d` `RewriteRule .+ - [L]` -- and it works fine. – LazyOne Jul 23 '11 at 18:32
  • 1
    I am sorry i was just crossing my wires... its all clear now. Thanks for that detailed and understandable explanation! – The Surrican Jul 23 '11 at 18:37
  • 2
    @Joe Let's have an example based on your rules: request URL is `/id/login_openid`. **Initial iteration:** URL is stripped to directory level and = `login_openid`. This will match rule #2 (line #5). Rewrite occurs and new URL is `/id/login_openid.php` and rewrite goes to next iteration. **Iteration #2**: URL is stripped down and = `login_openid.php`. This will much no rules EXCEPT the last one, which tells Apache to abort request (send 403 response) -- that is how your current rules work. – LazyOne Jul 23 '11 at 18:39
  • 2
    @LazyOne, how do we do an "exit now"? Basically I just want apache to rewrite *once* and exit.. – Pacerier Oct 12 '12 at 17:50
  • 1
    @Pacerier It's a tough question and I cannot give you concrete solution that will work for every situation. It's rather situation-dependant; but the general idea is to change the rewrite logic. For example -- you may utilise `S` flag, but it's quite unreliable as you may have to update such rules if other rules will be added/removed; etc. No concrete answer, sorry. – LazyOne Oct 12 '12 at 20:40
  • 4
    @LazyOne, hmm what about the END flag as hakre mentioned above? – Pacerier Oct 12 '12 at 20:55
  • 3
    @Pacerier Yes, it should do the job (from description it is exactly what is needed), but it only available in 2.3.x version -- I personally have not dealt with it yet (all servers are still v2.2 or even below) and have no real idea how wide-spread it is. If you control whole server (can install that version .. or it is already installed) then you definitely should try it. – LazyOne Oct 12 '12 at 22:14
  • Thank you for that explanation - everywhere I look for what `[L]` means was only info "This is last rule". And that's it. No mention about another iteration. Thanks to you I finally understand how it's working. Thanks! – S1awek Jun 20 '20 at 13:06
14

Put this in front of your cath all rule.

RewriteCond %{ENV:REDIRECT_STATUS} !=200

The problem is that once the [L] flag is processed, all the next RewriteRules are indeed ignored, however, the file gets processed AGAIN from the begin, now with the new url.

This magic Condition will not process the catch all if the file was already redirected.

PS: If it doesn't work, you might need to tweak the condition a bit: 200, !=200, ^., ^$.
Apparentely the variable gets set to 200 for a redirect, but also other pages (error and stuff) set it to some value. Now that means that you either check if it is empty, is not empty, is 200 or is not 200, depending on what you need.

Qwerty
  • 29,062
  • 22
  • 108
  • 136
  • 1
    I had a very similar problem and this solved it for me, using this as a 'catch all' at the end of my .htaccess `RewriteCond %{ENV:REDIRECT_STATUS} !=200 RewriteRule .* /index.php [L]` – Waboodoo Mar 25 '15 at 10:30