I am setting a cookie using rewrite rules, and that is working (simplified for the sake of brevity):
RewriteCond %{QUERY_STRING} set_cookie=1 [NC]
RewriteRule .* http://%{HTTP_HOST}%{REQUEST_URI}?skip=1 [QSA,NE,NC,L,CO=test_%{HTTP_HOST}:tmp:%{HTTP_HOST}:5:/]
This one sets a cookie with the name test_{host_name}. Now I want to read that cookie value the next request. I tried this (and some variants), but that does not seem to work.
RewriteCond %{QUERY_STRING} skip [NC]
RewriteCond %{HTTP_COOKIE} ^.*test_%{HTTP_HOST}=tmp.*$ [NC]
RewriteRule ^(.*)$ - [L]
When I was googling, I found an article that stated the following:
If you are wondering, "Why not use %{HTTP_HOST} instead of corz.org,
create universal code?", as far as I know, it's not possible to test
one server variable against another with a RewriteCond without using
Atomic Back References and some serious POSIX 1003.2+ Jiggery-Pokery.
I guess that's my problem, but I am sort of at a loss on how to solve it. Any help is greatly appreciated.
Regards,
Joost.
There's a useful trick in this area. It is simple regex, but a unique kind of mod_rewrite style.
Note I am not too careful about the matching here especially in the first condition -- this is for illustration in the 2nd condition:
RewriteEngine ON
RewriteCond %{HTTP_COOKIE} test_([^;]*)=tmp.*$
RewriteCond %1<>%{HTTP_HOST} ^(.+)<>\1
RewriteRule .* - [F]
The novel part (for mod_rewrite) is that you can only use variables/backrefs in the first argument, but you can use backrefs (for the current expression, not the preceding one) in the 2nd parameter.
The little <> is just something unlikely to appear as a separator.
I have found a solution. This part in my original question
RewriteCond %{QUERY_STRING} skip [NC]
RewriteCond %{HTTP_COOKIE} ^.*test_%{HTTP_HOST}=tmp.*$ [NC]
RewriteRule ^(.*)$ - [L]
should be replaced by the following
RewriteCond %{QUERY_STRING} skip [NC]
RewriteCond %{HTTP_HOST}##%{HTTP_COOKIE} ^([^#]*)##.*test_\1=tmp.* [NC]
RewriteRule ^(.*)$ - [L]
Only the second RewriteCond has changed. Its left hand side (%{HTTP_HOST}##%{HTTP_COOKIE}) concatenates the http host and cookie values, using ## as glue (## doesn't really mean something, it's just unlikely to be used in a normal host or cookie string).
The right hand side (^([^#]*)##.*test_\1=tmp.*) matches everything to the first "#", which is the host name, and then checks if it can be found somewhere in the cookie values, preceded by "test_" and followed by "=tmp".
Related
I have a maybe simple Problem. I have serveral URLs that needed to be redirected in this way:
if the URL "/abc/" is called, it should show the content located under "xyz.html"
i can do that with
RewriteRule abc$ xyz.html
but "xyz.html" should be 301 redirecting to "/abc" if it is called.
This is my simple problem i am searching for an solution since hours.
it would be easy if its like "test.html" and /test/. i can do it like
RewriteRule ^(.+)\.html$ /$1 [L,R=301]
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule !.*\.html$ %{REQUEST_URI}.html [END]
But in my case i have a bunch of URLs with no pattern.
All i tried results to server misconfiguration.
Can you help me with that one sample?
thanks, kanuddel
Could you please try following.
RewriteEngine ON
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{REQUEST_URI} ^/abc/? [NC]
RewriteRule ^(.*) /xyz.html [L]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{REQUEST_URI} ^/xyz\.html/? [NC]
RewriteRule ^(.*) /abc/? [R=301,L]
Detailed Explanation:
Why there is redirection loop with normal rule writing: Question from OP is interesting and contradictory(kind of), why because lets say we write 1st rule to redirect(in backend) from abc url to xyz.html it will work fine. But then when we write our 2nd rule which is to rewrite url from xyz.html to abc/ which is exactly opposite of first rule. Hence it becomes a loop, ASAP 1st rules gets served it reaches out to 2nd rule and it serves it back to 1st rule thus a REDIRECTION INFINITE LOOP(till 50 cycles or so).
How to prevent redirection loop?
First since both rules are exactly opposite of each other so in a normal rule writing it will become a loop, now how could we prevent it to become a loop? Answer is using: RewriteCond(explained in next step)
I have used an ENVIRONMENT VARIABLE named ENV:REDIRECT_STATUS which will have any redirection status in its value. 2 things we need to understand about this variable.
1- Without any request redirection its default value is NULL.
2- Whenever we do a successful redirection its value becomes 200(NON ZERO), which we can make use of in our conditions part.
Detailed explanation of htaccess Rules:
Now coming to the condition explanation part: In RewriteRuleRewriteRule ^(.*) /xyz.html [L] I have NOT done any rewriting of url on browser why because of the THUMB RULE that we always want to show USER FRIENDLY URLs to users, so environment variable ENV:REDIRECT_STATUS will always be ZERO here.
Coming to 2nd RewriteRule now RewriteRule ^(.*) /abc/? [R=301,L] where user already using a user NON-friendly URL so first thing is I need to rewrite URL in browser to user friendly url hence R=301(redirection with permanent flag is used here). Once Redirection happens through this condition, 1st condition will start failing now why because that checks if REDIRECT_STATUS variable is NULL which is NOT after serving redirection from 2nd condition. Hence this is how it prevents loop by this small trick :)
Thanks for the great Explanation!
I tried it with a second URL, where "/xxx/" should show "zzz.html" But this gave me an Misonfiguration. I tried to shorten it like this:
RewriteEngine ON
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{REQUEST_URI} ^/abc/? [NC]
RewriteRule ^(.*) /xyz.html [L]
RewriteCond %{REQUEST_URI} ^/xxx/? [NC]
RewriteRule ^(.*) /zzz.html [L]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{REQUEST_URI} ^/xyz\.html/? [NC]
RewriteRule ^(.*) /abc/? [R=301,L]
RewriteCond %{REQUEST_URI} ^/zzz\.html/? [NC]
RewriteRule ^(.*) /xxx/? [R=301,L]
I found my server's c-panel produced code of the following form when I defined redirects using it's web gui. This works fine. However I'm concerned that I could write this much more efficiently, more generally and hopefully with less repetition.
RewriteCond %{HTTP_HOST} ^domain\.com$ [OR]
RewriteCond %{HTTP_HOST} ^www\.domain\.com$
RewriteRule ^some/unwanted/uri$ "http\:\/\/www\.domain\.com\/some/improved/uri" [R=301,L]
RewriteCond %{HTTP_HOST} ^domain\.com$ [OR]
RewriteCond %{HTTP_HOST} ^www\.domain\.com$
RewriteRule ^someother/unwanted/uri$ "http\:\/\/www\.domain\.com\/someother/improved/uri" [R=301,L]
repeat x20
I understand from Jon Lin's answer that repeating the following would usually be necessary.
RewriteCond %{HTTP_HOST} ^domain\.com$ [OR]
RewriteCond %{HTTP_HOST} ^www\.domain\.com$
However hjpotter92 has suggested shortening that to:
RewriteCond %{HTTP_HOST} ^(www\.)?domain\.com$
This is definitely an improvement but how can it be made domain general too. Could that be written as follows to apply to apply more generally to test.domain.com too?
e.g.:
RewriteCond %{HTTP_HOST} ^(*\.)?domain\.com$
I also understand from Jon Lin's answer that I could also replace the following in my code
"http\:\/\/www\.domain\.com\/some/improved/uri" [R=301,L]
with:
"/some/improved/uri" [R=301,L]
Which is gladly both shorter and more general.
Is there any need to keep repeating the following?
Yes, rewrite conditions only apply to the immediately following rule, so any following rules won't have those conditions be applied.
It's not really that inefficient to repeat the conditions, since the conditions aren't even checked unless the rule's pattern matches. That means given this rule:
RewriteCond %{HTTP_HOST} ^peterliddle\.com$ [OR]
RewriteCond %{HTTP_HOST} ^www\.peterliddle\.com$
RewriteRule ^work/cornwall/cornwall_early$ /redirect-target [L,R=301]
If the URL was /blahblah, the rewrite engine first tries to match that URL against the rule's pattern, and since it doesn't match ^work/cornwall/cornwall_early$, the whole rule is skipped, the conditions don't even need to be considered.
If it's a matter of being hard to read, you could have a rule at the very top that bypasses all rules in your htaccess file if those conditions are not met. For example:
RewriteCond %{HTTP_HOST} !^(www\.)?peterliddle\.com$ [NC]
RewriteRule ^ - [L]
RewriteRule ^work/cornwall/cornwall_early$ http://www.peterliddle.com/work/cornwall/cornwall_58-65 [R=301,L]
RewriteRule ^work/cornwall/cornwall$ http://www.peterliddle.com/work/cornwall/cornwall_66-69 [R=301,L]
RewriteRule ^work/whale_moor_a/a_first$ http://www.peterliddle.com/work/whale_moor/lakeland_hills [R=301,L]
So if the conditions for the HTTP HOST fails, the rules are completely bypassed. Problem with doing things this way is you'd have to add any new rules that you don't want the conditions to be applied above these rules.
You can also remove the http://www.peterliddle.com part from your rule's target, since you've already tested for the same domain in the HTTP_HOST.
Following kind advice on stack overflow, testing and some reading I settled on a list of rewrite rules following of the form:
RewriteCond %{HTTP_HOST} (^|\.)domain\.com$
RewriteRule ^some/unwanted/uri$ "/some/improved/uri" [R=301,L]
N.B. The following snippet does not work. It crashes the site and is not recommended.
RewriteCond %{HTTP_HOST} ^(*\.)?domain\.com$
The following seams to work across all subdomains. Therefore I can test in test.domain.com and then use the same .htaccess file in www.domain.com and have greater confidence it will work without having to make further changes to the code.
RewriteCond %{HTTP_HOST} (^|\.)domain\.com$
I'm working on a virtual domain system. I have a wildcard DNS set up as *.loc, and I'm trying to work on my .htaccess file. The following code works:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www.)?example\.loc$ [NC]
RewriteCond %{REQUEST_URI} !^/example/
RewriteRule (.*) /example/$1 [L,QSA]
But, I want this to work with anything I put in. However, I need the %{REQUEST_URI} checked against the text found as the domain. I tried using this code:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www.)?([a-zA-Z0-9-]*.)?([a-zA-Z0-9-]+)\.loc$ [NC]
RewriteCond %{REQUEST_URI} !^/%3/
RewriteRule (.*) /%3/$1 [L,QSA]
But the line RewriteCond %{REQUEST_URI} !^/%3/ causes my code to throw an Internal Server Error. I understand this is because of the %N in my code but is there a way I can work with it? I need this line, otherwise, my code fails from internal redirects.
I hope this makes sense to someone. All I need is to be able to backreference a RewriteCond in a following RewriteCond.
There's 2 things that you are doing wrong here.
First, your %{HTTP_HOST} regex is no good. You need to escape the . dots otherwise they'll be treated as "any character that's not a newline". This essentially makes the %3 backreference the last character of the hostname before the TLD (e.g. http://blah.bar.loc, %3 = r).
Second, you can't use backreferences in the regex of a RewriteCond, only the left side string, it's sort of a weird limitation. However, you can use the \1 references, in the regex so that you can construct a clever left side string to match against. Something like %3::%{REQUEST_URI} and then you can match like this: !^(.*?)::/\1/?. This regex essentially says: "match and group the first block of text before the ::, then make sure the block of text following the :: starts with /(first block)".
So your rules should look like this:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?([a-zA-Z0-9-]*\.)?([a-zA-Z0-9-]+)\.loc$ [NC]
RewriteCond %3::%{REQUEST_URI} !^(.*?)::/\1/?
RewriteRule (.*) /%3/$1 [L,QSA]
I'm trying to redirect to a "mobile" version of the site, done by the following:
RewriteCond %{HTTP_USER_AGENT} "ipad|iphone|ipod" [NC]
RewriteCond %{HTTP_COOKIE} !^.*mobile.*$ [NC]
RewriteRule ^$ /?m=t [L,R=302,co=mobile:true:.domain.com]
RewriteCond %{HTTP_USER_AGENT} "ipad|iphone|ipod" [NC]
RewriteCond %{HTTP_COOKIE} ^.*mobile=true.*$ [NC]
RewriteCond %{QUERY_STRING} !(^|&)m=t(&|$) [NC]
RewriteRule ^$ /?m=t [L,R=302,co=mobile:true:.domain.com,QSA]
Now this works for the root.
But since the site depends heavily on rewrites, if I modify
RewriteRule ^$ /?m=t [L,R=302,co=mobile:true:.domain.com,QSA]
to
RewriteRule ^(.*)$ /$1?m=t [L,R=302,co=mobile:true:.domain.com,QSA]
It'll give me the correct redirect, but without any previous rewrites.
so if I have say a rewrite previously that was
RewriteRule ^product/shimano$ /index.php?product_cat=shimano [L]
The modified line will give me /index.php?m=t&product_cat=shimano instead of /product/shimano?m=t
What am I missing? I've been trying to figure this out for a while now.
Try redirecting first, then rewriting. That is, put these mobile check rules in front of the rewrites.
That way, if it's mobile, and m=t isn't there, it will do the 302 redirect with m=t added. That will then come through again, skip these rules (since m=t is there), and continue on wiht the normal rewrites.
I'm not entirely sure about the first set of rules above. They might also need that line from the second set, that does the querystring check for m=t, to avoid an infinite loop. Basically, if it already has m=t, then it doesn't do it again.
I have a rule that translates old-style urls into new style. It works ok as long as I use the same order of parameters in the query:
RewriteCond %{QUERY_STRING} ^country=([a-z]{2})&id=([0-9]+)$ [NC]
RewriteRule ^(.*)$ http://%1.localhost/%2? [R=301,L]
So url localhost/index.php?country=us&id=1234 would go to us.localhost/1234
But the problem is that using localhost/index.php?id=1234&country=us (note that arguments are now swapped in order) then the rule of course doesn't apply.
I thought about changing the rule to handle arguments separately, like this:
RewriteCond %{QUERY_STRING} country=([a-z]{2}) [NC]
RewriteCond %{QUERY_STRING} id=([0-9]+) [NC]
RewriteRule ^(.*)$ http://%1.localhost/%2? [R=301,L]
But when entering localhost/index.php?id=1234&country=us I get 1234.localhost/us which is not what I expect (I'd expect first cond to give me %1 and second cond %2 but it seems the order isn't determined this way)
Is there any easy way to achieve this? Of course I could write two separate rules each handling each case, but was wondering if some generic approach could be used (think if we had 3 parameters then permutations would make this unmanageable)
From the documentation, it looks like you can only reference rules from the last match:
RewriteCond backreferences: These are backreferences of the form %N (0 <= N <= 9). %1 to %9 provide access to the grouped parts (again, in parentheses) of the pattern, from the last matched RewriteCond in the current set of conditions. %0 provides access to the whole string matched by that pattern.
I've been racking my brains on a good way to do this and the best I can come up with is to use multiple rules to extract the data into environment variables and then reference those at the end for your final rewrite.
Something like this:
RewriteCond %{QUERY_STRING} country=([a-z]{2}) [NC]
RewriteRule ^(.*)$ - [E=URL_COUNTRY:%1]
RewriteCond %{QUERY_STRING} id=([0-9]+) [NC]
RewriteRule ^(.*)$ - [E=URL_ID:%1]
RewriteRule ^(.*)$ http://%{ENV:URL_COUNTRY}.localhost/%{ENV:URL_ID}? [R=301,L]