Forum Moderators: phranque

Message Too Old, No Replies

If / Else with multiple conditions

         

csdude55

7:11 am on Jan 19, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I'm running Apache 2.4.54, and using this as my guide for an IF ELSE section:
[httpd.apache.org...]

I need for the <If> to match when two conditions are true, so I have this:

# an exact copy and paste, except that I changed the regex matches for brevity
<FilesMatch "\.(php|cgi|lib)$">
...

<If "%{ENV:linker} =~ /^lorem(?:ipsum)?$/ && %{REQUEST_URI} !~ /^\/(?:foo|bar)[\/$]/i">
<If "%{QUERY_STRING} =~ /(?:^|&)default=([A-Za-z]+)/">
RewriteRule ^ - [E=default:${lc:%1}]
</If>

<Else>
RewriteRule ^/([a-z]+) - [E=default:${lc:$1}]
</Else>

RewriteRule ^/([a-z]+) - '[E=foo:bar]'
</If>
...
</FilesMatch>

But this throws an error on the first <If> line:
<If> directive missing closing '>'

Based on this, though, my format appears to be right:
[serverfault.com...]

Any suggestions on where I'm messing up?

lucy24

5:36 pm on Jan 19, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Have a closer look at
[\/$]

I suspect you are not actually matching a literal $ sign here, so instead it needs to be
(\/|$)

This may or may not affect the reported error, but try it anyway.

csdude55

7:35 am on Jan 20, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



That seemed to do it! I also changed the regex to m#...# so that I didn't have to escape the /, so that might have been the solution, too. But either way, it worked :-)

I don't understand WHY it worked... but it's all really just witchcraft, anyway.

phranque

8:08 am on Jan 20, 2023 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



I also changed the regex to m#...#

that was going to be my next suggestion.

lucy24

5:39 pm on Jan 20, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



The original error message
<If> directive missing closing '>'

made me suspect that the > which is supposed to be the end of the <If> was instead being read--presumably along with the preceding /i" string--as part of the pattern-to-match. But I couldn't figure out why it wasn't recognizing “this is the end of the pattern” so instead I focused on the bit that did jump out at me ;)

csdude55

6:41 pm on Jan 20, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



This whole things has become frustrating :-/ I've discovered a few things:

1. I'm not sure how Apache handles the order, but when I set an ENV prior to the IF-ELSE it doesn't appear to be read. Or maybe I'm reading it wrong. More below.

2. When I run a regex in the IF, the %1 isn't being carried into the results.

Example:

# www.example.com sets %{ENV:linker} = example
RewriteCond %{HTTP_HOST} ^(?:www\.)([^.]+)\. [NC]
RewriteRule ^ - [E=linker:${lc:%1}]

# expecting %{ENV:tester} = ex
<If "%{ENV:linker} =~ /(ex)ample/i">
RewriteRule ^ - [E=tester:%1]
</If>

I print all of the $_SERVER variables in PHP for testing. I can see that $_SERVER['linker'] is properly set to "example", but there's no $_SERVER['tester'];

Then I tried this modification:

RewriteCond %{HTTP_HOST} ^(?:www\.)([^.]+)\. [NC]
RewriteRule ^ - [E=linker:${lc:%1}]

<If "%{HTTP_HOST} =~ m#^(?:www\.)(ex)ample\.#i">
RewriteRule ^ - [E=tester:%1]
</If>

This time $_SERVER['tester'] is set so the IF is true, but tester is empty so %1 has no value.

I tried every variation of reqenv() and env() that I could think of from the docs, but nothing worked like I thought.

Any suggestions?

csdude55

8:29 pm on Jan 24, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Well, I've discovered that the problem here is with the order in which Apache processes IF statements. So this kinda works by forcing "foo" to be processed first:

RewriteRule ^ - [E=foo:bar]

<FilesMatch "\.php$">
<If "-n %{ENV:foo}">
RewriteRule ^ - [E=lorem:ipsum]
</If>

RewriteRule ^ - [E=this:that]
</FilesMatch>


This will print $_SERVER['foo'] and $_SERVER['lorem']...

... but not $_SERVER['this'] :-/

I can't figure out why it just dies after the IF statement, but it's starting to look like the IF-ELSE just isn't a practical solution for anything beyond very simple configs.

lucy24

10:43 pm on Jan 24, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Tee hee. Apache says somewhere that using mod_rewrite inside <Files> envelopes will work--probably--but it isn't supported and you do so at your own risk. (In fact I did it for several years after working out the parts the docs don't tell you, on account of the Not Supported business, but eventually decided it wasn't worth the bother.)

You must have said at some point, but I forget: are your environmental variables getting set by mod_env (which executes after mod_rewrite) or by mod_setenvif (which executes before mod_rewrite)?

csdude55

1:30 am on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I'm not sure if the answer is mod_env or... neither. I'm setting them using RewriteRule ^ - [E=foo:bar], so not explicitly invoking anything. But I'm guessing that this implicitly uses mod_env.

Based on your post, though, I took it out of <FilesMatch>:

RewriteRule ^ - [E=foo:bar]

<If "-n %{ENV:foo}">
RewriteRule ^ - [E=lorem:ipsum]
</If>

RewriteRule ^ - [E=this:that]


This sets foo and this, but not lorem.

Then I tried using SetEnvIfExpr, in case that changed up the order of operations:

SetEnvIfExpr "%{HTTP_HOST} =~ m#^(?:www\.)?(\w+)\.#i" foo=bar

<If "-n %{ENV:foo}">
RewriteRule ^ - [E=lorem:ipsum]
</If>

RewriteRule ^ - [E=this:that]


Same thing... I get foo and this, but not lorem.

Of course, I recognize that in this simple example I don't REALLY need the <If> condition. But my production code would have a lot more in the <If>, so if it's not going to work then I'm going to end up with dozens of RewriteCond preceding each rule :-/

Apache says somewhere that using mod_rewrite inside <Files> envelopes will work--probably--but it isn't supported and you do so at your own risk.

Hmph. Well, that kinda sucks. My actual process here is to restrict it to variables.php and variables.lib because I don't need to set environment variables for every HTTP request! If that's not stable then it feels like a lot of wasted resources for nothing :-/ :-/

lucy24

2:57 am on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I think the main issue with mod_rewrite inside <Files> is that it executes at a different time, after the Files(Match) has been evaluated--that is, it isn’t like an <IfModule> envelope where the presence of the envelope has no effect whatsoever on anything, except the raw fact of determining whether something is done at all. It's most noticeable in htaccess, because physical filepaths become involved. So for example if you're aiming for something in /rats/ then instead of the htaccess-normal
^rats/

you have to leave off the anchor ... and then take further steps to ensure you're not hitting /paintings/rats/ along the way. (This is the actual issue I ran into, all those years ago.)

If you're setting an environmental variable via a RewriteRule flag, I don't think it invokes mod_env at all; it's done directly in mod_rewrite. Apache says, cold-bloodedly,
This technique is offered as an example, not as a recommendation.


And, further (this is what I originally meant to look up, but got sidetracked),
<If>, <ElseIf>, and <Else> are applied last.
except that, in the fine tradition of Samuel Johnson's garret and cockloft,
Certain variables, such as CONTENT_TYPE and other response headers, are set after <If> conditions have already been evaluated

Thanks, Apache. That is all clear as mud.

csdude55

5:51 am on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Gotcha. In my case all I'm doing is setting a series of environment variables in the <FilesMatch>, so hopefully that's OK? I haven't seen any issues yet, anyway, but it's good to know that it could break with the next Apache update.

Certain variables, such as CONTENT_TYPE and other response headers, are set after <If> conditions have already been evaluated

Am I right in guessing that environment variables are set as a response header?

I discovered that this doesn't work; it sets foo and this but not lorem:

RewriteRule ^ - [E=foo:bar]

SetEnvIfExpr "-n %{ENV:foo}" lorem=ipsum

RewriteRule ^ - [E=this:that]


But this DOES set foo, lorem, blah, and this!

SetEnvIf Request_URI "\.php$" foo=bar

SetEnvIfExpr "-n %{ENV:foo}" lorem=ipsum blah=blerg

RewriteRule ^ - [E=this:that]

And so does this:

SetEnvIf Request_URI "\.php$" foo=bar

SetEnvIf foo [a-z] lorem=ipsum blah=blerg

RewriteRule ^ - [E=this:that]

So I might need to revisit the whole concept and use SetEnvIfExpr instead of <FilesMatch>.

Notice that I was able to apply multiple variables by delimiting with a space:

SetEnvIf foo lorem=ipsum blah=blerg

I don't suppose there's a flag or anything that would let me separate them with a line break (for readability)?

[edited by: csdude55 at 6:15 am (utc) on Jan 25, 2023]

lucy24

6:07 am on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



it sets foo and this but not lorem
well, that makes sense, since mod_setenvif executes before mod_rewrite. So at the time of the SetEnvIfExpr directive, foo has not yet been set. Remember that each module is an island; it makes no difference whether a RewriteRule is physically located above or below a SetEnvIf.

In short: variables set in mod_setenvif will be seen by mod_rewrite, but not the other way around ... unless the directives that need to execute later are inside a <Files> envelope. Which, I guess, is the argument in favor of <Files>.

I was able to apply multiple variables by delimiting with a space
Yup, that's in the docs:
https://httpd.apache.org/docs/2.4/mod/mod_setenvif.html#setenvif
Any extra stuff after the “attribute” and “regex” elements will turn into as many environmental variables as you want. I was going to say that I've never personally tried it--but, come to think of it, I do it all the time when UNsetting environmental variables, as in
BrowserMatch Googlebot !noaccept !nolang !en_only !lying_bot
Thanks to Apache's ultra-minimalist grammar, where spaces have syntactic meaning of one kind, and line breaks have syntactic meaning of a different kind, you're pretty well stuck with long lines.

csdude55

6:36 am on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



In short: variables set in mod_setenvif will be seen by mod_rewrite, but not the other way around ... unless the directives that need to execute later are inside a <Files> envelope. Which, I guess, is the argument in favor of <Files>.

Hang on, I'm struggling with what you mean.

Are you saying that this should work?

RewriteRule ^ - [E=foo:bar]

<FilesMatch "\.php$">
SetEnvIf foo [a-z] blah=blerg

<If "-n %{ENV:foo}">
RewriteRule ^ - [E=lorem:ipsum]
</If>
</FilesMatch>

I get foo, but that's all.

If I'm misunderstanding, then which direction would work?

lucy24

5:53 pm on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



It's been a while, but I think you have to say
RewriteEngine on
over again inside any Files envelope, the same way you do in anything directory-specific.

Seriously, though, I think you're better off working with default behaviors rather than twist yourself into a pretzel trying to override the defaults (like making A come after B in the alphabet). It begins to seem analogous to those CSS declarations that are littered with !important because the writer is trying to go against what would naturally happen.

csdude55

7:09 pm on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



It's been a while, but I think you have to say
RewriteEngine on
over again inside any Files envelope, the same way you do in anything directory-specific.


Well, unfortunately when I did this I ended up with no environment variables at all!
RewriteRule ^ - [E=foo:bar]

<FilesMatch "\.php$">
RewriteEngine on

SetEnvIf foo [a-z] blah=blerg

<If "-n %{ENV:foo}">
RewriteRule ^ - [E=lorem:ipsum]
</If>
</FilesMatch>

RewriteRule ^ - [E=this:that]


I know there has to be a magic trick to make this work, but I'm really spending way too much time on it. I guess I'll just rebuild the whole dang thing, and maybe revisit this after a few more versions of Apache.

lucy24

8:33 pm on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Oh! And you did check your rewrite inherit options ... right? Apache doesn't have the decency to tell us what the current default is. (It changed between 2.2 and 2.4 thanks to the expanded range of inherit alternatives, and I honestly don't understand why it says you can only specify one thing, when some of them have nothing to do with each other.)

:: looking vaguely around for phranque, who tends to be able to find this stuff ::

csdude55

9:05 pm on Jan 25, 2023 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



That one I dunno... I did this to see what modules are installed, that's as far as I know:

# httpd -M
Loaded Modules:
core_module (static)
so_module (static)
http_module (static)
mpm_event_module (shared)
systemd_module (shared)
cgid_module (shared)
access_compat_module (shared)
actions_module (shared)
alias_module (shared)
auth_basic_module (shared)
authn_core_module (shared)
authn_file_module (shared)
authz_core_module (shared)
authz_groupfile_module (shared)
authz_host_module (shared)
authz_user_module (shared)
autoindex_module (shared)
deflate_module (shared)
dir_module (shared)
expires_module (shared)
filter_module (shared)
headers_module (shared)
include_module (shared)
log_config_module (shared)
logio_module (shared)
mime_module (shared)
negotiation_module (shared)
proxy_module (shared)
proxy_fcgi_module (shared)
proxy_http_module (shared)
proxy_wstunnel_module (shared)
rewrite_module (shared)
setenvif_module (shared)
slotmem_shm_module (shared)
socache_dbm_module (shared)
socache_shmcb_module (shared)
speling_module (shared)
status_module (shared)
unique_id_module (shared)
unixd_module (shared)
userdir_module (shared)
version_module (shared)
bwlimited_module (shared)
ssl_module (shared)
fcgid_module (shared)
http2_module (shared)
pagespeed_module (shared)
security2_module (shared)