Forum Moderators: phranque

Message Too Old, No Replies

htaccess, rewrite only when a cookie does not exist not working

         

csdude55

6:50 pm on Oct 4, 2021 (gmt 0)

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



A few years ago a client wanted me to redirect their domain to another website (not on my server). They kept the account with me for their email and I still have their old site uploaded, I simply redirected everything via .htaccess:

RewriteRule ^(.*)$ https://www.example.com [L,R=301]


Now they want to go back to the way it was. They want to review the original site and handle any updates before making it live, so I need to find a way to make the site rewrite UNLESS there's some condition met (in which case nothing is rewritten at all).

My idea is that they would follow a very specific link that I give them (original.com/preapprove.php?foo=whatever), which would then set a cookie and redirect them back to the site. Then htaccess would look for that cookie and value, and if they exist then it wouldn't redirect them.

preapprove.php:

if ($_GET['foo'])
setcookie('bar', 'something_random');

// I added time() in an attempt to bypass cache
header('Location: https://original.com/?h=' . time());
exit;


.htaccess:

RewriteCond %{REQUEST_URI} !^/preapprove\.php
RewriteCond %{HTTP_COOKIE} !bar=something_random
RewriteRule ^(.*)$ https://www.example.com [L,R=301]


The cookie is definitely being written in PHP, but I'm still being rewritten in htaccess!

I changed header(...) in preapprove.php to print_r($_COOKIE) to make sure everything was good there, and it is. So it doesn't rewrite when %{REQUEST_URI} is preapprove.php, and the cookie is being set. It only appears to be happening when I redirect back.

Any suggestions on how to correct that?

lucy24

10:49 pm on Oct 4, 2021 (gmt 0)

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



RewriteRule ^(.*)$ https://www.example.com [L,R=301]
Surely, surely you meant to say either
RewriteRule (.*) https://www.example.com/$1 [L,R=301]
or
RewriteRule ^ https://www.example.com [L,R=301]
depending on whether the new site is or isn’t a page-for-page match.

Now then!

It isn’t clear what order all this is happening in. If the cookie is set in preapprove.php, then htaccess won’t know about it until the next time that same page is requested.

Is that pair of RewriteCond meant to be AND-delimited? That is, redirect only if the requested URL is not blahblah and also the cookie does not have the specified value? It kinda seems like it should be OR: redirect unless the request is either for this specific page, OR the cookie is already present. (But then, paired negative conditions tend to confuse me horribly and you may already be doing exactly that.)

Does your client operate from such a wide range of IPs that you can’t simply make a REMOTE_ADDR condition instead?

csdude55

3:41 am on Oct 5, 2021 (gmt 0)

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



Surely, surely you meant to say... RewriteRule ^ https://www.example.com [L,R=301]

In this case it wasn't going to be a page-for-page match, they wanted to be redirected to the other site's homepage regardless of the link. The whole thing went against my recommendations, but, ya know, bureaucracy :-/

It isn’t clear what order all this is happening in. If the cookie is set in preapprove.php, then htaccess won’t know about it until the next time that same page is requested.

Well.

The idea is that if they go to original.com or any secondary page then they'll be redirected to example.com... unless that secondary page is preapprove.php OR unless they have that cookie set.

So the user should be able to go to original.com/preapprove.php and get a cookie. Then preapprove.php uses header() to redirect them back to the homepage, so (in theory) it would be a whole new HTTP request. Then, since the cookie is there, RewriteCond %{HTTP_COOKIE} !bar=something_random should not match.

But it's matching anyway, and that's what's throwing me off.

Is that pair of RewriteCond meant to be AND-delimited? That is, redirect only if the requested URL is not blahblah and also the cookie does not have the specified value? It kinda seems like it should be OR: redirect unless the request is either for this specific page, OR the cookie is already present. (But then, paired negative conditions tend to confuse me horribly and you may already be doing exactly that.)

I know, right! It confuses me, too, so I tried it both ways and neither worked.

Does your client operate from such a wide range of IPs that you can’t simply make a REMOTE_ADDR condition instead?

It's actually the other way around. Think, corporation with a smaller department, and they all have the same REMOTE_ADDR. The department had its own website, then the new CEO wanted to simply have the domain point to the corporation's homepage. Now there's a new CEO and she greenlit having the original site back.

What I need to do is keep it so that the CEO and rest of the corporation sees the redirect, while working with the department head to bring it all up to date.

csdude55

4:30 am on Oct 5, 2021 (gmt 0)

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



Update:

I found that preapprove.php redirects as expected, and it's rewritten to:

example.com/?h=123456

(where "123456" is really the result for time())

Then if I manually change the link to this, it will work:

original.com/?h=123457 (incrementing the value)

So thinking that the issue is with the header() redirect, I changed preapprove.php to use Javascript for the redirect, with a 3 second delay:

if ($_GET['foo'])
setcookie('bar', 'something_random');

$nocache = time();

echo <<<EOF
<html>
<head>
<title>Please wait...</title>
</head>

<body onLoad = "setTimeout(`window.location.href = 'https://www.original.com/?h=$nocache'`, 3000)">
Please wait...
</body>
</html>

EOF;


But STILL! I'm redirected to example.com! I get the "Please wait" text so I know it's working to that point, but when it redirects I'm immediately rewritten.

I tried several variations; location.replace(), setting the cookie in Javascript (with and without an expiration and path), everything I could think of.

phranque

8:38 am on Oct 5, 2021 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



what other mod_rewrite directives are in the .htaccess file or any server config files?

dstiles

10:25 am on Oct 5, 2021 (gmt 0)

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



I have a couple or so spare domain names I use for testing sites. Change the domain name in the apache site conf for the duration. Assuming there are no absolute links within the site it should work ok, although you may have to obtain a cert for it (letsencrypt free).

csdude55

5:16 pm on Oct 5, 2021 (gmt 0)

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



what other mod_rewrite directives are in the .htaccess file or any server config files?

There's nothing else in the .htaccess. I didn't think to look at the server config files, but I THINK they should be OK. They were in all of the htaccess files originally, anyway.

I tried to copy them to the post but I'm getting a Forbidden error, and I have no idea what's causing the problem. These are the only ones that I think would match anyway, though:

# Compress
<IfModule mod_deflate.c>
AddOutPutFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/x-javascript text/xml application/xml application/xml+rss application/vnd.ms-fontobject application/x-font-ttf

<IfModule mod_setenvif.c>
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

SetEnvIfNoCase Request_URI .(?:gif|jpe?g|png)$ no-gzip dont-vary
</IfModule>

<IfModule mod_headers.c>
Header append Vary User-Agent env=!dont-vary
</IfModule>
</IfModule>

# Force SSL
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

lucy24

6:09 pm on Oct 5, 2021 (gmt 0)

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



There's nothing else in the .htaccess. I didn't think to look at the server config files, but I THINK they should be OK.
And it shouldn’t make a difference, because unless you set RewriteOptions to Inherit (or one of its many 2.4 variants), as soon as you hit htaccess, all previous RewriteRules should be as if they never existed.

I tried to copy them to the post but I'm getting a Forbidden error,
Yup, I’ve had that happen. I think the site code scans the post for malign content, but has no way of excluding stuff that’s inside [code] tags.

I realized this morning that I have something conceptually identical on one of my sites: If a person requests one of a short list of files from a smartphone AND the referer is not one specific page AND a certain cookie is not present, they are redirected to a special page saying, in effect, “Are you sure you want to do this?” This page sets a cookie and has links to several other pages, including the four that trigger the redirect. For comparison purposes, the code goes
RewriteCond %{HTTP_USER_AGENT} (Android|iPhone|iPod)
RewriteCond %{HTTP_COOKIE} !phonegame
RewriteCond %{HTTP_REFERER} !phone\.html
RewriteRule ^games/(tower|palace|canal|color)/?$ https://www.example.com/games/phone.html [R=302,CO=phonegame:1:.example.com:525600,L]
(In my case, the referer acts as a kind of double markedness: If the user for some reason doesn’t do cookies, they can still get to all pages, they just have to detour via phone.html.)

I just tested this on my Android to confirm that it works as intended--that is, that it doesn't redirect when the relevant cookies are met. From this I learned that I never got around to adding the "initialscale" blahblah header on this site, so it was a useful test in any case :)

Oh! Looking at this, I see that it intentionally says R=302 (equivalent to plain [R]). If it had the usual R=301, the browser would remember the redirect response and would follow it without putting in a new request to the server.

csdude55

6:39 pm on Oct 7, 2021 (gmt 0)

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



@lucy24, I think you inadvertently solved my problem :-) The [CO] hint is what I should have been using all along!

I dropped the preapprove.php script altogether, and instead changed the .htaccess to:

# if (foo === 'random_code')
RewriteCond %{QUERY_STRING} foo=random_code
RewriteRule ^ https://www.original.com/?h=%{TIME} [CO=foo:random_code:.original.com,L,R=302]

# else
RewriteCond %{HTTP_COOKIE} !foo=random_code
RewriteCond %{REQUEST_URI} !^/(images|styles\.css|javascript\.js)
RewriteRule ^ https://www.example.com [L,R=302]


So far that seems to be working perfectly :-)


Long term, it would probably be better to save the random code as an ENV variable instead of coding it 3 times. Not tested, but as a guide for future readers:

# if (foo === 'random_code')
RewriteCond %{QUERY_STRING} foo=random_code
RewriteRule ^ https://www.original.com/?h=%{TIME} [E=foo:%1,CO=foo:%1:.original.com,L,R=302]

# else
RewriteCond %{HTTP_COOKIE} !foo=%{ENV:foo}
RewriteCond %{REQUEST_URI} !^/(images|styles\.css|javascript\.js)
RewriteRule ^ https://www.example.com [L,R=302]