2

I have a RewriteCond that checks if {QUERY_STRING} contains the right version number, if it doesn't then redirect users to the correct version.

For instance if v0.7 is the latest, users accessing http://localhost/?v=0.5 should be redirected to http://localhost/?v=0.7 but for some reason, if am using RewriteMap in the conditions, it doesn't work...

This works

RewriteMap versions txt:/var/www/html/version.txt
RewriteCond "%{QUERY_STRING}" !^v=0.7
RewriteRule "^/$" "/?v=${versions:version}" [R,L]

This doesn't

RewriteMap versions txt:/var/www/html/version.txt
RewriteCond "%{QUERY_STRING}" !^v=${versions:version}
RewriteRule "^/$" "/?v=${versions:version}" [R,L]

Content of version.txt

##
##  version.txt -- rewriting map
##  The version number written here will be mapped to the URL
##
##
version 0.7
James Wong
  • 165
  • 8

1 Answers1

2
RewriteCond "%{QUERY_STRING}" !^v=${versions:version}

This doesn't work because the CondPattern (2nd argument to the RewriteCond directive) is a regex and consequently does not support variable expansion. (Just as you can't use backreferences of the form $n or %n or server variables %(SERVER_VAR} or env vars %{ENV:MY_ENV_VAR} etc.) It would otherwise conflict with the PCRE regex syntax. Only the TestString (first argument to the RewriteCond directive) and the RewriteRule substitution arguments support variable expansion, since these arguments are "regular" strings, not regex.

However, you can still do what you require and check that the correct version number, as returned from the RewriteMap, is present at the start of the query string. Instead, expand the result of the RewriteMap in the TestString (which does support variable expansion since this is an "ordinary" string, not a regex) and compare this using an internal backreference.

For example, change the RewriteCond to read:

RewriteCond %{QUERY_STRING}@${versions:version} !^v=([\d.]+)(?:&[^@]*)?@\1

After the variable expansion, we end up matching a string of the form v=0.5@0.7 against the regex ^v=([\d.]+)(?:&.*)?@\1. \1 is an internal backreference to the first captured subpattern (ie. the value of the v URL parameter). So, this effectively matches the URL parameter value against the value returned from the RewriteMap. This whole expression is then negated (! prefix) so it's successful when the condition does not match, ie. the version number is different.

([\d.]+) - Matches the version number in the query string, which consists of 1 or more digits or literal dots. eg. 0.5

(?:&.*)? - Matches the remainder of the query string (if any).

@ is just an arbitrary string that does not otherwise occur in the query string or "version" string.

You don't need to surround the arguments in double-quotes unless they contain spaces (even then you can backslash escape the spaces instead). So, the double quotes are entirely optional in this example. The same applies to the RewriteRule directive.

MrWhite
  • 11,643
  • 4
  • 25
  • 40
  • 2
    The perfect answer! Really appreciate the detailed explanation and also the provided solution works perfectly. Thank you @MrWhite! – James Wong Sep 30 '20 at 02:38