htaccess rewrite subdomain to some folder if exists else another - apache

I know there are some similar questions about subdomain -> folder htaccess, but I already have a setup that works for me for that simple case.
What I want to add, is "rewrite to projects/subdomain/current/public/ if that current/public exists, else rewrite to projects/subdomain/"
It's because I'm hosting a laravel application, which is in 'public/', and I deployed it using a tool that symlinks 'current/' to its latest release.
My current setup is:
# Redirect everything non-www to /projects/...
RewriteEngine on
RewriteCond %{HTTP_HOST} !^www.* [NC]
RewriteCond %{HTTP_HOST} ^([^\.]+)\.mydomain\.com
RewriteCond /var/www/mydomain.com/projects/%1 -d
RewriteRule ^(.*) /projects/%1/$1 [L]
Which is something like:
if it doesn't start with www
get the subdomain in a var
if the /projects/subdomain folder exists
rewrite the request to look into that folder
else 404
How would I update this to have an "IF /projects/subdomain/current/public exists, then that, ELSE IF /projects/subdomain exists, then that, ELSE 404"?
Thanks in advance!

Just create another rule before the existing one. For example, following the same pattern as your existing rule:
RewriteEngine on
# Redirect to /projects/<subdomain>/current/public/
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^([^\.]+)\.mydomain\.com
RewriteCond /var/www/mydomain.com/projects/%1/current/public -d
RewriteRule (.*) /projects/%1/current/public/$1 [L]
# Redirect everything non-www to /projects/...
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^([^\.]+)\.mydomain\.com
RewriteCond /var/www/mydomain.com/projects/%1 -d
RewriteRule (.*) /projects/%1/$1 [L]
I removed the ^ from the RewriteRule pattern (^(.*)) since it was superfluous.
I also changed !^www.* (matches any hostname that simply starts "www") to !^www\. (any hostname that starts "www." - the subdomain only) to avoid potentially spurious matches.
Presumably /var/www/mydomain.com is the document-root, so you could use the DOCUMENT_ROOT server variable instead in the RewriteCond TestString. For example:
RewriteCond %{DOCUMENT_ROOT}/projects/%1/current/public -d
However, by themselves, these directives will result in a rewrite-loop (500 error), so I assume you already have directives in place that prevent this. eg. Another .htaccess file in the directory that you are rewriting to that contains mod_rewrite directives (relevant to the individual projects)? If not then you can either add another condition to each rule that checks against the REDIRECT_STATUS environment variable, or include an exception at the top of the file that prevents further processing after the request is rewritten. For example:
RewriteEngine On
# Stop further processing if the request has already been rewritten
RewriteCond %{ENV:REDIRECT_STATUS} !^$
RewriteRule ^ - [L]
: Remaining directives follow...
UPDATE: If you are on Apache 2.4 you can instead simply change the L flag to END to halt all processing by the rewrite engine, to prevent a rewrite loop. The END flag is a relatively recent addition and consequently often gets overlooked - but it's not strictly necessary as there are always other (perhaps more complex) ways to do this.
Why would it loop?
The L flag does not stop all processing. It simply stops the current round of processing and then the rewrite engine effectively starts over, passing the rewritten URL back into the rewrite engine. In the above example, the rewritten URL also matches the same rules, so the URL would be rewritten again, and again, and again, ....
For example (for simplicity, just using your original rule that rewrites to /projects):
Request subdomain.mydomain.com/foo
<doc-root>/projects/subdomain directory exists
URL rewritten to /projects/subdomain/foo
Rewriting process starts over... passing in the rewritten URL, essentially subdomain.mydomain.com/projects/subdomain/foo, back into the rewrite engine.
<doc-root>/projects/subdomain directory exists (as previous)
URL rewritten to /projects/subdomain/projects/subdomain/foo
Rewriting process starts over... etc. etc.
The Rewriting process loops in this fashion until the URL passes through unchanged, unless something else steps in the way... for example, as mentioned above, if you have another .htaccess file located at /projects/subdomain/.htaccess that also contains mod_rewrite directives then control would pass to this .htaccess file after the first round of rewrite processing and prevent further rewrites (since mod_rewrite directives are not inherited by default).

Related

No trailing slash causes 404, how to fix using htaccess?

The URLs are:
(Doesn't work) http://example.com/seller/samsung
(Works) http://example.com/seller/samsung/
The .htaccess rule I have for these types of URLs looks like:
RewriteRule ^seller/[^/]+/(.*)$ ./$1
What can I do to make both of these URLs to go to the same page?
You could just force a trailing slash to appear on the end of your URLs. You can do that by using the following in your .htaccess:
RewriteCond %{REQUEST_URI} !(/$|\.)
RewriteRule (.*) %{REQUEST_URI}/ [R=301,L]
Just make sure you clear your cache before you test this.
EDIT:
RewriteCond %{REQUEST_URI} /+[^\.]+$
RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]
What does the above do? So the condition grabs your directory, so for example /samsung and will check if it has a / on the end. If it does not, it will grab the directory at the end of URL (once again /samsung and add a / on to it. It will do this using a 301 redirect and would leave you with /samsung/.
As for the L flag (taken from official documentation):
The [L] flag causes mod_rewrite to stop processing the rule set. In
most contexts, this means that if the rule matches, no further rules
will be processed. This corresponds to the last command in Perl, or
the break command in C. Use this flag to indicate that the current
rule should be applied immediately without considering further rules.

htaccess Force SSL if certain file exists

I have the following mod rewrite code that forces SSL on my website:
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
I have to edit the htaccess file if I want to turn this off or on. So what I am thinking is to have a file for example called ssl_on stored in a certain path of the server and if the file ssl_on exists then this rule applies and if the file doesn't exist then the rule shouldnt apply. Is this possible?
You can use your rule like this:
RewriteCond %{HTTPS} off
RewriteCond %{DOCUMENT_ROOT}/ssl_on -f
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=302]
-f condition will ensure that rule will fire only when ssl_on file exists in website root. Also better to use R=302 instead of R=301 to avoid permanent redirects caching in browser.
As per the docs:
One or more RewriteCond can precede a RewriteRule directive. The following rule is then only used if both the current state of the URI matches its pattern, and if these conditions are met.
So going by this, you could have before (or after) your current RewriteCond line a new RewriteCond line:
RewriteCond <HTTPS_TOGGLE_FILE_PATH> -f
And because both rules need to be true in order for the Rule to be matched, the file would have to exist before the 301 header would be issued.
Note that the rules for caching 301 headers in browsers are convoluted- a lot of them cache the redirection indefinitely. So if you want this to truly be toggleable (e.g. browsers that have accessed the site via HTTP when the redirection is enabled then accessing the site via HTTP when it is disabled) then a 302 would be preferable.

CodeIgniter on subdomain and htaccess

I'm trying to setup a website based on CodeIgniter.
For developing I want to have the domain dev.XXX.com
I changed my structure so I've got a no_www and a public_www folder.
Both are in the root of dev.XXX.com
So my idea was to rewrite url's form
dev.XXX.com/index.php/test
to
dev.XXX.com/public_www/index.php/test
So I just want add public_www to all the requests
In my htaccess file I've got:
RewriteRule ^(.*)$ /public_www/$1 [L]
But I always get 500 - Internal Server Error
Try adding the following to the .htaccess file in the root directory (public_www) of your site.
RewriteEngine on
RewriteBase /
#if its on dev.xxx.com
RewriteCond %{HTTP_HOST} ^dev\.XXX\.com$ [NC]
#if its not already public_www/ rewrite to public_www/
RewriteRule ^(?!public_www/)(.*)$ /public_www/$1 [L,NC]
The rule you had would lead to an infinite rewrite and subsequent 500 error. The one above should prevent that.
EDIT
could you maybe explain why my version leads into a infinite loop
I am likely incorrect about the infinite loop, but below is what will happen
Start with any input
^(.*)$ pattern will match any input
It will rewrite to /public_www/$1
.htaccess rules will be run again
^(.*)$ pattern will match rewritten input /public_www/$1
It will rewrite to /public_www/public_www/$1
At this point it will likely fail as the directory does not exist...
Your RewriteRule pattern ^(.*)$ will match all input and will rewrite to . The .htaccess rules will then be run again

Replace parts of the URL with mod_rewrite

I need a mod_rewrite rule to redirect url depending on the hostname they are comming from.
The situation:
We have multiple domains pointing to a same webspace and we need to restrict what the specific host can see/download.
domainname.com/images/logo.jpg and /www.domainname.com/images/logo.jpg should transform into domainname.com/domainname_com/images/logo.jpg
So basically I need a rule/function that replaces the dots in the %{HTTP_HOST} with _ and removes/replaces the www subdomain.
Is there any way to do this with mod_rewrite?
Try these rules:
RewriteCond %{ENV:DOMAIN_DIR} ^$
RewriteCond %{HTTP_HOST} ^(www\.)?(.+)
RewriteRule ^images/.+ - [E=DOMAIN_DIR:%2]
RewriteCond %{ENV:DOMAIN_DIR} ^([^.]*)\.(.+)
RewriteRule ^images/.+ - [E=DOMAIN_DIR:%1_%2,N]
RewriteCond %{ENV:DOMAIN_DIR} ^[^.]+$
RewriteRule ^images/.+ %{ENV:DOMAIN_DIR}/$0 [L]
The first rule will take the host and store it without www. in the environment variable DOMAIN_DIR. The second rule will replace one dot at a time; the N flag allows to restart the rewriting process without incrementing the internal recursion counter. Finally, the third rule will rewrite the request to the corresponding directory.

Hidden features of mod_rewrite

There seem to be a decent number of mod_rewrite threads floating around lately with a bit of confusion over how certain aspects of it work. As a result I've compiled a few notes on common functionality, and perhaps a few annoying nuances.
What other features / common issues have you run across using mod_rewrite?
Where to place mod_rewrite rules
mod_rewrite rules may be placed within the httpd.conf file, or within the .htaccess file. if you have access to httpd.conf, placing rules here will offer a performance benefit (as the rules are processed once, as opposed to each time the .htaccess file is called).
Logging mod_rewrite requests
Logging may be enabled from within the httpd.conf file (including <Virtual Host>):
# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2
Common use cases
To funnel all requests to a single point:
RewriteEngine on
# ignore existing files
RewriteCond %{REQUEST_FILENAME} !-f
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# map requests to index.php and append as a query string
RewriteRule ^(.*)$ index.php?query=$1
Since Apache 2.2.16 you can also use FallbackResource.
Handling 301/302 redirects:
RewriteEngine on
# 302 Temporary Redirect (302 is the default, but can be specified for clarity)
RewriteRule ^oldpage\.html$ /newpage.html [R=302]
# 301 Permanent Redirect
RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
Note: external redirects are implicitly 302 redirects:
# this rule:
RewriteRule ^somepage\.html$ http://google.com
# is equivalent to:
RewriteRule ^somepage\.html$ http://google.com [R]
# and:
RewriteRule ^somepage\.html$ http://google.com [R=302]
Forcing SSL
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R,L]
Common flags:
[R] or [redirect] - force a redirect (defaults to a 302 temporary redirect)
[R=301] or [redirect=301] - force a 301 permanent redirect
[L] or [last] - stop rewriting process (see note below in common pitfalls)
[NC] or [nocase] - specify that matching should be case insensitive
Using the long-form of flags is often more readable and will help others who come to read your code later.
You can separate multiple flags with a comma:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Common pitfalls
Mixing mod_alias style redirects with mod_rewrite
# Bad
Redirect 302 /somepage.html http://example.com/otherpage.html
RewriteEngine on
RewriteRule ^(.*)$ index.php?query=$1
# Good (use mod_rewrite for both)
RewriteEngine on
# 302 redirect and stop processing
RewriteRule ^somepage.html$ /otherpage.html [R=302,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# handle other redirects
RewriteRule ^(.*)$ index.php?query=$1
Note: you can mix mod_alias with mod_rewrite, but it involves more work than just handling basic redirects as above.
Context affects syntax
Within .htaccess files, a leading slash is not used in the RewriteRule pattern:
# given: GET /directory/file.html
# .htaccess
# result: /newdirectory/file.html
RewriteRule ^directory(.*)$ /newdirectory$1
# .htaccess
# result: no match!
RewriteRule ^/directory(.*)$ /newdirectory$1
# httpd.conf
# result: /newdirectory/file.html
RewriteRule ^/directory(.*)$ /newdirectory$1
# Putting a "?" after the slash will allow it to work in both contexts:
RewriteRule ^/?directory(.*)$ /newdirectory$1
[L] is not last! (sometimes)
The [L] flag stops processing any further rewrite rules for that pass through the rule set. However, if the URL was modified in that pass and you're in the .htaccess context or the <Directory> section, then your modified request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand this, it often looks like your [L] flag had no effect.
# processing does not stop here
RewriteRule ^dirA$ /dirB [L]
# /dirC will be the final result
RewriteRule ^dirB$ /dirC
Our rewrite log shows that the rules are run twice and the URL is updated twice:
rewrite 'dirA' -> '/dirB'
internal redirect with /dirB [INTERNAL REDIRECT]
rewrite 'dirB' -> '/dirC'
The best way around this is to use the [END] flag (see Apache docs) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent passes). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag.
For earlier versions, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine.
# Only process the following RewriteRule if on the first pass
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ...
Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
if you need to 'block' internal redirects / rewrites from happening in the .htaccess, take a look at the
RewriteCond %{ENV:REDIRECT_STATUS} ^$
condition, as discussed here.
The deal with RewriteBase:
You almost always need to set RewriteBase. If you don't, apache guesses that your base is the physical disk path to your directory. So start with this:
RewriteBase /
Other Pitfalls:
1- Sometimes it's a good idea to disable MultiViews
Options -MultiViews
I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active, because one of its properties is to try and 'guess' an extension to a file that it thinks I'm looking for.
I'll explain:
Suppose you have 2 php files in your web dir, file1.php and file2.php and you add these conditions and rule to your .htaccess :
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1
You assume that all urls that do not match a file or a directory will be grabbed by file1.php. Surprise! This rule is not being honored for the url http://myhost/file2/somepath. Instead you're taken inside file2.php.
What's going on is that MultiViews automagically guessed that the url that you actually wanted was http://myhost/file2.php/somepath and gladly took you there.
Now, you have no clue what just happened and you're at that point questioning everything that you thought you knew about mod_rewrite. You then start playing around with rules to try to make sense of the logic behind this new situation, but the more you're testing the less sense it makes.
Ok, In short if you want mod_rewrite to work in a way that approximates logic, turning off MultiViews is a step in the right direction.
2- enable FollowSymlinks
Options +FollowSymLinks
That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.
Equation can be done with following example:
RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]
Dynamic Load Balancing:
If you use the mod_proxy to balance your system, it's possible to add a dynamic range of worker server.
RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
A better understanding of the [L] flag is in order. The [L] flag is last, you just have to understand what will cause your request to be routed through the URL parsing engine again. From the docs (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (emphasis mine):
The [L] flag causes mod_rewrite to stop processing the rule set. In
most contexts, this means that if the rule matches, no further rules
will be processed. This corresponds to the last command in Perl, or
the break command in C. Use this flag to indicate that the current
rule should be applied immediately without considering further rules.
If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of
how the rules are processed. The simplified form of this is that once
the rules have been processed, the rewritten request is handed back to
the URL parsing engine to do what it may with it. It is possible that
as the rewritten request is handled, the .htaccess file or <Directory>
section may be encountered again, and thus the ruleset may be run
again from the start. Most commonly this will happen if one of the
rules causes a redirect - either internal or external - causing the
request process to start over.
So the [L] flag does stop processing any further rewrite rules for that pass through the rule set. However, if your rule marked with [L] modified the request, and you're in the .htaccess context or the <Directory> section, then your modifed request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand what happened, it looks like your first rewrite rule with the [L] flag had no effect.
The best way around this is to use the [END] flag (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent reparsing). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag. In this case, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine. Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
Another great feature are rewrite-map-expansions. They're especially useful if you have a massive amout of hosts / rewrites to handle:
They are like a key-value-replacement:
RewriteMap examplemap txt:/path/to/file/map.txt
Then you can use a mapping in your rules like:
RewriteRule ^/ex/(.*) ${examplemap:$1}
More information on this topic can be found here:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc
mod_rewrite can modify aspects of request handling without altering the URL, e.g. setting environment variables, setting cookies, etc. This is incredibly useful.
Conditionally set an environment variable:
RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]
Return a 503 response:
RewriteRule's [R] flag can take a non-3xx value and return a non-redirecting response, e.g. for managed downtime/maintenance:
RewriteRule .* - [R=503,L]
will return a 503 response (not a redirect per se).
Also, mod_rewrite can act like a super-powered interface to mod_proxy, so you can do this instead of writing ProxyPass directives:
RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]
Opinion:
Using RewriteRules and RewriteConds to route requests to different applications or load balancers based on virtually any conceivable aspect of the request is just immensely powerful. Controlling requests on their way to the backend, and being able to modify the responses on their way back out, makes mod_rewrite the ideal place to centralize all routing-related config.
Take the time to learn it, it's well worth it! :)