Why is this RewriteRule altering QUERY_STRING, but leaving REQUEST_URI untouched?
Asked Answered
V

3

8

I have a copy of Concrete5, a PHP-based CMS, running on example.com.

Concrete5 comes with the following basic instructions for pretty URLs (redirecting all URLs to a central index.php)

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} !^/c5.7
RewriteRule ^.*$ c5.7/$0 [L]    # Concrete5 is running in the c5.7/ subdirectory
</IfModule>

Pretty straightforward.

Now I have a certain set of URLs that take the form

 /product/{productname}

that I need to forward to the Concrete5 (virtual) URL

/products/details?name={productname}

That URL is set up and works as expected when I enter it manually in the browser.

So I added a line to the htaccess file and it now looks like this:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /

# New rule for products
RewriteCond %{REQUEST_URI} ^/product/
RewriteRule ^product/(.+)$ /products/details?name=$1 [QSA]

RewriteCond %{REQUEST_URI} !^/c5.7
RewriteRule ^.*$ c5.7/$0 [L]

</IfModule>

I can confirm the RewriteRule gets triggered when I choose a random, external URL as the redirection target.

But whenever it is an internal redirect like above, what happens is, I get a 404 inside Concrete5. When I inspect what was passed to it, I see:

 REQUEST_URI:  /product/my-random-product
 QUERY_STRING: name=my-random-product

So it appears that the rule is triggered and does some rewriting, but REQUEST_URI remains unchanged!

Why?

Is it because PHP 7.1 is running via CGI?

I have tried a zillion variations and all the flags in the book, with little success.

Vanvanadate answered 24/5, 2017 at 13:48 Comment(2)
"redirecting all URLs to a central index.php" - The top .htaccess snippet you posted doesn't actually do this. All this bit does is rewrite all requests to the /c5.7 subdirectory. There is probably another .htaccess file in this subdirectory that then rewrites the URL to index.php.Cogitate
Sorry guys, didn't manage to test the solutions in time to award the bounty. If one of them works out for me, I'll start another and award it manually.Vanvanadate
M
3

The REQUEST_URI in PHP is not the same as the REQUEST_URI within mod_rewrite, so you can't do it like this. In PHP it always contains the original URL. So you can't change it like this if your CMS is working off that.

You should set up your CMS to use the URLs you want, rather than trying to augment your CMS's URL rewriting like this.

If you inspect REDIRECT_URL in PHP you will see the last rewritten URI.

Mouldy answered 7/6, 2017 at 4:7 Comment(0)
R
2

REQUEST_URI in PHP will always be the original request URI. Because this is already explained by LSerni and SuperDuperApps, I won't elaborate. Instead, I'm offering a quick solution: modify the REQUEST_URI and add a name parameter in PHP instead of in .htaccess.

Add the following code to the start of your Concrete5 index.php to make sure that REQUEST_URI is modified before any Concrete5 code runs:

if(preg_match('-^/product/([^?]*)-',$_SERVER['REQUEST_URI'],$matches)){
    $_SERVER['REQUEST_URI'] = '/products/details';
    $_GET['name'] = $matches[1];
}
Randolphrandom answered 8/6, 2017 at 23:5 Comment(1)
Clever idea - but I can't seem to override REQUEST_URI in my (shared) server configuration. It's possible they have Suhosin or some other security patch installed. Too bad!Vanvanadate
M
1

Your setup works on a PHP 7.1 machine (without Concrete5). It does call a script I just put in, which is in /c5.7/products/details. So the Apache part is working.

Inside the script, I see that REQUEST_URI is the old value prior to the rewrite.

So its value is normal and it not being rewritten is a red herring - it isn't supposed to be rewritten. The 404 error must be due to something else.

Your Concrete5 routing should support the real URL, not just the virtual one, because C5's routing relies itself on REQUEST_URI. If this is so, you need to create a route for your short URLs

Route::register('/product/{productname}' ...)

and an appropriate controller to get the parameters and invoke the "old" controller.

One possibility using .htaccess could be this, but I'm not too sure it will work since REQUEST_URI is still left unchanged:

# New rule for products
RewriteCond %{REQUEST_URI} ^/product/
RewriteRule ^product/(.+)$ c5.7/products/details?name=$1 [L,QSA]

Otherwise you need to do an external redirect, which will disclose the URL in the browser:

RewriteRule product/(.*)$ http://.../products/details?name=$1 [QSA]

See also this other question.

Milamilady answered 2/6, 2017 at 22:49 Comment(1)
Thank you - going to test this soon.Vanvanadate

© 2022 - 2024 — McMap. All rights reserved.