How to search and replace GET parameters with mod_rewrite - apache

I try write a rewrite rule on an apache webserver configuration in the httpd.conf file, which should replace every GET parameter key which contains &foo= to &poo= but shouldn't change any other GET parameter.
For example:
https://test.com/imb/rs/search?test=abc&foo=123&test2=def&foo=456
Should be changed to:
https://test.com/imb/rs/search?test=abc&poo=123&test2=def&poo=456
I tried it with:
RewriteCond %{QUERY_STRING} ^(.*)&foo=(.*)$
RewriteRule ^(.*)$ $1?%1?&poo=%2 [NC]
But it only changed the last occurrence of foo to poo.
My httpd.conf file is structured as follows:
<Directory ~ "^/imb/rs/*">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
RewriteEngine on
RewriteCond %{QUERY_STRING} ^(.*)&foo=(.*)$
RewriteRule ^(.*)$ $1?%1&poo=%2 [NC]
</Directory>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{QUERY_STRING} ^(.*)&foo=(.*)$
RewriteRule ^(.*)$ $1?%1&poo=%2 [NC]
</IfModule>
The /imb/rs/ part is written because the url starts with https://test.com/imb/rs/ and continues after that. I thought that would match my case.
Does anyone have an idea how I could solve this?

<Directory ~ "^/imb/rs/*">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
RewriteEngine on
RewriteCond %{QUERY_STRING} ^(.*)&foo=(.*)$
RewriteRule ^(.*)$ $1?%1&poo=%2 [NC]
</Directory>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{QUERY_STRING} ^(.*)&foo=(.*)$
RewriteRule ^(.*)$ $1?%1&poo=%2 [NC]
</IfModule>
There are a few issues here. The <Directory> container is not going to match the request, so the mod_rewrite directives inside this container are not processed. Only the mod_rewrite directives outside the <Directory> container are processed, but this will only replace the last instance of &foo= in the query string (since the regex is greedy and the rewrite engine makes just a single pass in a vHost/server context).
<Directory ~ "^/imb/rs/*">
You are mixing regex and wildcard syntax. However, this should be an absolute filesystem path, not a URL-path, as this appears to be ("url starts with https://test.com/imb/rs/"). But you do not need to use the regex version of the <Directory> directive here anyway. I'm assuming /imb/rs is a physical directory and not simply a virtual URL-path?
AllowOverride All
You are explicitly enabling .htaccess overrides. If you do have a .htaccess file that contains mod_rewrite directives in this directory then this will completely override the <Directory> container in the server config. You should probably be disabling .htaccess overrides altogether.
Order allow,deny
Allow from all
These are Apache 2.2 directives and are formerly deprecated on Apache 2.4 (which I would assume you are using). You should be using the equivalent Require all granted instead. (But you do need to make sure you are using Apache 2.4 directives throughout - do not mix the two.)
Options Indexes FollowSymLinks
Aside: Are you intentionally allowing mod_autoindex to generate directory listings of your content? Generally, this should be disabled (ie. remove Indexes from this rule).
Try the following instead:
<Directory "/absolute/file/path/to/imb/rs">
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
RewriteEngine On
RewriteCond %{QUERY_STRING} (.*)&foo=(.*)
RewriteRule (.*) $1?%1&poo=%2 [L]
</Directory>
And remove the rule from outside the <Directory> container.
So, the rule you had in the beginning was basically OK. The start-of-string and end-of-string anchors are not required here, since the regex is greedy. The NC flag is not required. The L flag is not strictly required, but would be if you add any more rules later.

I found this solution which works only with 1 occurrence, not with multiple.
This one takes only the first occurrence:
RewriteCond %{QUERY_STRING} (.*?)foo(.*) [NC]
RewriteRule ^ %{REQUEST_URI}?%1poo%2 [NC]
This one takes only the last occurrence:
RewriteCond %{QUERY_STRING} (.*)foo(.*) [NC]
RewriteRule ^ %{REQUEST_URI}?%1poo%2 [NC]
But I couldn't find a way to replace all of them.

Related

How to use rewrite rules using .htaccess in an aliased directory outside document root

I am trying to use .htaccess to rewrite requests for PHP files that don't explicitly include the .php file extension (e.g. example.com/foo should rewrite to example.com/foo.php), but without applying that rule to any folders (or subfolders) of the document root.
My http.conf file contains following:
DocumentRoot "/webfiles"
<Directory "/webfiles">
Options Indexes FollowSymLinks Includes ExecCGI
AllowOverride All
Require all granted
</Directory>
Alias "/exams" "/training/exams"
<Directory "/training/exams">
Options Indexes FollowSymLinks Includes ExecCGI
AllowOverride All
Require all granted
</Directory>
DirectoryIndex index.php
There is a .htaccess file in the /training/exams folder containing following:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^([^\.]+)$ $1.php [NC,L]
The goal is to:
serve requests like example.com/exams with /training/exams/index.php file,
serve requests like example.com/exams/first with /training/exams/first.php file,
while still having requests like example.com/myhome.html served with /webfiles/myhome.html.
Examples 1. and 3. work fine, but for 2. I get a 404 error.
Interesting question.
RewriteRule ^([^\.]+)$ $1.php [NC,L]
This ultimately ends up trying to rewrite the URL to /training/exams/first.php (after the directory-prefix is added back). I think that mod_rewrite (in a directory context) is seeing this as a URL-path and example.com/training/exams/first.php doesn't exist, hence the 404.
You can either use the RewriteBase directive to override the directory-prefix with the correct root-relative URL-path instead. For example:
RewriteBase /exams
Or, hardcode the URL-path in the RewriteRule substitution. For example:
RewriteRule ^([^.]+)$ /exams/$1.php [L]
(No need for the NC flag or to escape the literal dot when used in a character class.)
Or, use the REQUEST_URI server variable to construct a root-relative URL-path:
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^ %{REQUEST_URI}.php [L]

Apache2: <Files> not applied to Rewrite substitution

Consider this .htaccess in the web root.
RewriteEngine on
RewriteBase /
RewriteRule "^pretty/(.*)" index.php?pretty=$1
Order Allow,Deny
Deny from all
<Files index.php>
Allow from all
</Files>
/pretty/sweet is correctly rewritten to /index.php?pretty=sweet (with the second half disabled).
However, I get a 403 Forbidden (with 2nd half enabled)
I assumed that URL substitution is applied first, and then <Files index.php> will match the substituted URL, allowing access.
What am I missing or misunderstanding here, and how do I fix this?
RewriteRule and Allow/Deny directives are from different Apache modules. Their loading order can be different from what you've in .htaccess.
I suggest you stick with mod_rewrite itself like this:
RewriteEngine on
RewriteBase /
RewriteRule ^pretty/(.*)$ index.php?pretty=$1 [L,QSA]
# block all files except some known files
RewriteCond %{REQUEST_URI} !(?:/|/index\.php|.+\.(?:js|css|jpe?g|png|gif))$ [NC]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [F]

Apache rewrite 1 file globally to new file URL

I have a file with an identical name that exists in 15 subdirectories. Let's say it's called blah.txt.
I'm looking to do a global rewrite within httpd.conf to match any subdirectory's blah.txt and Rewrite it to a singular http://myserver/blah.txt.
If I add the following to an .htaccess, it works great. I hit the subdirectory's txt file, and I'm 301 forwarded. However doing this means I need to manage 15 .htaccess's. I'm trying to avoid that.
RewriteEngine On
RewriteRule ^(blah)\.txt http://myserver/blah.txt [L,R=301]
If I put this same text into httpd.conf under the <Directory /> section, I get a 404 on the subdirectory's blah.txt.
<Directory />
Options All Indexes FollowSymLinks
AllowOverride All
Allow from all
RewriteEngine On
RewriteRule ^(blah)\.txt http://myserver/blah.txt [L,R=301]
</Directory>
How can I have this global match within httpd.conf?
Edit: If I change the httpd.conf rewrite to the below, it seems like it wants to work, however I get stuck in an infinite loop because it's constantly matching the same file name.
RewriteEngine On
RewriteRule blah.txt http://myserver/blah.txt [L,R=301]
Still looking for a way to have it match in subdirectories and redirect to the same filename.
Edit 2: I think I got this working - but would love confirmation!
I added a RewriteCond to my rule. Basically, I think, this means that if the URI does not contain "firmware", then rewrite.
<Directory />
Options All Indexes FollowSymLinks
AllowOverride All
Allow from all
RewriteEngine On
RewriteCond %{REQUEST_URI} !(firmware) [NC]
RewriteRule blah.txt firmware/series/version/blah.txt [R=301,L]
</Directory>
It seems to be working without looping
As per my Edit 2: I think I got this working.
I added a RewriteCond to my rule. Basically, I think, this means that if the URI does not contain "firmware", then rewrite.
<Directory />
Options All Indexes FollowSymLinks
AllowOverride All
Allow from all
RewriteEngine On
RewriteCond %{REQUEST_URI} !(firmware) [NC]
RewriteRule blah.txt firmware/series/version/blah.txt [R=301,L]
</Directory>
After quite a bit of testing, it seems to be working without looping

Deny access to all content except content from one folder

I want to allow access to only one folder of my site in a given subdomain, and have another subdomain pointing to same documentRoot with full access. I want this to avoid duplicated urls (for SEO purposes).
In the restricted virtualHost I have this configuration ...
<Directory /var/www/secundary.mysite.com/web>
Options -Includes +ExecCGI
AllowOverride All
RewriteEngine On
RewriteCond %{QUERY_STRING} !/bundles.*$ [NC]
RewriteRule ^.*$ - [F]
</Directory>
So I expect that navigation to mysite.com give a forbidden response, but www.mysite.com/bundles/js/script.js returns a normal response.
The result is that every request to secundary.mysite.com returns a normal response. Am I missing something, or ...?
I have been using a wrong variable. I wrote %{QUERY_STRING} instead %{REQUEST_URI} that was the variable that I wanted to match again the regular expression. The correct syntax for my purpose was:
<Directory /var/www/secundary.mysite.com/web>
Options -Includes +ExecCGI
AllowOverride All
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/bundles(.*)$ [NC]
RewriteRule ^.*$ - [F]
</Directory>

mod_rewrite in <Location /> in apache conf causing rewrite in .htaccess file not to work?

I am using zend framework, which has a nifty example .htaccess used for redirecting non-existing locations to index.php to be processed by the framework:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ /site/index.php [NC,L]
And here is the apache config for /site
Alias /site "/path/to/zf/project/public"
<Directory "/path/to/zf/project/public">
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
While we are upgrading the site, I want to redirect all traffic to a specific file (offline.html, for example) except for a certain IP (127.0.0.1, for example), so I am trying to use this rule in the apache config:
<Location />
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
RewriteEngine On
RewriteBase /
RewriteCond %{REMOTE_HOST} !^127\.0\.0\.1
RewriteCond %{REQUEST_URI} !/offline\.html$
RewriteRule .* /offline.html [R=302,L]
</Location>
This seems to work, but for some reason it makes my .htaccess file seem not to work. I can access /site just fine, but I can't go any deeper to, for example, /site/controller/action.
Thanks!
The Apache 2.2 and Apache 2.4 documentation of mod_rewrite clearly state that rewrite rules in <Location> directives should be avoided. This caution was not included in the Apache 2.0 documentation.
Although rewrite rules are syntactically permitted in <Location> and <Files> sections (including their regular expression counterparts), this should never be necessary and is unsupported. A likely feature to break in these contexts is relative substitutions.
So strange things can happen. You could remove the <Location> section (and RewriteBase directive) and use these new rewrite rules directly in the <VirtualHost> definition, without any <Directory> or <Location> section. It's even faster.
The only problem with global level rewrite rules is that you do not have the REQUEST_FILENAME already computed (you could hack that a little but here you do not even need REQUEST_FILENAME).
You also have one error in your RewriteRule, you use a Redirect so the rewrite Rule should use a absolute url:
RewriteRule .* http://www.example.com/offline.html [R=302,L]
About the maintenance page, a classic way of handling it is with these two lines:
ErrorDocument 503 /htdocs/err/503.html
RedirectMatch 503 ^/(?!err/)
Where you do not filter on local IP, but the interesting part is that the code used for maintenance is 503 (temporary unavailable) which is more correct (in fact a redirect 307 is even more correct but old browser could have problems with it). To do the same with a local IP restriction and a RewriteRule it would be:
ErrorDocument 503 /offline.html
RewriteCond %{REMOTE_HOST} !^127\.0\.0\.1
RewriteCond %{ENV:REDIRECT_STATUS} !=503
RewriteRule ^ - [L,R=503]
To have these rules in the htaccess file, you'll have to add/remove them by hand when you want to use "offline mode".
A better way to do this through the application is to create a controller plugin.
If the APPLICATION_ENV = 'offline', the plugin would do _forward('offline', 'error', 'default');
Alternatively, you could write the logic in a subclass of Zend_Controller_Action which you use as the base class for your controllers.