I did this in two stages. The first is a set of rules that extracts one parameter, leaving the others as-is. Then I expanded this to two parameters. The rules themselves aren't that bad; it was just tricky figuring it out.
One Parameter
This uses the a
parameter for the example. There are 4 conditions:
- a only
- a-other
- other-a
- other-a-other
Because of the question mark vs. ampersand, it's simplest to do separate rules. It turned out that the last two were easy to combine into one rule.
(Note: I'm using Helicon Ape rewrite, which is generally compatible with Apache. I had an issue where the RewriteRule question mark needs to be escaped before parameters, e.g., \?%2
. I don't know if this true in general.)
# a
# ?a=foo
# Starts with a=, non-ampersand to the end.
# Suppress querystring with trailing question mark.
RewriteCond ${QUERY_STRING} ^a=([^&]+)$
RewriteRule ^/path/$ /path/%1/? [NC,R=301,L]
# a-other
# ?a=foo&b=bar, ?a=foo&b=bar&c=1
# Starts with a=, non-ampersand, ampersand, remaining required.
# Escape question mark so it doesn't include entire original querystring.
RewriteCond ${QUERY_STRING} ^a=([^&]+)&(.+)
RewriteRule ^/path/$ /path/%1/\?%2 [NC,R=301,L]
# other-a or other-a-other
# ?b=baz&a=qux, ?b=baz&c=1&a=qux
# ?c=1&a=foo&d=2&b=bar&e=3, ?z=4&c=1&a=foo&d=2&b=bar&e=3
# Starts with anything, ampersand, a=, non-ampersand, remaining optional.
# The remaining optional lets it follow with nothing, or with ampersand and more parameters.
# Escape question mark so it doesn't include entire original querystring.
RewriteCond ${QUERY_STRING} ^(.+)&a=([^&]+)(.*)$
RewriteRule ^/path/$ /path/$2/\?%1%3 [NC,R=301,L]
Two Parameters
To put these together for both parameters is a little tricky, but the idea is to rewrite a
, then fall through to rewrite b
and redirect. To manage the two sections together, rewrite path
to temppath
then temppath2
, then rewrite it back to path
when done. This ensures that these only run when both a
and b
are present. If only one or the other is present, then it skips all this. (If you meant to also handle only one, this can be adjusted.)
# Test cases:
# 1) /path/?a=foo&b=bar to /path/foo/bar
# 2) /path/?a=foo&b=bar&c=1 to /path/foo/bar?c=1
# 3) /path/?a=foo&b=bar&c=1&d=2 to /path/foo/bar?c=1&d=2
# 4) /path/?b=baz&a=qux to /path/qux/baz
# 5) /path/?b=baz&c=1&a=qux to /path/qux/baz/?c=1
# 6) /path/?c=1&b=baz&a=qux to /path/qux/baz/?c=1
# 7) /path/?c=1&d=2&b=baz&a=qux to path/qux/baz/?c=1&d=2
# 8) /path/?c=1&a=foo&d=2&b=bar&e=3 to /path/foo/bar/?c=1&d=2&e=3
# 9) /path/?z=4&c=1&a=foo&d=2&b=bar&e=3 to /path/foo/bar/?z=4&c=1&d=2&e=3
# Check for a and b (or b and a), rewrite to temp path and continue.
RewriteCond ${QUERY_STRING} (?:^|&)(?:a|b)=.+&(?:b|a)=.+$
RewriteRule ^/path/$ /temppath/ [NC]
# a
# ?a=foo
# This case isn't needed, since we test for a and b above.
# a-other
# 1) /temppath/?a=foo&b=bar to /temppath2/foo/?b=bar
# 2) /temppath/?a=foo&b=bar&c=1 to /temppath2/foo/?b=bar&c=1
# 3) /temppath/?a=foo&b=bar&c=1&d=2 to /temppath2/foo/?b=bar&c=1&d=2
# Starts with a=, non-ampersand, ampersand, remaining required.
RewriteCond ${QUERY_STRING} ^a=([^&]+)&(.+)$
RewriteRule ^/temppath/$ /temppath2/%1/\?%2 [NC]
# other-a or other-a-other
# 4) /temppath/?b=baz&a=qux to /temppath2/qux/?b=baz
# 5) /temppath/?b=baz&c=1&a=qux to /temppath2/qux/?b=baz&c=1
# 6) /temppath/?c=1&b=baz&a=qux to /temppath2/qux/?c=1&b=baz
# 7) /temppath/?c=1&d=2&b=baz&a=qux to /temppath2/qux/?c=1&d=2&b=baz
# 8) /temppath/?c=1&a=foo&d=2&b=bar&e=3 to /temppath2/foo/?c=1&d=2&b=bar&e=3
# 9) /temppath/?z=4&c=1&a=foo&d=2&b=bar&e=3 to /temppath2/foo/?z=4&c=1&d=2&b=bar&e=3
# Starts with anything, ampersand, a=, non-ampersand, remaining optional.
# The remaining optional lets it follow with nothing, or with ampersand and more parameters.
# Escape question mark so it doesn't include entire original querystring.
RewriteCond ${QUERY_STRING} ^(.+)&a=([^&]+)(.*)$
RewriteRule ^/temppath/$ /temppath2/%2/\?%1%3 [NC]
# b
# 1) /temppath2/foo/?b=bar to /path/foo/bar
# 4) /temppath2/qux/?b=baz to /path/qux/baz
# Starts with b=, non-ampersand to the end.
# Capture and use path after temppath2, since it has the a folder from above.
RewriteCond ${QUERY_STRING} ^b=([^&]+)$
RewriteRule ^/temppath2/(.*)/$ /path/$1/%1/? [NC,R=301,L]
# b-other
# 2) /temppath2/foo/?b=bar&c=1 to /path/foo/bar?c=1
# 3) /temppath2/foo/?b=bar&c=1&d=2 to /path/foo/bar?c=1&d=2
# 5) /temppath2/qux/?b=baz&c=1 to /path/qux/baz/?c=1
# Starts with b=, non-ampersand, ampersand, remaining required.
# Capture and use path after temppath2, since it has the a folder from above.
# Escape question mark so it doesn't include entire original querystring.
RewriteCond ${QUERY_STRING} ^b=([^&]+)&(.+)$
RewriteRule ^/temppath2/(.*)/$ /path/$1/%1/\?%2 [NC,R=301,L]
# other-b or other-b-other
# 6) /temppath2/qux/?c=1&b=baz to /path/qux/baz/?c=1
# 7) /temppath2/qux/?c=1&d=2&b=baz to /path/qux/baz/?c=1&d=2
# 8) /temppath2/foo/?c=1&d=2&b=bar&e=3 to /path/foo/bar/?c=1&d=2&e=3
# 9) /temppath2/foo/?z=4&c=1&d=2&b=bar&e=3 to /path/foo/bar/?z=4&c=1&d=2&e=3
# Starts with anything, ampersand, b=, non-ampersand, remaining optional.
# The remaining optional lets it follow with nothing, or with ampersand and more parameters.
# Capture and use path after temppath2, since it has the a folder from above.
# Escape question mark so it doesn't include entire original querystring.
RewriteCond ${QUERY_STRING} ^(.+)&b=([^&]+)(.*)$
RewriteRule ^/temppath2/(.*)/$ /path/$1/%2/\?%1%3 [NC,R=301,L]
It is probably easier in code....