Why doesn't the RewriteCond prevent the RewriteRule from processing? - apache

I've got the following in my .conf file used for cache busting js:
<Directory "/www/virtual/site/html/js/">
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^\d+\/(.+)$ $1 [L]
</Directory>
The idea here is to rewrite site.com/js/123456/script.js to site.com/js/script.js
(The rewriting works just fine)
If I look at the rewrite log the part I'm not understanding is that it is applying the RewriteRule to the script after the interal redirect... Shouldn't the %{REQUEST_FILENAME} !-f prevent it from processing the RewriteRule since the file exists?
Rewrite Log
[rid#2b607f8f2dc8/initial] (3) [perdir /www/virtual/site2/html/js/] add path info postfix: /www/virtual/site2/html/js/1234 -> /www/virtual/site2/html/js/1234/script.js
[rid#2b607f8f2dc8/initial] (3) [perdir /www/virtual/site2/html/js/] strip per-dir prefix: /www/virtual/site2/html/js/1234/script.js -> 1234/script.js
[rid#2b607f8f2dc8/initial] (3) [perdir /www/virtual/site2/html/js/] applying pattern '^\d+\/(.+)$' to uri '1234/script.js'
[rid#2b607f8f2dc8/initial] (2) [perdir /www/virtual/site2/html/js/] rewrite '1234/script.js' -> 'script.js'
[rid#2b607f8f2dc8/initial] (3) [perdir /www/virtual/site2/html/js/] add per-dir prefix: script.js -> /www/virtual/site2/html/js/script.js
[rid#2b607f8f2dc8/initial] (2) [perdir /www/virtual/site2/html/js/] strip document_root prefix: /www/virtual/site2/html/js/script.js -> /js/script.js
[rid#2b607f8f2dc8/initial] (1) [perdir /www/virtual/site2/html/js/] internal redirect with /js/script.js [INTERNAL REDIRECT]
[rid#2b6073eb14a8/initial/redir#1] (3) [perdir /www/virtual/site2/html/js/] strip per-dir prefix: /www/virtual/site2/html/js/script.js -> script.js
[rid#2b6073eb14a8/initial/redir#1] (3) [perdir /www/virtual/site2/html/js/] applying pattern '^\d+\/(.+)$' to uri 'script.js'
[rid#2b6073eb14a8/initial/redir#1] (1) [perdir /www/virtual/site2/html/js/] pass through /www/virtual/site2/html/js/script.js

mod_rewrite (by design) keeps on applying rewrite-rules, till the url no longer changes.
So the first time it rewrite 1234/script.js to script.js the second time it tries to apply the rule, the pattern regular expression doesn't match and script.js is 'passed through'.
This is the way it is supposed to work. It can sometimes be a pain, but it makes mod_rewrite a lot more powerful/useful.
edit
mod_rewite first tests the RewriteRule pattern, before trying to apply the RewriteCond's. This is so you can use backreferences $0, $1 etc. in your RewriteCond. After all the RewriteCond's it will create the substitution string, so it can also use backrefereces %0, %1. etc. See also the diagram below.
So this is why it tried to apply the pattern before testing if the file exists.

Related

Rewrite rules issue with two variables

I am trying to introduce two URL path rewrites but for some reason it does not work as expected.
I am trying to rewrite:
/styles/democlient/main.css to _projects/democlient/public/styles/main.css in the first instance. The second 'styles' rule works as expected, only the first one doesn't.
I added LogLevel alert rewrite:trace6 to my Apache vhosts configuration to make it easier to track. It seems to me that when mod is reaching the first 'style' rule then it does not stop and for some reason tries to match it to the second one.
My .htaccess
Options +FollowSymLinks
RewriteEngine on
RewriteRule styles/([a-z0-9]+)/(.*) _projects/$1/public/styles/$2 [NC,L]
RewriteRule styles/(.*) public/styles/$1 [NC,L]
RewriteRule (.*)/(.*) index.php?page=$1&subpage=$2 [NC,L,QSA]
RewriteRule (.*) index.php?page=$1 [NC,L,QSA]
My logtrack
[perdir E:/www/WEB_tech4rec/] add path info postfix: E:/www/WEB_tech4rec/styles -> E:/www/WEB_tech4rec/styles/democlient/main.css
[perdir E:/www/WEB_tech4rec/] strip per-dir prefix: E:/www/WEB_tech4rec/styles/democlient/main.css -> styles/democlient/main.css
[perdir E:/www/WEB_tech4rec/] applying pattern 'styles/([a-z0-9]+)/(.*)' to uri 'styles/democlient/main.css'
[perdir E:/www/WEB_tech4rec/] rewrite 'styles/democlient/main.css' -> '_projects/democlient/public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] add per-dir prefix: _projects/democlient/public/styles/main.css -> E:/www/WEB_tech4rec/_projects/democlient/public/styles/main.css
[perdir E:/www/WEB_tech4rec/] strip document_root prefix: E:/www/WEB_tech4rec/_projects/democlient/public/styles/main.css -> /_projects/democlient/public/styles/main.css
[perdir E:/www/WEB_tech4rec/] internal redirect with /_projects/democlient/public/styles/main.css [INTERNAL REDIRECT]
[perdir E:/www/WEB_tech4rec/] strip per-dir prefix: E:/www/WEB_tech4rec/_projects/democlient/public/styles/main.css -> _projects/democlient/public/styles/main.css
[perdir E:/www/WEB_tech4rec/] applying pattern 'styles/([a-z0-9]+)/(.*)' to uri '_projects/democlient/public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] strip per-dir prefix: E:/www/WEB_tech4rec/_projects/democlient/public/styles/main.css -> _projects/democlient/public/styles/main.css
[perdir E:/www/WEB_tech4rec/] applying pattern 'styles/(.*)' to uri '_projects/democlient/public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] rewrite '_projects/democlient/public/styles/main.css' -> 'public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] add per-dir prefix: public/styles/main.css -> E:/www/WEB_tech4rec/public/styles/main.css
[perdir E:/www/WEB_tech4rec/] strip document_root prefix: E:/www/WEB_tech4rec/public/styles/main.css -> /public/styles/main.css
[perdir E:/www/WEB_tech4rec/] internal redirect with /public/styles/main.css [INTERNAL REDIRECT]
[perdir E:/www/WEB_tech4rec/] strip per-dir prefix: E:/www/WEB_tech4rec/public/styles/main.css -> public/styles/main.css
[perdir E:/www/WEB_tech4rec/] applying pattern 'styles/([a-z0-9]+)/(.*)' to uri 'public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] strip per-dir prefix: E:/www/WEB_tech4rec/public/styles/main.css -> public/styles/main.css[perdir E:/www/WEB_tech4rec/] applying pattern 'styles/(.*)' to uri 'public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] rewrite 'public/styles/main.css' -> 'public/styles/main.css'
[perdir E:/www/WEB_tech4rec/] add per-dir prefix: public/styles/main.css -> E:/www/WEB_tech4rec/public/styles/main.css
[perdir E:/www/WEB_tech4rec/] initial URL equal rewritten URL: E:/www/WEB_tech4rec/public/styles/main.css [IGNORING REWRITE]
If you could help me find the reason, would be great.
You need to add a ^ before your styles RewriteRules and prevent your last RewriteRules to redirect physical files.
Options +FollowSymLinks
RewriteEngine on
RewriteRule ^styles/([a-z0-9]+)/(.*) _projects/$1/public/styles/$2 [NC,L]
RewriteRule ^styles/(.*) public/styles/$1 [NC,L]
RewriteCond %{REQUEST_URI} !\.(jpg|png|css|js|php)$
RewriteRule (.*)/(.*) index.php?page=$1&subpage=$2 [NC,L,QSA]
RewriteCond %{REQUEST_URI} !\.(jpg|png|css|js|php)$
RewriteRule (.*) index.php?page=$1 [NC,L,QSA]

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/.

Rewrite rules not working with .htaccess but do with vhost

I am a little miffed by this one, the rules I am pretty sure are correct yet when using them in a .htaccess file they just won't apply.
The rules push everything through a dispatch.php which then handles my MVC views and controllers for me.
//htaccess file - Accessing http://topaz.local/app/users/login
RewriteEngine On
RewriteCond %{REQUEST_URI} !dispatch/.php$
RewriteCond /var/www/topaz%{REQUEST_FILENAME} !-f
RewriteRule ^(/.*)$ /dispatch.php?url=$1 [L,QSA]
RewriteRule ^x/.!dispatch/.php([^b]+)!dispatch/.php$ !dispatch/.php [L,NE]
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] add path info postfix: C:/var/www/topaz/app/users -> C:/var/www/topaz/app/users/login
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] strip per-dir prefix: C:/var/www/topaz/app/users/login -> app/users/login
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] applying pattern '^(/.*)$' to uri 'app/users/login'
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] add path info postfix: C:/var/www/topaz/app/users -> C:/var/www/topaz/app/users/login
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] strip per-dir prefix: C:/var/www/topaz/app/users/login -> app/users/login
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] applying pattern '^x/.!dispatch/.php([^b]+)!dispatch/.php$' to uri 'app/users/login'
[topaz.local/sid#12a94f8][rid#1ee71b0/initial] [perdir C:/var/www/topaz/] pass through C:/var/www/topaz/app/users
Compared to the output via the vhost:
[topaz.local/sid#18374e8][rid#1868310/initial] init rewrite engine with requested uri /app/users/login
[topaz.local/sid#18374e8][rid#1868310/initial] applying pattern '^(/.*)$' to uri '/app/users/login'
[topaz.local/sid#18374e8][rid#1868310/initial] rewrite '/app/users/login' -> '/dispatch.php?url=/app/users/login'
[topaz.local/sid#18374e8][rid#1868310/initial] split uri=/dispatch.php?url=/app/users/login -> uri=/dispatch.php, args=url=/app/users/login
[topaz.local/sid#18374e8][rid#1868310/initial] local path result: /dispatch.php
[topaz.local/sid#18374e8][rid#1868310/initial] prefixed with document_root to C:/var/www/topaz/dispatch.php
[topaz.local/sid#18374e8][rid#1868310/initial] go-ahead with C:/var/www/topaz/dispatch.php [OK]

Mod_rewrite: Ruleset causes strange split and append in rewrite log

What causes the querystring to be split out and what
causes /news/50 to be appended in the second run (as an effect of the next flag N).
The behaviour I'm after is to rewrite /about/news/50/ to /about/news/news-item?post_id=50.
I add the N flag since I want to send the rewritten uri /about/news/news-item?post_id=50 to index.php.
I have the following rules in my .htaccess file:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule %{REQUEST_FILENAME} !-f
RewriteRule %{REQUEST_FILENAME} !-d
RewriteRule ^about/news/(\d+)/$ /about/news/news-item?post_id=$1 [QSA,N]
RewriteRule ^index\.php$ - [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [QSA,L]
</IfModule>
The log entries:
(2) init rewrite engine with requested uri /about/news/50/
(1) pass through /about/news/50/
(3) add path info postfix: /wordpress/about -> /wordpress/about/news/50/
(3) strip per-dir prefix: /wordpress/about/news/50/ -> about/news/50/
(3) applying pattern '%{REQUEST_FILENAME}' to uri 'about/news/50/'
(3) add path info postfix: /wordpress/about -> /wordpress/about/news/50/
(3) strip per-dir prefix: /wordpress/about/news/50/ -> about/news/50/
(3) applying pattern '%{REQUEST_FILENAME}' to uri 'about/news/50/'
(3) add path info postfix: /wordpress/about -> /wordpress/about/news/50/
(3) strip per-dir prefix: /wordpress/about/news/50/ -> about/news/50/
(3) applying pattern '^about/news/(\d+)/$' to uri 'about/news/50/'
(2) rewrite 'about/news/50/' -> '/about/news/news-item?post_id=50'
(3) split uri=/about/news/news-item?post_id=50 -> uri=/about/news/news-item, args=post_id=50
(3) add path info postfix: /about/news/news-item -> /about/news/news-item/news/50/
(3) applying pattern '%{REQUEST_FILENAME}' to uri '/about/news/news-item/news/50/'
(3) add path info postfix: /about/news/news-item -> /about/news/news-item/news/50/
(3) applying pattern '%{REQUEST_FILENAME}' to uri '/about/news/news-item/news/50/'
(3) add path info postfix: /about/news/news-item -> /about/news/news-item/news/50/
(3) applying pattern '^about/news/(\d+)/$' to uri '/about/news/news-item/news/50/'
(3) add path info postfix: /about/news/news-item -> /about/news/news-item/news/50/
(3) applying pattern '^index\.php$' to uri '/about/news/news-item/news/50/'
(3) add path info postfix: /about/news/news-item -> /about/news/news-item/news/50/
(3) applying pattern '.' to uri '/about/news/news-item/news/50/'
(4) RewriteCond: input='/about/news/news-item' pattern='!-f' => matched
(4) RewriteCond: input='/about/news/news-item' pattern='!-d' => matched
(2) rewrite '/about/news/news-item/news/50/' -> '/index.php'
(2) trying to replace prefix /wordpress/ with /
(1) internal redirect with /index.php [INTERNAL REDIRECT]
(2) init rewrite engine with requested uri /index.php
(1) pass through /index.php
(3) strip per-dir prefix: /wordpress/index.php -> index.php
(3) applying pattern '%{REQUEST_FILENAME}' to uri 'index.php'
(3) strip per-dir prefix: /wordpress/index.php -> index.php
(3) applying pattern '%{REQUEST_FILENAME}' to uri 'index.php'
(3) strip per-dir prefix: /wordpress/index.php -> index.php
(3) applying pattern '^about/news/(\d+)/$' to uri 'index.php'
(3) strip per-dir prefix: /wordpress/index.php -> index.php
(3) applying pattern '^index\.php$' to uri 'index.php'
(1) pass through /wordpress/index.php
Try the DPI (Discard PATH_INFO flag) if you rewrite a URL that had PATH_INFO stripped from it and don't want it added back on. It's a problem with per-directory rewrites
edit: if you do this, since wordpress counts on the PATH_INFO, you'd have to actually rewrite to /index.php/about/news/... to let the next round still see PATH_INFO.
RewriteRule ^about/news/(\d+)/$ /index.php/about/news/news-item?post_id=$1 [DPI,L]
instead of
RewriteRule ^about/news/(\d+)/$ index.php [DPI,L]

Help understanding rewrite log (want to internally rewrite a page when requested from specific HTTP_HOST)

I have a Drupal site, site.com, and our client has a campaign that they're promoting for which they've bought a new domain name, campaign.com. I'd like it so that a request for campaign.com internally rewrites to a particular page of the Drupal site. Note Drupal uses an .htaccess file in the document root.
The normal Drupal rewrite is
# Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
I added the following before the normal rewrite.
# Custom URLS (eg. microsites) go here
RewriteCond %{HTTP_HOST} =campaign.com
RewriteCond %{REQUEST_URI} =/
RewriteRule ^ index.php?q=node/22 [L]
Unfortunately it doesn't work, it just shows the homepage. Turning on the rewrite log I get this.
1. [rid#2da8ea8/initial] (3) [perdir D:/wamp/www/] strip per-dir prefix: D:/wamp/www/ ->
2. [rid#2da8ea8/initial] (3) [perdir D:/wamp/www/] applying pattern '^' to uri ''
3. [rid#2da8ea8/initial] (2) [perdir D:/wamp/www/] rewrite '' -> 'index.php?q=node/22'
4. [rid#2da8ea8/initial] (3) split uri=index.php?q=node/22 -> uri=index.php, args=q=node/22
5. [rid#2da8ea8/initial] (3) [perdir D:/wamp/www/] add per-dir prefix: index.php -> D:/wamp/www/index.php
6. [rid#2da8ea8/initial] (2) [perdir D:/wamp/www/] strip document_root prefix: D:/wamp/www/index.php -> /index.php
7. [rid#2da8ea8/initial] (1) [perdir D:/wamp/www/] internal redirect with /index.php [INTERNAL REDIRECT]
8. [rid#2da7770/initial/redir#1] (3) [perdir D:/wamp/www/] strip per-dir prefix: D:/wamp/www/index.php -> index.php
9. [rid#2da7770/initial/redir#1] (3) [perdir D:/wamp/www/] applying pattern '^' to uri 'index.php'
10.[rid#2da7770/initial/redir#1] (3) [perdir D:/wamp/www/] strip per-dir prefix: D:/wamp/www/index.php -> index.php
11.[rid#2da7770/initial/redir#1] (3) [perdir D:/wamp/www/] applying pattern '^(.*)$' to uri 'index.php'
12.[rid#2da7770/initial/redir#1] (1) [perdir D:/wamp/www/] pass through D:/wamp/www/index.php
I'm not used to mod_rewrite, so I might be missing something, but comparing the logs from a call to http://site.com/node/3 and from http://campaign.com/ I can't see any meaningful difference. Specifically uri and args on line 4 seem correct, the internal redirect on line 7 seems right, and the pass through on line 12 seems right (because the file index.php exists). But for some reason it seems the query string's been discarded/ignored around the time of the internal redirect. I'm completely stumped.
Also, if anyone could provide a reference on understanding the rewrite log, that might help. It'd be great if there's a way to track the query string through the internal redirect.
FWIW I'm using WampServer 2.1 with Apache 2.2.17.
Thanks for asking this question, it's something that I need to do too. I don't know the way to do this by means of the .htaccess, and hope that someone here can answer that.
But I do the same thing by using Drupal's menu system with this code in a custom module:
function mymodule_menu() {
$items = array();
$items['domain_redirect'] = array(
'page callback' => 'domain_redirect',
'type' => MENU_NORMAL_ITEM,
'access arguments' => array('access content'),
);
return $items;
}
function domain_redirect() {
switch ($_SERVER['SERVER_NAME'])
{
case "campaign.com":
$goto = "node/22";
break;
default:
$goto = "/";
}
drupal_goto($goto);
}
Then set the frontpage to domain_redirect.