change file name with mod_rewrite? - apache

If you have a file /foo/index.html that you want to access via /bar/index.html that's easy enough with a rewrite:
RewriteRule ^bar/(.*)$ foo/$1 [L] # <-- Leaves URL the same, but gives content of foo/
But that means both /foo/index.html and /bar/index.html now work to access the content. Is there a way to now disallow access via /foo/index.html? Just doing:
RewriteRule ^foo/(.*)$ bar/$1 [R=301,L] # <-- Change the URL if accessed via /foo
RewriteRule ^bar/(.*)$ foo/$1 [L] # <-- Leaves URL the same, but gives content of foo/
causes a redirect loop. Is there a way to indicate "redirect foo to bar, unless the URL is already '/bar'"?

Yeah, but you need to do a check against something like %{THE_REQUEST} otherwise your two rules are going to keep changing each other's rewrites and loop. So something like:
# this is the rule you already had:
RewriteRule ^bar/(.*)$ foo/$1 [L]
# this is the rule that externally redirects
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /foo/
RewriteRule ^foo/(.*)$ /bar/$1 [L,R=301]
The check against the %{THE_REQUEST} variable ensures that the actual request was made for /foo, and not an internally rewritten request.

Related

Dynamically pointing multiple domains to a single domain's subfolder with .htaccess

In this example, I manage domain.com. Inside it, I have a store template in php that loads the selected store dinamically:
domain.com/store1
domain.com/store2
domain.com/store3
The nice urls are being generated by the .htaccess below. What's really being loaded in the back, is this:
domain.com/store/index.php?store=store1
domain.com/store/index.php?store=store2
domain.com/store/index.php?store=store3
Each store has internal links, such as these:
domain.com/store1/catalogue
domain.com/store1/catalogue/instruments
domain.com/store1/catalogue/instruments/guitars
domain.com/store1/electric-guitar/1337
It works exactly as expected. These are the .htaccess rules to make that work in domain.com. The store query string (store1, store2, store3) in each RewriteRule determines which store is being loaded:
# permalinks
RewriteEngine on
# errors
ErrorDocument 404 /error.php?error=404
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]
#################################################
# store
RewriteRule ^([0-9a-zA-Z-]{2,50})/?$ store/index.php?store=$1 [QSA,L]
# store: catalogue
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/?$ store/catalogue.php?store=$1 [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=$1&cat=$2 [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=$1&cat=$2&subcat=$3 [QSA,L]
# store: products
RewriteRule ^([0-9a-zA-Z-]{2,50})/([0-9a-zA-Z-]{1,150})/([0-9]+)$ store/product.php?store=$1&slug=$2&id_product=$3 [QSA,L]
Now, here comes the tricky part. Some of the clients, would like to use their own domains, so that store1.com/* loads the same content of domain.com/store1/* (without using a redirect).
Example:
store1.com => domain.com/store1/
store1.com/catalogue => domain.com/store1/catalogue
store1.com/catalogue/instruments => domain.com/store1/catalogue/instruments
store1.com/catalogue/instruments/guitars => domain.com/store1/catalogue/instruments/guitars
store1.com/electric-guitar/1337 => domain.com/store1/electric-guitar/1337
You get the idea.
All the domains (domain.com, store1.com, store2.com, store3.com) are configured in the same Apache Web Server environment (using virtual hosts).
The question is: Can this be implemented in a .htaccess file in each one of the store's domain root path dynamically or inside the virtualhost .conf file? If so, how?
Make sure all your custom "store" domains (eg. store1.com, store2.com, etc.) are defined as ServerAlias in the domain.com vHost and so resolve to the same place as domain.com.
You can then rewrite requests for the custom store domain to prefix the URL-path with the store-id (the domain name) and then use your existing rules unaltered.
For example, a request for store1.com/catalogue/instruments is internally rewritten to /store1/catalogue/instruments and then processed by the existing rules as usual.
The following directives should go before the existing # store rules:
# Internally rewrite the request when a custom domain is used
# - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 !^/([^/]+)/.*#\1
RewriteRule (.*) %1/$1
And that's basically it, although you may also decide to implement a canonical redirect - see below.
Explanation of the above directives:
The first condition simply excludes requests for the main domain.com (which should already have the relevant store-id prefixed to the URL-path).
%{REQUEST_URI} !\.\w{2,4}$ - The second condition avoids rewriting requests for static resources (images, JS, CSS, etc.). Specifically, it excludes any request that ends in - what looks like - a file extension.
%{HTTP_HOST} ^([^.]+) - The third condition captures the requested domain name before the TLD which is then accessible using the %1 backreference later and used to prefix the URL-path. This assumes there is no www subdomain (as stated in comments). eg. Given a request for store1.com or store2.co.uk, store1 or store2 respectively are captured.
%{REQUEST_URI}#%1 !^/([^/]+)/.*#\1 - The fourth condition checks that the URL-path is not already prefixed with the domain name (captured above). This is primarily to ensure that rewritten requests are not rewritten again, causing a rewrite loop. This is achieved using an internal backreference (\1) that compares the first path-segment in the URL-path against the previously captured domain name (%1).
(.*) %1/$1 - Finally, the request is internally rewritten, prefixing the domain name to the requested URL-path.
Ignore existing files (may not be required)
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]
You've not stated how you are referencing your static resources (images, JS, CSS, etc.), but you may need to modify this rule to also match requests for existing files. (Although the condition I added to the rule above may already be sufficient to exclude these requests.) For example:
# ignore existing directories or files
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
Canonical redirect (optional)
You should also consider redirecting any direct requests of the form store1.com/store1/catalogue/instruments back to the canonical URL store1.com/catalogue/instruments - should these URLs ever be exposed/discovered. A request of the form store1.com/store2/catalogue/instruments will naturally result in a 404, so is not an issue.
For example, the following would go immediately after the ErrorDocument directive in your existing rules:
# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 ^/([^/]+)/.*#\1
RewriteRule ^(?:[^/]+)(/.*) $1 [R=301,L]
Test first with a 302 (temporary) redirect to avoid potential caching issues.
This is basically the reverse of the above rewrite, but applies to direct requests only. The check against the REDIRECT_STATUS env var ensures that only direct requests from the client and not rewritten requests by the later rewrite are processed.
Summary
With the two rule blocks in place...
# permalinks
RewriteEngine on
# errors
ErrorDocument 404 /error.php?error=404
# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 ^/([^/]+)/.*#\1
RewriteRule ^(?:[^/]+)(/.*) $1 [R=301,L]
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Internally rewrite the request when a custom domain is used
# - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 !^/([^/]+)/.*#\1
RewriteRule (.*) %1/$1
#################################################
# store
:
: existing directives follow
:

Using a .htaccess to RewriteRule and Redirect 301 at the same time?

I have a couple of specific URLs that I want to display differently on my website. For example I want "/contact.php" to become "/contact". So I added this to my .htaccess:
RewriteRule ^contact$ contact.php
And to avoid having 2 different URLS pointing to the same page, I also want to do a 301 redirect between the old URL and the new one:
Redirect 301 /contact.php http://www.example.com/contact
Each of the line above works well separately. But if I add them both in my htaccess, I have a redirect loop. How can I fix that?
In the end, if I either type "/contact" or "/contact.php", I want to see the contact page with the url "/contact".
Edit: I also tried things like that, and it doesn't work:
RewriteRule ^/contact\.php$ http://www.example.com/contact [R=301,L]
RewriteRule ^/contact$ /contact.php [L]
Yes it will indeed cause redirection loop since mod_rewrite rules are applied in a loop. Here value of REQUEST_URI changes to contact.php after first rule and to contact by your second rule.
To avoid this looping you need to use %{THE_REQUEST} in your external redirect rule as THE_REQUEST variable represents original request received by Apache from your browser and it doesn't get overwritten after execution of some other rewrite rules. Example value of this variable is GET /index.php?id=123 HTTP/1.1.
Use this:
RewriteEngine On
# external redirect from /contact.php to /contact
RewriteCond %{THE_REQUEST} \s/+(contact)\.php\[\s?] [NC]
RewriteRule ^ /%1? [R=302,L]
# internal forward from /contact to /contact.php
RewriteRule ^(contact)/?$ $1.php [L,NC]
Change 302 to 301 once you make sure it is working fine for you.

Mod_rewrite in two directions?

An existing page is called /foo/bar.php. What I have done is a rewrite so that when a user types /foobar, it load the contents of /foo/bar.php (while keeping /foobar in the url bar)
But I also want the opposite - when a user clicks on a link or types /foo/bar.php, I want to have /foobar in the url. The reason is to avoid manually changing all the links.
How could I do that (if possible without an http redirect, but via some rewrite magic)? And is it possible for those two rules to co-exist?
Edit - After the first response, I realized my description of the problem was not proper. /foobar is not supposed to be a concatenation of foo, bar of /foo/bar.php, but an arbitrary string (/whatever).
Edit 2:
I now added RewriteRule ^whetever/?$ /foo/bar.php [L] in the / .htaccess. Then I added RewriteRule bar\.php$ /whetever [R=302,L] in the /foo .htaccess. The problem is it 's a circular reference and fails.
Thanks,
John
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/foo/[^/]+\.php$
RewriteCond %{IS_SUBREQ} !true
RewriteRule ^/foo/([^/]+)\.php$ /foo$1 [R,L]
RewriteCond %{REQUEST_URI} ^/foo[^/]
RewriteRule ^/foo(.*) /foo/$1.php [L]
The first part matches /foo/something.php and transforms them into /foosomething, but only if it is not a sub-request.
The second part takes any /foosometing and transforms it into /foo/something.php, via sub-request
You can try matching against %{THE_REQUEST} and only do the redirect when the actual request is for the php file:
RewriteEngine On
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /foo/bar\.php
RewriteRule bar\.php$ /whatever [R=302,L]
RewriteRule ^whatever/?$ /foo/bar.php [L]

mod_rewrite condition for existing folder

I'm trying to set-up an .htaccess file that will pass every request URL as GET into a file called index.php. The only exception is, when the request URL points to a directory res.
Examples:
/wiki/cool-stuff => index.php?uri=/wiki/cool-stuff
/wiki/cool-stuff?news=on => index.php?uri=/wiki/cool-stuff&news=on
/res/cool-photo.jpg => res/cool-photo.jpg
Two problems:
The GET variable passed to /wiki/cool-stuff in the second example is not passed to index.php
Accessing /res (not /res/!!) suddenly shows me /res/?uri=res in my browser and index.php with uri=res/. Accessing /res/ instead, shows me index.php with uri=res/ and the URL in the browser stays (which is okay).
The .htaccess:
RewriteEngine on
RewriteBase /subthing/
RewriteCond %{REQUEST_URI} !/res/(.+)$
RewriteCond %{REQUEST_URI} !index.php
RewriteRule (.*) index.php?uri=$1
How can I achieve the desired behaviour?
Try using the Query-String-Append flag, QSA
Make the trailing slash optional - in Regex, this is achieved by adding ?.
New .htaccess:
RewriteEngine on
RewriteBase /subthing/
RewriteCond %{REQUEST_URI} !/res(/.*)?$
RewriteCond %{REQUEST_URI} !index.php
RewriteRule (.*) index.php?uri=$1 [QSA]
Note that I have tweaked the Regex on the /res folder to cause /resabc to be redirected (if the slash was the only optional piece, anything beginning with res would match.
Apache Mod_Rewrite Documentation

invisible mod_rewrite is not always invisible!? ("www" and "without subdomain")

My site is on a host using cPanel 11.
Unfortunatly it redirects both "www.e-motiv.net" and "e-motiv.net" to public_html.
I want resp. public_html/www and public_html/ and this invisible to the end user.
I thought the best way was through mod_rewrite, so I did the following.
File space looks like this (from public_html/):
/.htaccess
/index.php
/www/index.html
/www/test/index.html
And I want this (second part invisible!):
e-motiv.net -> /index.php
www.e-motiv.net -> /www/index.php
www.e-motiv.net/test -> /www/test/index.php
I thought this would do it:
RewriteCond %{HTTP_HOST} ^www.e-motiv.net$
RewriteCond %{REQUEST_URI} !/www
RewriteRule ^(.*)$ /www/$1 [NC,L]
1 and 2 work, but although 3 gives the right file, it changes the address!? (so not invisible)
So, in address bar you get: www.e-motiv.net/test -> www.e-motiv.net/www/test/
Huh??
If mod_rewrite is not the best solution, please do tell!
This is because of mod_dir. mod_dir adds the tailing slashed to urls that map to directories. mod_dir is not aware of these 'virtual urls' created with mod_rewrite.
So either disable this behavior by using
DirectorySlash Off
This will however make requests to www.example.com/folder result in a 404 not found. You can fix this with some rewriterule though. So the complete solution would be something like:
DirectorySlash Off
#www dir only
RewriteCond %{DOCUMENT_ROOT}/$0 -d
RewriteRule ^www/(.+[^/])$ /$1/ [R,L]
#other dirs
RewriteCond %{DOCUMENT_ROOT}/$0 -d
RewriteRule ^(.+[^/])$ /$1/ [R,L]