133

Is this how to use AND, OR for RewriteCond on Apache?

rewritecond A [or]
rewritecond B
rewritecond C [or]
rewritecond D
RewriteRule ... something

becomes if ( (A or B) and (C or D) ) rewrite_it.

So it seems like "OR" is higher precedence than "AND"? Is there a way to easily tell, like in the (A or B) and (C or D) syntax?

Gumbo
  • 643,351
  • 109
  • 780
  • 844
nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • 3
    Yes, correct. [OR] is higher precedence than the (implicit) "AND". The combined condition is indeed ((A or B) and (C or D)). – Doin May 22 '13 at 18:13
  • 2
    You may find the details in http://www.ckollars.org/apache-rewrite-htaccess.html#precedence helpful. – Chuck Kollars May 14 '16 at 21:18

2 Answers2

137

This is an interesting question and since it isn't explained very explicitly in the documentation I'll answer this by going through the sourcecode of mod_rewrite; demonstrating a big benefit of open-source.

In the top section you'll quickly spot the defines used to name these flags:

#define CONDFLAG_NONE               1<<0
#define CONDFLAG_NOCASE             1<<1
#define CONDFLAG_NOTMATCH           1<<2
#define CONDFLAG_ORNEXT             1<<3
#define CONDFLAG_NOVARY             1<<4

and searching for CONDFLAG_ORNEXT confirms that it is used based on the existence of the [OR] flag:

else if (   strcasecmp(key, "ornext") == 0
         || strcasecmp(key, "OR") == 0    ) {
    cfg->flags |= CONDFLAG_ORNEXT;
}

The next occurrence of the flag is the actual implementation where you'll find the loop that goes through all the RewriteConditions a RewriteRule has, and what it basically does is (stripped, comments added for clarity):

# loop through all Conditions that precede this Rule
for (i = 0; i < rewriteconds->nelts; ++i) {
    rewritecond_entry *c = &conds[i];

    # execute the current Condition, see if it matches
    rc = apply_rewrite_cond(c, ctx);

    # does this Condition have an 'OR' flag?
    if (c->flags & CONDFLAG_ORNEXT) {
        if (!rc) {
            /* One condition is false, but another can be still true. */
            continue;
        }
        else {
            /* skip the rest of the chained OR conditions */
            while (   i < rewriteconds->nelts
                   && c->flags & CONDFLAG_ORNEXT) {
                c = &conds[++i];
            }
        }
    }
    else if (!rc) {
        return 0;
    }
}

You should be able to interpret this; it means that OR has a higher precedence, and your example indeed leads to if ( (A OR B) AND (C OR D) ). If you would, for example, have these Conditions:

RewriteCond A [or]
RewriteCond B [or]
RewriteCond C
RewriteCond D

it would be interpreted as if ( (A OR B OR C) and D ).

tempcke
  • 700
  • 8
  • 15
Sjon
  • 4,989
  • 6
  • 28
  • 46
  • 1
    It seems to me that execution of the source code on the example .htaccess given produces ((A OR B) and C) or D) [i.e. neither what the OP hopes nor what you initially calculated]. Can you help me find my interpretation error? [Also to be more explidt, what about the reverse: if one wants to implement ((A OR B) AND (C OR D)), what exactly should one code in the .htaccess file?] – Chuck Kollars Jul 23 '15 at 19:23
  • 3
    +1 for 'demonstrating a big benefit of open-source' :) - @ChuckKollars the logic would not produce (((A or B) and C) or D) but (A or B) and (C or D). on each pass though the loop it checks CONDFLAG_ORNEXT which would only be set when looking at A and C. When it is set it either skips the next condition if the current condition passed or it continues not caring that the current condition failed because future conditions may pass and the last of the OR group will not have the flag set so if all fail the entire thing fails. – tempcke Sep 21 '15 at 20:59
  • 1
    can't get my head around how to do ( ( A and B ) OR ( C and D ) ) - I've ended up with ( A and ( B or C ) and D ) – Pete Oct 26 '17 at 13:48
  • 2
    @WillshawMedia You can do ((A and B) OR (C and D)) by splitting it into two separate rules: RewriteCond A RewriteCond B RewriteRule AB RewriteCond C RewriteCond D RewriteRule CD – Isaac Jul 19 '18 at 16:14
11

After many struggles and to achive a general, flexible and more readable solution, in my case I ended up saving the ORs results into ENV variables and doing the ANDs of those variables.

# RESULT_ONE = A OR B
RewriteRule ^ - [E=RESULT_ONE:False]
RewriteCond ...A... [OR]
RewriteCond ...B...
RewriteRule ^ - [E=RESULT_ONE:True]

# RESULT_TWO = C OR D
RewriteRule ^ - [E=RESULT_TWO:False]
RewriteCond ...C... [OR]
RewriteCond ...D...
RewriteRule ^ - [E=RESULT_TWO:True]

# if ( RESULT_ONE AND RESULT_TWO ) then ( RewriteRule ...something... )
RewriteCond %{ENV:RESULT_ONE} =True
RewriteCond %{ENV:RESULT_TWO} =True
RewriteRule ...something...

Requirements:

DrLightman
  • 575
  • 6
  • 13