altering requests with two rewrite rules - apache

So my webserver is serving up files like file_name.php. I want to make it so requests for file-name.php gets transparently redirected to file_name.php and that requests for file_name.php get explicitly redirected via a 301 redirect to file-name.php.
ie. you request file_name.php and you get 301 redirected to file-name.php which transparently loads file_name.php instead.
Unfortunately, the .htaccess file I've written to accomplish this isn't working. Here it is:
# make it so files with slashes that don't exist transparently redirect to files with underscores
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^-]*)-([^-]*)$ $1_$2
RewriteRule ^([^-]*)-([^-]*)-([^-]*)$ $1_$2_$3
# make it so files with underscores that do exist explicitely redirect to files with slashes
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^([^_]*)_([^_]*)$ /$1-$2 [L,R=301]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3 [L,R=301]
On their own they work but together it results in an infinite loop.
Any ideas?

This was really an interesting problem.
Code that I'm suggesting is a generic recursion based code that will translate each _ by - in URL externally (no matter how many underscore are there). While internally it will do the reverse translation and load the actual URL.
# Only single underscore do an external 301 redirect
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s/+([^_]+)_([^_\s]*) [NC]
RewriteRule ^ /%1-%2 [R=301,L]
# Recursively translate each _ to - in URL and do external 302 redirect
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s/+([^_]+)_([^\s]*) [NC]
RewriteRule ^ /%1-%2 [R,L]
# Recursively translate - to _ to load actual URL internally
RewriteRule ^([^-]+)-(.*)$ /$1_$2 [L]

Because the URI gets rewritten then plugged back into the rewrite engine, you'd get a redirect loop. You have to externally redirect by matching against the request and not the URI. Also, rewrite conditions only apply to the immediately following rewrite rule, so you need to duplicate them for each of your rules:
# make it so files with slashes that don't exist transparently redirect to files with underscores
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^-]*)-([^-]*)$ $1_$2 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^-]*)-([^-]*)-([^-]*)$ $1_$2_$3 [L]
# make it so files with underscores that do exist explicitely redirect to files with slashes
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /([^_]*)_([^_]*)_([^_\ \?]*)
RewriteRule ^ /%1-%2-%3 [L,R=301]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /([^_]*)_([^_\ \?]*)
RewriteRule ^ /%1-%2 [L,R=301]

Related

Both URL With PHP and Non-PHP Access after RewriteRule In Htaccess

Access Both URL With PHP and Non-PHP in PHP project after applying Htacces Rules
RewriteRule ^c/([a-zA-Z0-9-/]+)$ category.php?id=$1 [L]
RewriteRule ^p/([a-zA-Z0-9-/]+)$ detail.php?post=$1 [L]
Here I access both URLs like www.example.com/c/category-name and www.example.com/category.php?id=12 but I want only www.example.com/c/category-name URL. I don't Want Duplicate URLs both this page.
With your shown samples, attempts please try following htaccess rules. Make sure to clear your browser cache before testing your URLs.
RewriteEngine ON
##Internal rewrite rules.
RewriteCond %{HTTP_HOST} ^(?:www\.)?example.com$ [NC]
RewriteRule ^c/([\w-]+)/?$ category.php?id=$1 [QSA,NC,L]
##External redirect rules.
RewriteCond %{HTTP_HOST} ^(?:www\.)?example.com$ [NC]
RewriteCond %{THE_REQUEST} \s/category\.php?id=(\S+)\s [NC]
RewriteRule ^ /c/%1? [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^[^/]*/(.*)/?$ category.php?id=$1 [QSA,NC,L]
Unless you have changed an existing URL structure and category.php and/or detail.php have been indexed by search engines then you could simply force a 404 when either of these URLs are accessed directly.
For example, the following should go before your existing rewrites:
# Block direct access to "category.php" or "detail.php"
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(category|detail)\.php$ - [R=404]
The check against the REDIRECT_STATUS env var ensures that we are only checking direct requests and not rewritten requests by the later rewrite.
Otherwise, if these "old" URLs have previously been indexed by search engines or linked to by third parties then you should redirect to the "new" (canonical) URLs instead. For example:
# Redirect "category.php" or "detail.php" to canonical URL
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{QUERY_STRING} ^(?:id|post)=([a-zA-Z0-9/-]+)$
RewriteRule ^(?:(c)ategory\.php|detail\.(p)hp)$ /$1/%1 [R=301,L]
I've moved the hyphen to the end of the character class (ie. from [a-zA-Z0-9-/] to [a-zA-Z0-9/-]) to avoid a potential ambiguity since hyphens are naturally special characters inside a character class.
The $1 backreference contains either c or p, depending on the request, to form the first path segment. %1 is the value captured from the URL-parameter. Importantly, this is the same regex you are using the later rewrite to match the value.
NB: Test first with a 302 (temporary) redirect to avoid potential caching issues.

.htaccess applies only to root, while I need it to work across the domain name

I have the following ReWrite Rule
RewriteCond %{REQUEST_URI} !/maintenance.php$ [NC]
RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif) [NC]
RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif) [NC]
RewriteCond %{REMOTE_ADDR} !{developer_ip}
RewriteRule .* /maintenance.php [R=302,L]
This .htaccess should redirect all requests to the maintenance.php, except the requests coming from {developer_ip} which is my own ip address.
Problem:
The above rules work, but when I click on any of the internal links, it again shows me maintenance.php (which due to the IP rule, must not happen) which ends in either a index.php?{some_query} or a URL which is also already rewritten by the .htaccess itself, such as /Page/About-US (which originally is index.php?page_id=200.
Now I want the .htaccess to redirect all requests to maintenace.php (which already is doing) but no the requests coming from {developer_ip}. The above rules are fine, except the part excluding my own ip address, which redirects me for the internal links.
Perform an internal rewrite to maintenance page and keep your rules in this order:
RewriteEngine On
RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif)$ [NC]
RewriteCond %{REMOTE_ADDR} !{developer_ip}
RewriteRule !^maintenance\.php$ /maintenance.php [NC,L]
# skip any requests alredy rewritten to maintenance.php
RewriteRule ^maintenance\.php$ - [NC,L]
# rest of your rewrite rules

.htaccess redirect to WWW with current htaccess code?

I want to redirect my URLs to the www version. I can do this, but I already have working .htaccess code that is redirecting the browser to my index.php file where the URL is processed. I have this code (which I did not write), and I do not know enough about htaccess to figure the problem out:
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$ /index.php [NC]
I have tried simply using the full URL path in the redirect, but this produces a 404 error when I attempt to access pages. I have also attempted to simply include more rules underneath this code, with no success.
Do you mean you want http://example.com/foo/bar to redirect to http://www.example.com/foo/bar?
If so, this should do the trick, while preserving your intent
RewriteEngine On
# First redirect non-www hosts (the "L" flag means we won't process
# any more rewrite conditions in this redirect; on the next request,
# the rewrite condition won't match and we'll fall through to your
# original rule)
RewriteCond %{HTTP_HOST} !^www\.(.*) [NC]
RewriteRule ^(.*)$ http://www.yourdomain.com/$1 [R=301,NC,L]
# Handle normal requests
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule ^(.*)$ /index.php [NC]:

Apache ModRewrite Working in vhost but not .htaccess

I have the following mod rewrites:
RewriteEngine On
# rest api rewrites
RewriteCond %{REQUEST_URI} /api/v [NC]
RewriteRule ^(.*)$ /index.php/$1 [L,QSA]
# main application rewrite
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
RewriteRule ^/[a-zA-Z0-9\-\_/]+?$ /index.html [L,QSA]
When I put these into my vhost config, everything works as excepted however when I put this into my .htaccess file, the first rewrite does an infinite loop (the log show an INTERNAL REWRITE) and the second rewrite doesn't work at all.
Why would these rewrites would in the vhost but not my .htaccess file?
The first rule loops because your target matches the regex, so once it rewrites the first time and the rules loop, the ^(.*)$ matches the URI that you've just rewritten to (/index.php/etc...`), so it causes an infinite loop (or loops as many times as the internal redirect limit is configured to). You need to add a condition to prevent the looping:
# rest api rewrites
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteCond %{REQUEST_URI} /api/v [NC]
RewriteRule ^(.*)$ /index.php/$1 [L,QSA]
The second rule doesn't get applied at all because the URI being put through rules in an htaccess file has the leading slash stripped off (because htaccess is essentially like a <Directory> in vhost/server config), so you need to at least make that leading slash optional:
# main application rewrite
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
RewriteRule ^/?[a-zA-Z0-9\-\_/]+?$ /index.html [L,QSA]

Apache Rewrite Rules - Path Style

I am coming from the IIS world to Apache and would appreciate some help on the rewrite rules.
I want this relative path:
/index.php?go=order&id=12345
to be rewritten as:
/go/order/id/12345
Also, if there are more parameters, they should be converted to path format:
/index.php?go=order&id=12345&action=process
becomes
/go/order/id/12345/action/process
How do I achieve this please? Thanks for any input.
Try putting this in your vhost config:
RewriteEngine On
# Start converting query parameters to path
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\?[^\ ]+
RewriteCond %{QUERY_STRING} ^([^=]+)=([^&]+)&?(.*)$
RewriteRule ^(.*)$ $1/%1/%2?%3 [L]
# done converting, remove index.php and redirect browser
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\?[^\ ]+
RewriteCond %{QUERY_STRING} ^$
RewriteRule ^/index.php/(.*)$ /$1 [R=301,L]
# internally rewrite paths to query strings
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteRule ^/([^/]+)/([^/]+)/?(.*) /$3?$1=$2 [L,QSA]
# No more path, rewrite to index.php
RewriteRule ^/$ /index.php [L]
These rules will make it so if you type in a URL like http://yourdomain.com/index.php?a=b&1=2&z=x in your browser, the browser will get redirected to http://yourdomain.com/a/b/1/2/z/x. When the clean looking URL gets requested, the 2nd set of rules internally rewrites it back to /index.php?a=b&1=2&z=x. If you want to put these rules in an htaccess file (in your document root), you need to remove all the leading slashes in the RewriteRule's. So ^/ needs to be ^ in the last 3 rules.
Note that if you simply go to http://yourdomain.com/index.php, without a query string, nothing gets rewritten.