Unable to ignore mod_rewrite internal redirects with NS flag - apache

I have defined a couple mod_rewrite rules in an .htaccess file, one to rewrite the URL path from /rwtest/source.html to /rwtest/target.html, and another to prohibit direct access to /rwtest/target.html. That is, all users wishing to see the content of /rwtest/target.html must enter /rwtest/source.html in their URL bar.
I was trying to use the NS flag in the forbid rule to prevent rewritten URLs from being denied as well, but it appears this flag does not distinguish between the first request and the internal redirect. It would seem that NS should do the job, but I'm sure I'm misunderstanding something.
Can someone please clarify this behavior? What exactly makes this internal redirect not an internal subrequest that the NS flag can ignore?
Details:
Here's my full .htaccess file:
Options +FollowSymLinks -Multiviews
RewriteEngine on
RewriteBase /rwtest
# Forbid rule. Prohibit direct access to target.html. Note the NS flag.
RewriteRule ^target.html$ - [F,NS]
# Rewrite rule. Rewrite source.html to target.html.
RewriteRule ^source.html$ target.html
I'm running Apache 2.4.9 on Windows 7 x64, but I've observed similar behavior on Apache 2.4.3 on Linux. Here's Log output for a request to /rwtest/source.html.
[rewrite:trace3] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] strip per-dir prefix: C:/Apache24/htdocs/rwtest/source.html -> source.html
[rewrite:trace3] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] applying pattern '^target.html$' to uri 'source.html'
[rewrite:trace3] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] strip per-dir prefix: C:/Apache24/htdocs/rwtest/source.html -> source.html
[rewrite:trace3] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] applying pattern '^source.html$' to uri 'source.html'
[rewrite:trace2] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] rewrite 'source.html' -> 'target.html'
[rewrite:trace3] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] add per-dir prefix: target.html -> C:/Apache24/htdocs/rwtest/target.html
[rewrite:trace2] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] trying to replace prefix C:/Apache24/htdocs/rwtest/ with /rwtest
[rewrite:trace5] [rid#20b6200/initial] strip matching prefix: C:/Apache24/htdocs/rwtest/target.html -> target.html
[rewrite:trace4] [rid#20b6200/initial] add subst prefix: target.html -> /rwtest/target.html
[rewrite:trace1] [rid#20b6200/initial] [perdir C:/Apache24/htdocs/rwtest/] internal redirect with /rwtest/target.html [INTERNAL REDIRECT]
[rewrite:trace3] [rid#20ba360/initial/redir#1] [perdir C:/Apache24/htdocs/rwtest/] strip per-dir prefix: C:/Apache24/htdocs/rwtest/target.html -> target.html
[rewrite:trace3] [rid#20ba360/initial/redir#1] [perdir C:/Apache24/htdocs/rwtest/] applying pattern '^target.html$' to uri 'target.html'
[rewrite:trace2] [rid#20ba360/initial/redir#1] [perdir C:/Apache24/htdocs/rwtest/] forcing responsecode 403 for C:/Apache24/htdocs/rwtest/target.html
Workarounds
I've posted a few workarounds below.

There are several workarounds for this, each with their pros and cons. As a disclaimer, I've only tested them in an .htaccess context.
Workaround 1. Check for empty REDIRECT_STATUS
Add a RewriteCond checking to see if %{ENV:REDIRECT_STATUS} is empty. If it is empty, then the current request is not an internal redirect.
Pros
Most direct way to determine internal redirect.
Cons
Lack of documentation. The page on Custom Error Responses mentions this variable briefly:
REDIRECT_ environment variables are created from the environment variables which existed prior to the redirect. They are renamed with a REDIRECT_ prefix, i.e., HTTP_USER_AGENT becomes REDIRECT_HTTP_USER_AGENT. REDIRECT_URL, REDIRECT_STATUS, and REDIRECT_QUERY_STRING are guaranteed to be set, and the other headers will be set only if they existed prior to the error condition.
I've tried every other REDIRECT_ variable in RewriteCond, yet all of them except REDIRECT_STATUS were empty for internal redirects. Why REDIRECT_STATUS is the special one in mod_rewrite remains a mystery.
Example
# Forbid rule. Prohibit direct access to target.html.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^target.html$ - [F]
# Rewrite rule. Rewrite source.html to target.html.
RewriteRule ^source.html$ target.html
Credits for this approach go to URL rewrite : internal server error.
Workaround 2. Halt rewrite rule processing with END
Unlike the L flag, END halts rewrite rules even for internal redirects.
Pros
Simple. Just an extra flag.
Cons
Does not give you enough control over which rules to process and which to skip.
Example
# Forbid rule. Prohibit direct access to target.html.
RewriteRule ^target.html$ - [F]
# Rewrite rule. Rewrite source.html to target.html.
RewriteRule ^source.html$ target.html [END]
For more information see END flag.
Workaround 3. Match against original URL in THE_REQUEST
%{THE_REQUEST}
The full HTTP request line sent by the browser to the server (e.g., "GET /index.html HTTP/1.1").
THE_REQUEST does not change with internal redirects, so you can match against it.
Pros
Can be used to match against the original URL even in the second round of URL processing.
Cons
Significantly more complicated than the other approaches. Forces the use of RewriteCond where just one RewriteRule would have been sufficient.
Matches against the full URL which has not been unescaped (decoded), unlike most other variables.
Inconvenient to use in multiple RewriteRules. RewriteConds can be copied above every RewriteRule or the value can be exported to an environment variable (see example). Both hacky alternatives.
Example
# Forbid rule. Prohibit direct access to target.html.
RewriteCond %{THE_REQUEST} "^[^ ]+ ([^ ?]*)" # extract path from request line
RewriteCond %1 ^/rwtest/target.html$
RewriteRule ^ - [F]
# Rewrite rule. Rewrite source.html to target.html.
RewriteRule ^source.html$ target.html
Or, export the path to an environment variable and use it in multiple RewriteRules.
# Extract the original URL and save it to ORIG_URL.
RewriteCond %{THE_REQUEST} "^[^ ]+ ([^ ?]*)" # extract path from request line
RewriteRule ^ - [E=ORIG_URL:%1]
# Forbid rule. Prohibit direct access to target.html.
RewriteCond %{ENV:ORIG_URL} ^/rwtest/target.html$
RewriteRule ^ - [F]
# Rewrite rule. Rewrite source.html to target.html.
RewriteCond %{ENV:ORIG_URL} ^/rwtest/source.html$
RewriteRule ^ target.html

Related

Send all requests to subfolder using Apache

I'm trying to get all requests to my website passed on to a subfolder in my webroot.
Here's my folder structure:
/webroot
current
releases
release1
index.html
en
index.html
release2
index.html
en
index.html
...
.htaccess
The current folder is a symlink pointing to a folder in the releases folder. The idea is that when I make a new release, a new folder is created in the releases folder and the current symlink is then pointing to this new folder.
In my .htaccess I'm then trying to pass on all requests to the current symlink. So if a user requests, for example, /en the requests should be routed through the symlink and while the browser URL will look like https://example.com/en the actual request will end up at https://example.com/current/en
Now, this works perfectly as long as the request ends with a trailing slash, for example https://example.com/en/ but if I remove the trailing slash the routing works BUT the URL in the browser will be https://example.com/current/en
Here's the rewrites I'm doing in my .htaccess:
Options +FollowSymlinks -MultiViews -Indexes
DirectorySlash On
RewriteEngine on
RewriteBase /
RewriteCond %{REQUEST_URI} !^/current/
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.*)$ current/$1 [L]
And here's the logfile
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'en'
[perdir /path/to/webroot/] RewriteCond: input='/en' pattern='!^/current/' => matched
[perdir /path/to/webroot/] RewriteCond: input='' pattern='^$' => matched
[perdir /path/to/webroot/] rewrite 'en' -> 'current/en'
[perdir /path/to/webroot/] add per-dir prefix: current/en -> /path/to/webroot/current/en
[perdir /path/to/webroot/] trying to replace prefix /path/to/webroot/ with /
strip matching prefix: /path/to/webroot/current/en -> current/en
add subst prefix: current/en -> /current/en
[perdir /path/to/webroot/] internal redirect with /current/en [INTERNAL REDIRECT]
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en -> current/en
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en'
[perdir /path/to/webroot/] RewriteCond: input='/current/en' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en/ -> current/en/
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en/'
[perdir /path/to/webroot/] RewriteCond: input='/current/en/' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en/
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en/index.php -> current/en/index.php
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en/index.php'
[perdir /path/to/webroot/] RewriteCond: input='/current/en/index.php' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en/index.php
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en/index.html -> current/en/index.html
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en/index.html'
[perdir /path/to/webroot/] RewriteCond: input='/current/en/index.html' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en/index.html
but if I remove the trailing slash the routing works BUT the URL in the browser will be https://example.com/current/en
I assume you mean https://example.com/current/en/ (with a trailing slash).
This happens because mod_dir tries to "fix" (with a 301 redirect) the URL by appending a trailing slash to physical directories when omitted (this is necessary for directory indexes to work correctly). This "fix" occurs after the URL has been rewritten to the subdirectory (since the URL-path /en does not initially map to a subdirectory).
You can disable this behaviour of mod_dir, so that trailing slashes are not appended; but that gets messy (you then need to manually append the trailing slash as required) and could potentially raise security issues.
Instead, you need to make sure you are always linking to the URL with a trailing slash and manually correcting any URL that omits the trailing slash in .htaccess before mod_dir does so.
Your existing rewrite that rewrites to the /current subdirectory looks OK, although you don't necessarily need to check that the request is not already for /current/ (the first condition). (By checking that the request does not already start /current/ then you are allowing direct requests to the actual symlink'd filesystem location - although maybe that's a requirement?)
So, before your existing rewrite, add the following redirect:
# Fix any requests for directories that omit the trailing slash
RewriteCond %{DOCUMENT_ROOT}/current/$1 -d
RewriteRule ^(.+[^/])$ /$1/ [R=301,L]
Given a request for /example, the above first checks whether /current/example exists as a directory (albeit a symlink'd directory). If if does then it issues a 301 redirect to append the trailing slash to the original URL, not the rewritten URL. eg. /example is redirected to /example/, which is then later rewritten to /current/example/.
Test first with 302 (temporary) redirects to avoid potential caching issues.
You will need to clear your browser cache, since the erroneous 301 (permanent) redirect by mod_dir will have been cached by the browser.
UPDATE:
it should not be possible to directly access any files or folders in the "current" folder. Currently, that's possible...
As hinted at above, you could simply remove the first condition that checks whether the REQUEST_URI does not already start with /current/. So all direct requests are unconditionally rewritten to /current/. A request for /current/en/ would therefore be rewritten to /current/current/en, resulting in a 404.
However, a user could still potentially request the filesystem location directly, bypassing the symlink entirely. eg. /releases/release1/en/.
To block both these locations from direct access you could add the following as the first rule:
# Block direct access to "/current/" and "/releases/"
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(current|releases)($|/) - [F]
Requesting /current/... or /releases/... directly would result in a "403 Forbidden". Or change the F to R=404 to serve a "404 Not Found" instead (no redirect occurs, despite the use of the R flag).
The check against the REDIRECT_STATUS environment variable (as in your original rule) ensures that the request can still be internally rewritten to these locations. (The REDIRECT_STATUS env var is empty on the initial request from the client, but set to the HTTP response status (eg. "200") after the first rewrite.)

Rewrite subdomain to subdirectory in Apache .htaccess file

Suppose I have a domain called example.com and I want to use rewrite rules in the .htaccess file of Apache to rewrite:
https://office.example.com/index.html
to
https://example.com/office/index.html.
How would I do that? I checked lots of answers here, and the solution seems to be something like this:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^office.example.com$
RewriteRule ^(.*)$ https://example.com/office/$1 [L,NC,QSA]
This works when I test it here:
https://htaccess.madewithlove.be?share=b40ca72f-86d3-5452-a04b-ac9f24812c57
Regrettably, it does generate an error 500 on my server. I enabled logging and found that this seems to be a recursion problem:
AH00124: Request exceeded the limit of 10 internal redirects.
In the logs it seems to add office to office endlessly: /office/office/office/.... I have no idea why this is happening. The rewritten URL doesn't meet the rewrite condition, so why would it do this?
I have found a way to make it "work". If I add R=301 to the RewriteRule attributes it does a redirect, and works, but I would prefer if the original URL remained in the address bar.
Here's the log for the first 2 redirects:
init rewrite engine with requested uri /
applying pattern '^(.*)$' to uri '/'
applying pattern '^(.*)$' to uri '/'
applying pattern '^(.*)$' to uri '/'
pass through /
[perdir /var/www/vhosts/example.com/httpdocs/] strip per-dir prefix: /var/www/vhosts/example.com/httpdocs/ ->
[perdir /var/www/vhosts/example.com/httpdocs/] applying pattern '^(.*)$' to uri ''
[perdir /var/www/vhosts/example.com/httpdocs/] rewrite '' -> 'https://example.com/office/'
reduce https://example.com/office/ -> /office/
[perdir /var/www/vhosts/example.com/httpdocs/] internal redirect with /office/ [INTERNAL REDIRECT]
#1 init rewrite engine with requested uri /office/
#1 applying pattern '^(.*)$' to uri '/office/'
#1 applying pattern '^(.*)$' to uri '/office/'
#1 applying pattern '^(.*)$' to uri '/office/'
#1 pass through /office/
#1 [perdir /var/www/vhosts/example.com/httpdocs/] strip per-dir prefix: /var/www/vhosts/example.com/httpdocs/office/
#1 [perdir /var/www/vhosts/example.com/httpdocs/] applying pattern '^(.*)$' to uri 'office/'
#1 [perdir /var/www/vhosts/example.com/httpdocs/] rewrite 'office/' -> 'https://example.com/office/office/'
#1 reduce https://example.com/office/office/ -> /office/office/
#1 [perdir /var/www/vhosts/example.com/httpdocs/] internal redirect with /office/office/ [INTERNAL REDIRECT]
#2 init rewrite engine with requested uri /office/office/
#2 applying pattern '^(.*)$' to uri '/office/office/'
#2 applying pattern '^(.*)$' to uri '/office/office/'
#2 applying pattern '^(.*)$' to uri '/office/office/'
#2 pass through /office/office/
#2 [perdir /var/www/vhosts/example.com/httpdocs/] add path info postfix: /var/www/vhosts/example.com/httpdocs/office
#2 [perdir /var/www/vhosts/example.com/httpdocs/] strip per-dir prefix: /var/www/vhosts/example.com/httpdocs/office/
#2 [perdir /var/www/vhosts/example.com/httpdocs/] applying pattern '^(.*)$' to uri 'office/office/'
#2 [perdir /var/www/vhosts/example.com/httpdocs/] rewrite 'office/office/' -> 'https://example.com/office/office/off
#2 reduce https://example.com/office/office/office/ -> /office/office/office/
#2 [perdir /var/www/vhosts/example.com/httpdocs/] internal redirect with /office/office/office/ [INTERNAL REDIRECT]
RewriteCond %{HTTP_HOST} ^office.example.com$
RewriteRule ^(.*)$ https://example.com/office/$1 [L,NC,QSA]
Rather confusing, this should implicitly trigger an external 302 (temporary) redirect, not an internal rewrite - when specifying a different host in the substitution string to the one being requested. (Although in my experience, any absolute URL in the substitution string triggers an external redirect.)
If it does trigger an internal rewrite (as indicated by the logs) then the requested hostname does not change (since this is not a separate request) and you will indeed get a rewrite loop.
However, if "the subdomain is an alias of the main domain" and a rewrite is what's required, then there is no need to specify a hostname in the substitution string and you will indeed need to make additional checks to prevent an internal rewrite loop (500 error).
Try the following instead:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^office\.example\.com [NC]
RewriteRule !^office office%{REQUEST_URI} [L]
...to exclude any requests (including rewritten requests) that already start /office.
No need for the NC and QSA flags.
Alternatively, to only target direct requests (not rewritten requests) you could check the REDIRECT_STATUS environment variable instead (which is empty on the initial request and set to "200", as in 200 OK, after the first successful rewrite).
For example:
RewriteCond %{HTTP_HOST} ^office\.example\.com [NC]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule (.*) office/$1 [L]
This has the added "benefit" that you can potentially have a sub-subdirectory called /office as well. ie. /office/office.
UPDATE: A third version is to check against the REQUEST_URI server variable. However, I would not expect this to be any different from the first version above.
RewriteCond %{HTTP_HOST} ^office\.example\.com [NC]
RewriteCond %{REQUEST_URI} !^/office
RewriteRule ^ office%{REQUEST_URI} [L]
Sadly enough, both your suggestions gave the same error as before.
Two things to try...
Add a slash prefix on the substitution string. ie. /office%{REQUEST_URI} and /office/$1 respectively. This changes the substitution string into a URL-path, rather than a relative filesystem path. However, I wouldn't necessarily expect this to make any difference in this respect. (It would be required for an external redirect.)
Use the END flag instead of L on the RewriteRule directives - this is an Apache 2.4 addition that should halt all processing. The L flag "only" ends the current pass before restarting the rewriting process (hence the need for additional checks to prevent rewrite loops).
But now any other file (IMG, CSS) gives an 404.
The above rewrites everything, so it will naturally rewrite all static resources if they don't already start /office. (If they already start /office then they should already be excluded by the above rules.)
To exclude common resources, you could make an exception (an additional RewriteCond directive) to exclude specific file extensions. For example:
RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|gif)$
And/or add an additional RewriteCond directive to exclude requests that already map to physical files (although this is "marginally" more expensive). For example:
RewriteCond %{REQUEST_FILENAME} !-f
Summary:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^office\.example\.com [NC]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|gif)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule (.*) office/$1 [END]

How to redirect URL with htaccess (RewriteRule) and prevent direct access

using .htaccess I'd like to transparently redirect requests for folder "old" to folder "new", and in the same time prevent direct access to folder "new":
desired result:
http://example.com/old/... -> will display what's in "new" (no URL change in browser!)
http://example.com/new/... -> no access
this is my code in .htaccess (the 1st line is here because several domains share the same root folder):
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^old(.*)$ new$1 [L]
RewriteRule ^new(.*)$ - [F]
Well, what happens is that the 3d line triggers because of the substitution in the 2nd. I was convinced that the flag "L" would prevent this from happening (end of processing), but it seems that's not the case.
Do you have any suggestions what needs to be done (I tried to debug with rewrite log, but without success)?
I did some logging and found the following:
[rid#d0ac98/initial] (3) [per-dir C:/www/example/] add path info postfix: C:/www/example/new -> C:/www/example/new/
[rid#d0ac98/initial] (3) [per-dir C:/www/example/] strip per-dir prefix: C:/www/example/new/ -> new/
[rid#d0ac98/initial] (3) [per-dir C:/www/example/] applying pattern '^new(.*)$' to uri 'new/'
[rid#d0ac98/initial] (4) RewriteCond: input='localhost' pattern='^example\.com$' => not-matched
[rid#d0ac98/initial] (4) RewriteCond: input='localhost' pattern='^localhost$' => matched
[rid#d0ac98/initial] (2) [per-dir C:/www/example/] rewrite new/ -> old/
[rid#d0ac98/initial] (3) [per-dir C:/www/example/] add per-dir prefix: old/ -> C:/www/example/old/
[rid#d0ac98/initial] (2) [per-dir C:/www/example/] strip document_root prefix: C:/www/example/old/ -> /example/old/
[rid#d0ac98/initial] (1) [per-dir C:/www/example/] internal redirect with /example/old/ [INTERNAL REDIRECT]
[rid#d217a8/initial/redir#1] (3) [per-dir C:/www/example/] strip per-dir prefix: C:/www/example/old/ -> old/
[rid#d217a8/initial/redir#1] (3) [per-dir C:/www/example/] applying pattern '^new(.*)$' to uri 'old/'
[rid#d217a8/initial/redir#1] (3) [per-dir C:/www/example/] strip per-dir prefix: C:/www/example/old/ -> old/
[rid#d217a8/initial/redir#1] (3) [per-dir C:/www/example/] applying pattern '^old(.*)$' to uri 'old/'
[rid#d217a8/initial/redir#1] (2) forcing 'C:/www/example/old/' to be forbidden
This seems an internal redirect, which causes the "forbidden" result. Indeed, the documentation mentions it:
It is therefore important, if you are using RewriteRule directives in one of these contexts, that you take explicit steps to avoid rules looping, and not count solely on the [L] flag to terminate execution of a series of rules, as shown below. An alternative flag, [END], can be used to terminate not only the current round of rewrite processing but prevent any subsequent rewrite processing from occurring in per-directory (htaccess) context. This does not apply to new requests resulting from external redirects...
So I suppose that in my example the error was due to the fact that I used "L" flag instead the "END" flag?
I found an alternative solution (3rd line is inserted here):
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^old(.*)$ new$1 [L]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^new(.*)$ - [F]
The 4th line will be executed only if there's no internal redirect.
You can use:
RewriteEngine On
# if directly requesting /new then block it
RewriteCond %{THE_REQUEST} \s/+new(/\S*)?\s [NC]
RewriteRule ^ - [F]
# forward /old/abc to /new/abc
RewriteCond %{HTTP_HOST} ^(www\.)?example\.com$ [NC]
RewriteRule ^old(/.*)?$ new$1 [L,NC]
We used THE_REQUEST in first rule. THE_REQUEST variable represents original request received by Apache from your browser and it doesn't get overwritten after execution of some rewrite rules. REQUEST_URI on the other hand changes its value after other rewrite rules.
For the same reason your rule RewriteRule ^new(.*)$ - [F] will even block your request from /old/ since first rule changes URI to /new/.

Why this modrewrite rule not having an redirect loop

Apache Version: Apache/2.2.22 (Ubuntu)
I have the following rewrite rule defined in .htaccess file
RewriteRule ^goto/(.*)$ goto/index.php?q=$1 [L,QSA]
And it is working fine (means it is reaching index.php of goto folder). But my thought in this that it should generate a redirect loop.
Suppose a url is http://example.com/goto/foo. So in first iteration it will have http://example.com/goto/index.php?q=foo. In second iteration it should match rewriterule goto/(.*) and should have a redirect loop.
My question is how it avoiding the redirect loop?
My .htacces file contains only the folwwing.
RewriteEngine on
RewriteRule ^goto/(.*)$ goto/index.php?q=$1 [L,QSA]
And inside goto folder there is only index.php. No other files there.
EDIT
I have also tested this using wamp 2.2
Apache version 2.2.2
Below is the rewrite log
[perdir D:/wamp/www/test/blog/] strip per-dir prefix: D:/wamp/www/test/blog/goto/ddfd -> goto/ddfd
[perdir D:/wamp/www/test/blog/] applying pattern '^goto/(.*)$' to uri 'goto/ddfd'
[perdir D:/wamp/www/test/blog/] rewrite 'goto/ddfd' -> 'goto/index.php?q=ddfd'
split uri=goto/index.php?q=ddfd -> uri=goto/index.php, args=q=ddfd
[perdir D:/wamp/www/test/blog/] add per-dir prefix: goto/index.php -> D:/wamp/www/test/blog/goto/index.php
[perdir D:/wamp/www/test/blog/] strip document_root prefix: D:/wamp/www/test/blog/goto/index.php -> /test/blog/goto/index.php
[perdir D:/wamp/www/test/blog/] internal redirect with /test/blog/goto/index.php [INTERNAL REDIRECT]
[perdir D:/wamp/www/test/blog/] strip per-dir prefix: D:/wamp/www/test/blog/goto/index.php -> goto/index.php
[perdir D:/wamp/www/test/blog/] applying pattern '^goto/(.*)$' to uri 'goto/index.php'
[perdir D:/wamp/www/test/blog/] rewrite 'goto/index.php' -> 'goto/index.php?q=index.php'
split uri=goto/index.php?q=index.php -> uri=goto/index.php, args=q=index.php&q=ddfd
[perdir D:/wamp/www/test/blog/] add per-dir prefix: goto/index.php -> D:/wamp/www/test/blog/goto/index.php
[perdir D:/wamp/www/test/blog/] initial URL equal rewritten URL: D:/wamp/www/test/blog/goto/index.php [IGNORING REWRITE]
Last entry says it is IGNORING REWRITE. So what configuration is actually instructing to ignore rewrite in this case?
On my setup, this rule indeed causes an infinite loop. It will eventually give a 500 Internal Error, because it exceeds the maximum amount of internal redirects. In .htaccess the [L] flag will only stop the current cycle of rewrites to stop, but it won't stop a new cycle from happening. It will only stop if the url stops changing. (This is different behaviour than in httpd.conf when not in per-directory context where the [L] flag will stop rewriting completely)
There are a couple of ways you can stop the infinite loop.
#1. Any url with index.php in it will not be rewritten
RewriteRule index\.php - [L]
RewriteRule ^goto/(.*)$ goto/index.php?q=$1 [L,QSA]
Every url with index.php in it will match the first rule. Because the url is not rewritten, it will not initiate a new cycle.
#2. Exclude
RewriteCond %{REQUEST_URI} !^/goto/index\.php$
RewriteRule ^goto/(.*)$ goto/index.php?q=$1 [L,QSA]
Use a condition to check if index.php is not in the current url
#3. Use the END flag
RewriteRule ^goto/(.*)$ goto/index.php?q=$1 [END,QSA]
Use the END flag. Please note that this flag is only available from Apache 2.3.9 and up. It will stop rewriting completely in .htaccess context.
Actually this is due to non-presence of leading slash in target URL of this URL:
RewriteRule ^goto/(.*)$ goto/index.php?q=$1 [L,QSA]
Now try this rule with leading slash before goto:
RewriteRule ^goto/(.*)$ /goto/index.php?q=$1 [L,QSA]
Now you will notice infinite looping error.
Reason is that in the first case target URI does not begin with a slash. In that case mod_rewrite is smart enough to prevent infinite looping, as it detects that the original request of goto/index.php is the same as the internal rewrite to goto/index.php after first pass, therefore it won't perform the further rewrite with this message in RewriteLog:
[IGNORING REWRITE]
Thanks for the update. Since the first rewritten URI up to (but not including) the query string is the same as the second rewritten URI up to the same point, it is being discarded as redundant. This is a feature of mod_rewrite designed to prevent infinite rewrite loops. If you replace your rule with:
RewriteRule ^goto/(.*)$ /goto/index.php?q=$1 [L,QSA]
(notice the '/' in front of the substitution), assuming goto is in DocumentRoot, you will get the loop you expected. This is because "/goto/index.php" (the result) is different from "goto/index.php" (the originally matched URI).
-- Original answer follows --
Can you verify that you are actually rewriting anything at all? I ask because this is a rewrite from and to a relative path, and you have no RewriteBase directive in the .htaccess file. Unless .htaccess is in the server's (or virtual host's) DocumentRoot directory, the rewrite engine requires RewriteBase to determine how to finish rewriting it.
Further, even if this is in the DocumentRoot directory (which is implied by the fact that the original URL to be rewritten is http://example.com/goto/foo), unless there is an AllowOverride directive giving FileInfo override permission, AND an Options directive specifying FollowSymLinks, effective for the directory, mod_rewrite directives in .htaccess files are ignored.
As for "reaching index.php" - without seeing the configuration, I really can't comment on why it's successfully serving a page, despite the lack of rewriting. It could be that an ErrorDocument directive gives you a page that matches what you expect. There could even be some rewriting rules in the server or virtual host configuration files that handle the case where /([^/]+)/index.php exists, rewriting requests that would otherwise generate a 404 error code to use the appropriate index.php file, instead.
References:
http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewriterule - explains what conditions must be satisfied for rewriting to work.
http://httpd.apache.org/docs/2.2/mod/core.html#allowoverride - documents the FileInfo keyword for the AllowOverride directive.
http://codex.wordpress.org/htaccess - describes several ways to manage rewriting to index.php

Apache mod_rewrite a subdomain to a subfolder (via internal redirect)

I'm trying to write a set of mod_rewrite rules that allow my users to utilize a single folder for doing development on different projects, and not have to mess with adding vhosts for every single project.
My idea to accomplish this, is to set up a "Global VHost" for every single user who needs this ability (only 3-4), the vhost would be something like: .my-domain.com. From there, I want to promote my users to write code as if it were on a domain, and not in a sub folder. For example, if bob was working on a project named 'gnome,' I'd like the URL bob (and anyone else on our internal network) loads to get to this project to be: http://gnome.bob.my-domain.com. But, what I'd like Apache to do, is recognize that "gnome" is a "project" and thus map the request, internally, to bob.my-domain.com/gnome/.
I've got what I thought would work, and it's quite simple, but..it doesn't work! The request just goes into an infinite loop and keeps prefixing the sub domain onto the re-written request URI.
The mod rewrite code i have is:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^([^.]+)\.bob\.my-domain\.com
RewriteCond %{REQUEST_URI} !^/%1.*
RewriteRule ^(.*)$ /%1/$1 [L]
I've googled around a bit about this, but I've yet to find any real solutions that work. Has anyone tried this - or maybe, does anyone have a better idea? One that doesn't involve making a virtual host for every project (I've got designers..I think everyone would agree that a designer shouldn't be making virtual hosts..)
Thanks!
Here is a snippet from the rewrite_log:
[rid#838dc88/initial] (3) [perdir /home/bob/http/] strip per-dir prefix: /home/bob/http/index.html -> index.html
[rid#838dc88/initial] (3) [perdir /home/bob/http/] applying pattern '^(.*)$' to uri 'index.html'
[rid#838dc88/initial] (4) [perdir /home/bob/http/] RewriteCond: input='gnome.bob.my-domain.com' pattern='^([^.]+)\.bob\.my-domain\.com' => matched
[rid#838dc88/initial] (4) [perdir /home/bob/http/] RewriteCond: input='/index.html' pattern='!^/%1.*' => matched
[rid#838dc88/initial] (2) [perdir /home/bob/http/] rewrite 'index.html' -> '/gnome/index.html'
[rid#838dc88/initial] (1) [perdir /home/bob/http/] internal redirect with /gnome/index.html [INTERNAL REDIRECT]
[rid#8392f30/initial/redir#1] (3) [perdir /home/bob/http/] strip per-dir prefix: /home/bob/http/gnome/index.html -> gnome/index.html
[rid#8392f30/initial/redir#1] (3) [perdir /home/bob/http/] applying pattern '^(.*)$' to uri 'gnome/index.html'
[rid#8392f30/initial/redir#1] (4) [perdir /home/bob/http/] RewriteCond: input='gnome.bob.my-domain.com' pattern='^([^\.]+)\.bob\.my-domain\.com' => matched
[rid#8392f30/initial/redir#1] (4) [perdir /home/bob/http/] RewriteCond: input='/gnome/index.html' pattern='!^/%1.*' => matched
[rid#8392f30/initial/redir#1] (2) [perdir /home/bob/http/] rewrite 'gnome/index.html' -> '/gnome/gnome/index.html'
[rid#8392f30/initial/redir#1] (1) [perdir /home/bob/http/] internal redirect with /gnome/gnome/index.html [INTERNAL REDIRECT]
[rid#8397970/initial/redir#2] (3) [perdir /home/bob/http/] add path info postfix: /home/bob/http/gnome/gnome -> /home/bob/http/gnome/gnome/index.html
This is just a snippet, there are a few 10s or 100 or so lines of apache basically rewriting /gnome/index.html to /gnome/gnome/gnome/gnome/gnome/index.html, etc before apache hits its rewrite limit, gives up, and throws error 500
After a few years of ignoring this problem and coming back to it at various points, I finally found a workable solution.
RewriteEngine on
RewriteCond %{HTTP_HOST} ^([^.]+)\.bob\.my-domain\.com
RewriteCond %1::%{REQUEST_URI} !^(.*?)::/\1/
RewriteRule ^(.*)$ /%1/$1 [L]
What I found was that back-references for previous RewriteCond directions are not available in the ConditionPattern parameter of future RewriteConditions. If you want to use a back-reference from a previous RewriteCond directive, you can only use it in the TestString parameter.
The above directives prepend the sub-domain matched in the 1st RewriteCond directive to the RequestURI, delimited by ::. What we then do in the RewriteCond Test String (regex) is re-capture the sub-domain name, then check to make sure our actual RequestURI doesn't begin with that sub-domain as a folder using a back reference within the same regex.
This sounds a lot more confusing than it really is, and I can't take the credit for discovering the answer. I found the answer as a response to another question here, %N backreference inside RewriteCond. Thanks to Jon Lin for answering that question, and unknown to him, my question too!
You might want to check
http://httpd.apache.org/docs/2.2/vhosts/mass.html
it deals with the DocumentRoot problem that you were experiencing.
Rule goes something like this
VirtualDocumentRoot /var/www/%1/
You can change the %1 for whatever suits you (http://httpd.apache.org/docs/2.0/mod/mod_vhost_alias.html)
Cheers
Some Questions:
You said "map internally" -- do you NOT want to use a redirect?
Are you using the same VirtualHost for gnome.bob.mysite.com and bob.mysite.com
Did you remember to create a ServerAlias for *.bob.mysite.com?
Here is a rough version that you could modify to work. It will capture the subdomain and requested URL, and do a redirect to the main domain with the subdomain as the first part of the path, followed by the requested path, followed by the query string.
ServerName www.mysite.com
ServerAlias *.mysite.com
RewriteCond %{HTTP_HOST} ^([a-zA-Z0-9-]+)\\.mysite.com$
RewriteRule ^/(.*) http://www.mysite.com/%1/$1 [R=301,L]',
Have you tried using another rewrite rule to process the one before it?
RewriteEngine On
RewriteCond %{HTTP_HOST} ^([^.]+)\.bob\.my-domain\.com
RewriteCond %{REQUEST_URI} !^/%1.*
RewriteRule ^(.*)$ /%1/$1 [C]
RewriteRule ^/(.*)\.bob\.my-domain\.com/(.*) /$1/$2 [L]
But I think your bigger problem is the fact that your server doesn't understand it is getting served under a different name.
It thinks it is running in the /gnome/ directory while the browser things it is running in the / directory. So any relative URL's that you have are going to cause issues.
What you need is a filter that will run all the URL's in your page through a processor and change them from /gnome/ to /.