Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

With the below rewrites in htaccess, as expected, this HTTPS request is not rewritten:

https://example.com/system/anything

is not re-written to

http://example.com/system/anything

But, unexpectedly, this HTTPS request is rewritten:

https://example.com/preview/anything

is re-written to

http://example.com/index.php/preview/anything

Why is this?

A couple other facts / observations:

/system/ is an actual path on the server. But /preview/ is not an actual path--it's a URL segment that makes sense in the CMS, e.g., /index.php/preview/anything is how the CMS gets the request for the /preview/anything URL.

Other non-system URLs do rewrite properly (from HTTPS to HTTP), and properly do get passed to index.php. E.g.,

https://example.com/real

is re-written to

http://example.com/real

Here is the full block of rules:

 <IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteBase /

 # Force HTTPS for System URLs
 RewriteCond %{REQUEST_URI} ^/system(.*)$ [NC]
 RewriteCond %{HTTPS} !=on
 RewriteRule ^(.*)$ "https://example.com/$1" [R=301,L]

 # Force HTTP for Other URLs, but not: system or preview
 RewriteCond %{REQUEST_URI} !^/(system|preview)/(.*)$ [NC]
 RewriteCond %{HTTPS} =on
 RewriteRule ^(.*)$ "http://example.com/$1" [R=302,L]

 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule ^(.*)$ /index.php/$1 [L]
 </IfModule>

Any insights into why /preview/ gets the weird treatment?


Added: Note that /preview/anything is getting a 302 redirect to /index.php/preview/anything -- and THAT'S a big part of what seems so weird / unexpected. It should not be getting a redirect in the final rule, just a rewrite.

share|improve this question
    
Just found a similar, also unanswered, question: serverfault.com/questions/128554/… –  Jay F Dec 12 '13 at 4:52
add comment

4 Answers

up vote 1 down vote accepted
+50

Are these rewrite rules in an .htaccess file? In this case the [L] flag does not do what you think it does — it stops processing of the current ruleset, but then the request is processed by Apache again, using .htaccess files appropriate for the rewritten URI, therefore your rules may be executed again. This does not happen for rules which are in the Apache configuration file (and not inside a <Directory> section) — in this case the [L] flag is handled as expected.

For your example, https://example.com/preview/anything is internally rewritten into https://example.com/index.php/preview/anything by the third rule; however, in order to process that request, Apache must read the .htaccess file again — and this time the URI matches your second rule, which returns a 302 redirect.

Apache 2.4.x supports the [END] flag which stops such rewrite loops, unlike [L]; solutions for earlier Apache versions are more complex.

If you want to make sure that Apache makes only a single pass over your rewrite rules, you may add the following rule before all others:

RewriteCond %{ENV:REDIRECT_STATUS} !=""
RewriteRule ^ - [L]

On the first pass REDIRECT_STATUS will be empty; on the second pass it will have a non-empty value (usually 200), and the rule will match and really stop further rewrites.

In case such a rule is not appropriate (e.g., in some cases you need to handle rewritten URIs on the second pass), you can set an environment variable in rules which should be really final:

RewriteRule ^(.*)$ /index.php/$1 [L,E=FINISH:1]

and add the following rule before all others:

RewriteCond %{ENV:REDIRECT_FINISH} !=""
RewriteRule ^ - [L]

Note that during the second pass Apache prepends REDIRECT_ to environment variable names which were defined during the first pass, therefore you need to set FINISH, but test REDIRECT_FINISH.

Alternatively, you may try to modify your matching conditions so that on the second pass URIs modified on the first pass will not be matched (e.g., insert (index\.php/)? into the regexp in the second rule).

share|improve this answer
    
Yes, the L|Last flag means stop processing rules, but as soon as the result of the rule is an alias (and the third rule makes an alias) this alias becomes a new entry for the rewrite engine, and all rules are applied on this new entry. –  regilero Dec 15 '13 at 10:20
    
Yes! I totally see it now--and, it actually seems obvious, in retrospect. While I knew that the results of the 301/302 redirects would be run through the rewrite engine again, I didn't fully grasp that the result of the alias (third rule) would be as well. I had already fixed this in production by blocking index\.php/preview in the second rule, and it's great to now know about the options of using REDIRECT_STATUS or combo of E=FINISH and REDIRECT_FINISH. Thanks! –  Jay F Dec 16 '13 at 2:51
add comment

This sounds like the last rule is introducing an absolute http-based redirect when it's being done, perhaps due to some bug or feature in Apache or mod_rewrite.

How about you break it down into two rules with an extra condition?

Try this:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTPS} =off
RewriteRule ^(.*)$ http://example.com/index.php/$1 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTPS} =on
RewriteRule ^(.*)$ https://example.com/index.php/$1 [L]
share|improve this answer
add comment

Is the http scheme (instead of https) that bothers you? If so, you should look at the RewriteBase directive.

From the mod_rewrite docs:

The RewriteBase directive specifies the URL prefix to be used for per-directory (htaccess) RewriteRule directives that substitute a relative path.

This directive is required when you use a relative path in a substitution in per-directory (htaccess) context (...)

share|improve this answer
    
Thanks, but no. RewriteBase just has to do with the path, right? It doesn't recognize the protocol / scheme. –  Jay F Dec 13 '13 at 19:10
add comment

I don't think you need the '(.*)$' at the end of the RewriteCond statements, because you're not using the captured data anywhere. You could simplify those 2 like so:

RewriteCond %{REQUEST_URI} ^/system [NC]

and RewriteCond %{REQUEST_URI} !^/(system|preview)/ [NC]

What I would also recommend is turning on RewriteLog and setting RewriteLogLevel to see exactly what apache is doing per request. With apache 2.2 or less, it would be:

RewriteLog /var/log/apache2/rewrite.log
RewriteLogLevel 7

You should see step by step exactly what matches and what action apache took.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.