Grab more than 2 parameters using RewriteCond - apache

I have a URL with 4 query parameters:
https://address.com/bin/servlet?firstKey=firstValue&secondKey=secondValue&thirdKey=thirdValue
I would to map this URL to path variables:
https://address.com/content/firstValue/secondValue/thirdValue
This is what I'm trying to do:
RewriteCond %{QUERY_STRING} &?firstKey=([^&]+)&?secondKey=([^&]+) [NC]
Then rewriting rule:
RewriteRule ^/bin/servlet /content/%1/%2 [QSD,PT]
This is working fine.
But the pain is: Third parameter, when I'm adding 3rd key like other 2 parameters it won't work.
RewriteCond %{QUERY_STRING} &?firstKey=([^&]+)&?secondKey=([^&]+)&?thirdKey=([^&]+) [NC]
RewriteRule ^/bin/servlet /content/%1/%2/%3 [QSD,PT]

There are a number of issues with your implementation, for example the usage of "&" in the condition. But in general there is no limit to the number of parameters you can detect and handle with your chose strategy.
I made a few adjustments, this probably is what you are looking for:
RewriteEngine on
RewriteCond %{QUERY_STRING} ^firstKey=([^&]+)&secondKey=([^&]+)&thirdKey=([^&]+)$
RewriteRule ^/?bin/servlet$ /content/%1/%2/%3 [R=301,QSD,L]
RewriteRule ^/?content/([^/]+)/([^/]+)/([^/]+)/?$ /bin/servlet?firstKey=$1&secondKey=$2&thirdKey=$3 [L,QSA]
This obviously only handles requests using exactly three parameters. It is possible to generalize such approach to be able to handle a dynamic number of parameters, but I would advise against it: often it is better to keep things less complex, so just add corresponding rules for 2 and 4 parameters, if required. That way you will be able to maintain and extend the solution easily.
UPDATE:
In your comment below you ask if it is possible to capture argument values specified in arbitrary order. Sure, everything is possible, but things get a bit more complex then. Take a look at this example:
RewriteEngine on
RewriteCond %{QUERY_STRING} (?:^|&)firstKey=([^&]+)(?:&|$)
RewriteRule ^/?bin/servlet$ - [E=FIRST_VAL:%1]
RewriteCond %{QUERY_STRING} (?:^|&)secondKey=([^&]+)(?:&|$)
RewriteRule ^/?bin/servlet$ - [E=SECOND_VAL:%1]
RewriteCond %{QUERY_STRING} (?:^|&)thirdKey=([^&]+)(?:&|$)
RewriteRule ^/?bin/servlet$ - [E=THIRD_VAL:%1]
RewriteRule ^/?bin/servlet$ /content/%{ENV:FIRST_VAL}/%{ENV:SECOND_VAL}/%{ENV:THIRD_VAL} [R=301,QSD,L]
RewriteRule ^/?content/([^/]+)/([^/]+)/([^/]+)/?$ /bin/servlet?firstKey=$1&secondKey=$2&thirdKey=$3 [L,QSA]

Related

Modify query string value using htacces

I want to redirect
https://www.example.com/signup?plan=basic to https://www.example.com/signup?plan=basic-monthly and https://www.example.com/signup?plan=pro to https://www.example.com/signup?plan=pro-monthly .
How can I achieve this using htaccess ?
There are many questions related to this here. But, couldn't find an answer for this specific scenario.
This is the code I tried and failed:
RewriteCond %{QUERY_STRING} (^|&)plan=pro(&|$)
RewriteRule ^signup /$0?plan=pro-monthly [R=301,L]
Also, while trying the same with "basic" instead of "pro", the word "basic" i shown in red color as if it is a keyword.
Could you please try following, written with shown samples. Please make sure you clear your browser cache before testing your URLs.
RewriteEngine ON
RewriteCond %{QUERY_STRING} ^(plan=(?:basic|pro))$ [NC]
RewriteRule ^(signup)/?$ $1?%1-monthly [L]
2nd solution: Or you could try following too. Make sure you either put 1st solution rules OR this one at a time.
RewriteEngine ON
RewriteCond %{THE_REQUEST} \s/(signup)\?(plan=(?:basic|pro))\s [NC]
RewriteRule ^ %1?%2-monthly [L]

Escape string within RewriteRule

I am currently chewing on this problem: I am reworking a sort of CMS system, the result will be that URLs will look entire differently, but I want to save links and bookmarks, so I scrathced this .htaccess file together:
RewriteEngine on
# Rewrite old links to match with new URL syntax:
# universal rewrite
RewriteCond %{REQUEST_URI}~%{QUERY_STRING} ^(.*?\/*)? (viewuser|viewstory|reviews|news)\.php~(.+)$ [NC]
RewriteRule ^(.+).php$ %1?action=redirect&source=$1&%3 [R=301,L,NE]
And it works, http://example.com/news.php?action=newsstory&nid=51 actually becomes http://example.com/?action=redirect&source=news&action=newsstory&nid=51
But you can see the problem, a double action. Using 'action' is not the most inventive term, but it is what the script I need to feed is working with, and also what I am getting from the old one, so I need to either:
replace the second 'action' with anything else
or serialize/escape the entire part which is defined by %3 from, the RewriteCond
In case you are wondering, the question mark required the use of a RewriteCond, couldn't get it to work any other way, so any solution that gets rid of it is just as welcome.
Well, thanks for looking into and maybe even shedding a light onto things, I just made my way into the whole regex thing, but havewn't entirely figured how variables are passed from chained RewriteRule to another, maybe that would have been the way to got, but try and error didn't get me anywhere.
Try removing the NE flag and use a B:
RewriteCond %{REQUEST_URI}~%{QUERY_STRING} ^(.*?\/*)?(viewuser|viewstory|reviews|news)\.php~(.+)$ [NC]
RewriteRule ^(.+).php$ /?action=redirect&source=$1&%3 [R=301,L,B]
The only problem is that the %1 backreference will get encoded as well so I left that out. If you must have it there, you can try doing a two step rewrite:
RewriteCond %{REQUEST_URI}~%{QUERY_STRING} ^(.*?\/*)?(viewuser|viewstory|reviews|news)\.php~(.+)$ [NC]
RewriteRule ^(.+).php$ /special-rewrite-here?action=redirect&source=$1&%3 [L,B]
RewriteCond %{THE_REQUEST} ^[A-Z]+\ (.*?\/*)?(viewuser|viewstory|reviews|news)\.php~(.+)$ [NC]
RewriteRule ^special-rewrite-here$ /%1 [L,R=301]
Fixed with help from Jon Lin:
RewriteCond %{REQUEST_URI}~%{QUERY_STRING} ^(.*?\/*)? (viewuser|viewstory|reviews|news)\.php~(.+)$ [NC]
RewriteRule ^(.+).php$ /special-rewrite-here?action=redirect&source=$1&old_data=%3 [B]
RewriteCond %{REQUEST_URI}~%{QUERY_STRING} ^(.*?\/*)? (viewuser|viewstory|reviews|news)\.php~(.+)$ [NC]
RewriteRule ^/.+$ %1 [L,R=301]
I had to do the RewriteCond again to preserve %1, and had to modify the second RewriteRule slightly, but he sure got me on the right track, so: problem solved, the rest will take place in PHP, that should be no problem then.

How do I remove parts of a query string with RewriteCond and RewriteRule?

Background
I want to replace several (3) query string parameters in urls. We're moving to a new search engine and there are tons of links that we would like to still work. Examples:
http://example.com/search?q=s&restrictBy[foo]=fltr1&restrictBy[baz]=fltr2 ->
http://example.com/search?q=s&newfoo=fltr1&bazinga=fltr2
http://example.com/search?q=s&restrictBy[bar]=fltr3 ->
http://example.com/search?q=s&barista=fltr3
http://example.com/search?q=s&restrictBy[bar]=fltr1&restrictBy[baz]=fltr2&restrictBy[foo]=fltr3 ->
http://example.com/search?q=s&barista=fltr1&bazinga=fltr2&newfoo=fltr3
Problem
The first parameter to RewriteRule does not look at the query string, so I cannot replace a single parameter while keeping the rest of the url intact. Additionally, the fact that NONE, SOME, or ALL of the parameters may exist, and IN ANY ORDER is throwing me for a loop.
# Does not work :/
RewriteRule (.*)restrictBy\[foo\]=(.*) $1newfoo=$2
# Kinda works, but loses rest of params
RewriteCond %{QUERY_STRING} restrictBy\[foo\]=([^&]*)
RewriteRule (.*) $1?newfoo=%1 [L]
# Kinda works, but doesn't remove old params
RewriteCond %{QUERY_STRING} restrictBy\[foo\]=([^&]*)
RewriteRule (.*) $1?newfoo=%1 [QSA,L]
Question
How can I replace ANY OR ALL 3 params without losing data and without additional data?
Remove QSA from rule to overwrite existing query string:
RewriteCond ::%{QUERY_STRING} ::(?:|.*&)restrictBy\[foo\]=([^&]*)
RewriteCond %1::%{QUERY_STRING} (.*?)::(?:|.*&)restrictBy\[baz\]=([^&]*)
RewriteCond %1&%2::%{QUERY_STRING} ([^&]*)&(.*?)::(?:|.*&)q=([^&]*)
RewriteRule ^(.*)$ $1?q=%3&newfoo=%1&bazinga=%2 [L,R]

RewriteCond to match query string parameters in any order

I have a URL which may contain three parameters:
?category=computers
&subcategory=laptops
&product=dell-inspiron-15
I need 301 redirect this URL to its friendly version:
http://store.example.com/computers/laptops/dell-inspiron-15/
I have this but cannot make it to work if the query string parameters are in any other order:
RewriteCond %{QUERY_STRING} ^category=(\w+)&subcategory=(\w+)&product=(\w+) [NC]
RewriteRule ^index\.php$ http://store.example.com/%1/%2/%3/? [R,L]
You can achieve this with multiple steps, by detecting one parameter and then forwarding to the next step and then redirecting to the final destination
RewriteEngine On
RewriteCond %{QUERY_STRING} ^category=([^&]+) [NC,OR]
RewriteCond %{QUERY_STRING} &category=([^&]+) [NC]
RewriteRule ^index\.php$ $0/%1
RewriteCond %{QUERY_STRING} ^subcategory=([^&]+) [NC,OR]
RewriteCond %{QUERY_STRING} &subcategory=([^&]+) [NC]
RewriteRule ^index\.php/[^/]+$ $0/%1
RewriteCond %{QUERY_STRING} ^product=([^&]+) [NC,OR]
RewriteCond %{QUERY_STRING} &product=([^&]+) [NC]
RewriteRule ^index\.php/([^/]+/[^/]+)$ http://store.example.com/$1/%1/? [R,L]
To avoid the OR and double condition, you can use
RewriteCond %{QUERY_STRING} (?:^|&)category=([^&]+) [NC]
as #TrueBlue suggested.
Another approach is to prefix the TestString QUERY_STRING with an ampersand &, and check always
RewriteCond &%{QUERY_STRING} &category=([^&]+) [NC]
This technique (prefixing the TestString) can also be used to carry forward already found parameters to the next RewriteCond. This lets us simplify the three rules to just one
RewriteCond &%{QUERY_STRING} &category=([^&]+) [NC]
RewriteCond %1!&%{QUERY_STRING} (.+)!.*&subcategory=([^&]+) [NC]
RewriteCond %1/%2!&%{QUERY_STRING} (.+)!.*&product=([^&]+) [NC]
RewriteRule ^index\.php$ http://store.example.com/%1/%2/? [R,L]
The ! is only used to separate the already found and reordered parameters from the QUERY_STRING.
I take a slightly different approach for this sort of thing, leveraging ENV VARs set and read by mod_rewrite. I find it more readable / maintainable to refer to the backreferences by name like this, and these ENV VARs can be reused later in request processing too. Overall I think it's a more powerful and flexible approach than the accepted answer here. In any case, it works well for me. I've copied my gist below in its entirety:
From https://gist.github.com/cweekly/5ee064ddd551e1997d4c
# Mod_rewrite is great at manipulating HTTP requests.
# Using it to set and read temp env vars is a helpful technique.
#
# This example walks through fixing a query string:
# Extract good query params, discard unwanted ones, reorder good ones, append one new one.
#
# Before: /before?badparam=here&baz=w00t&foo=1&bar=good&mood=bad
# After: /after?foo=1&bar=good&baz=w00t&mood=happy
#
# Storing parts of the request (or anything you want to insert into it) in ENV VARs is convenient.
# Note the special RewriteRule target of "-" which means "no redirect; simply apply side effects"
# This lets you manipulate the request at will over multiple steps.
#
# In a RewriteRule, set custom temp ENV VARs via [E=NAME:value]
# Note it's also possible to set multiple env vars
# like [E=VAR_ONE:hi,E=VAR_TWO:bye]
#
# You can read these values using %{ENV:VAR_NAME}e <- little "e" is not a typo
#
# Tangent:
# Note you can also read these env vars the same way, if you set them via SetEnvIf[NoCase]
# (It won't work to use SetEnv, which runs too early for mod_rewrite to pair with it.)
#
# Regex details:
# (?:) syntax means "match but don't store group in %1 backreference"
# so (?:^|&) is simply the ^ beginning or an & delimiter
# (the only 2 possibilities for the start of a qs param)
# ([^&]+) means 1 or more chars that are not an & delimiter
RewriteCond %{QUERY_STRING} (?:^|&)foo=([^&]+)
RewriteRule ^/before - [E=FOO_VAL:%1]
RewriteCond %{QUERY_STRING} (?:^|&)bar=([^&]+)
RewriteRule ^/before - [E=BAR_VAL:%1]
RewriteCond %{QUERY_STRING} (?:^|&)baz=([^&]+)
RewriteRule ^/before - [E=BAZ_VAL:%1]
RewriteRule ^/before /after?foo=%{FOO_VAL}e&bar=%{BAR_VAL}e&baz=%{BAZ_VAL}e&mood=happy [R=301,L]
P.S. This is not a copy/pasteable solution to your question, but rather shows exactly how to handle this kind of problem. Armed w this understanding, leveraging it for your example will be completely trivial. :)
1) In case You just need to check that all parameters are in url:
RewriteCond %{QUERY_STRING} (^|&)category\=computers($|&)
RewriteCond %{QUERY_STRING} (^|&)subcategory\=laptops($|&)
RewriteCond %{QUERY_STRING} (^|&)product\=dell\-inspiron\-15($|&)
RewriteRule ^$ http://store.example.com/computers/laptops/dell-inspiron-15/? [R=301,L]
2) In case You need exact set of parameters:
RewriteCond %{QUERY_STRING} ^&*(?:category\=computers|subcategory\=laptops|product\=dell\-inspiron\-15)(?!.*&\1(?:&|$))(?:&+(category\=computers|subcategory\=laptops|product\=dell\-inspiron\-15)(?!.*&\1(?:&|$))){2}&*$
RewriteRule ^$ http://store.example.com/computers/laptops/dell-inspiron-15/? [R=301,L]
This rule is generated by 301 redirect generator

Htaccess redirect depending on the value of variable

I have some difficulties with such an easy problem.
On our site we've got a paginator, wich works with GET-variable "p" (p=1 - is the first page, p=2 - second and so on).
I do not want the script to consider the value 1 of this variable (p=1). So I've got the query string like:
http://www.mysite.ru/mypage/some_page2?p=1
and want to redirect user via htaccess to page:
http://www.mysite.ru/mypage/some_page2
It would be nice, if htaccess rule handle not only value "1", but also "","bla-bla-bla" like
http://www.mysite.ru/mypage/some_page2?p=$#^&*_not_right_value
http://www.mysite.ru/mypage/some_page2?p=
Thank you for your attention.
UPD:
The working solution for my case:
RewriteCond %{QUERY_STRING} ^p=1$
RewriteRule ^(.*)$ /$1? [R,L]
Query string manipulation via .htaccess can generally only be done via mod_rewrite. I haven't tested it, but the following should be close to what you need.
RewriteEngine on
RewriteCond %{QUERY_STRING} p=1 [OR]
RewriteCond %{QUERY_STRING} p=[^0-9]
RewriteRule (.*) http://www.mysite.ru/$1 [R, L]
The second condition simply tests that the p= is followed by a number. Once tested, the rule can be expanded to test that only a number exists after p (ie, right now p=2garbage would work) but it is better to start off with just the basics.
The working solution for my case:
RewriteCond %{QUERY_STRING} ^p=1$
RewriteRule ^(.*)$ /$1? [R,L]