0

How can I make mod_rewrite redirect to a certain page or probably just throw 404 if no other rules have been satisfied? Here's what I have in my .htaccess file:

RewriteEngine on

RewriteRule ^\. / [F,QSA,L]
RewriteRule ^3rdparty(/.*)$ / [F,QSA,L]

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^((images|upload)/.+|style.css)$ $1 [L]

RewriteRule ^$ special [QSA]
RewriteRule ^(special|ready|building|feedback)/?$ $1.php [QSA,L]
RewriteRule ^(ready|building)/(\d+)/?$ show_property.php?type=$1&property_id=$2 [QSA,L]

RewriteRule . error.php?code=404 [QSA,L]

This is supposed, among other things, to send user to error.php if he tries to access anything that was not explicitly specified here (by the way, what is the proper way to throw 404?). However, instead it sends user from every page to error.php. If I remove the last rule, everything else works.

What am I doing wrong?

Septagram
  • 9,425
  • 13
  • 50
  • 81

5 Answers5

1

What is happening is that when you are doing a rewrite, you then send the user to the new URL, where these rewrite rules are then evaluated again. Eventually no other redirectoin rules will be triggered and it will get to the final rule and always redirect to the error.php page.

So you need to put some rewrite conditions in place to make this not happen.

Mike Brant
  • 70,514
  • 10
  • 99
  • 103
1

The rewrite engine loops, so you need to pasthrough successful rewrites before finally rewriting to error.php. Maybe something like:

RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_URI} !^/(special|ready|building|feedback|show_property)\.php
RewriteCond %{REQUEST_URI} !^/((images|upload)/.+|style.css)$
RewriteRule ^ error.php?code=404 [QSA,L,R=404]

Each condition makes sure the URI isn't one of the ones your other rules have rewritten to.

The R=404 will redirect to the error.php page as a "404 Not Found".


Unfortunatelly, it didn't work - it allows access to all files on the server (presumably because all conditions need to be satisfied). I tried an alternate solution:

Something else must be slipping through, eventhough when I tested your rules plus these at the end in a blank htaccess file, it seems to work. Something else you can try which is a little less nice but since you don't actually redirect the browser anywhere, it would be hidden from clients.

You have a QSA flag at the end of all your rules, you could add a unique param to the query string after you've applied a rule, then just check against that. Example:

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^((images|upload)/.+|style.css)$ $1?_ok [L,QSA]

then at the end:

RewriteCond %{QUERY_STRING} !_ok
RewriteRule ^ error.php?code=404&_ok [QSA,L,R=404]    
Jon Lin
  • 142,182
  • 29
  • 220
  • 220
  • Hm, this could solve the problem, but it requires duplicating the regexes, which is not a good thing to do. Is there another way? – Septagram Aug 21 '12 at 21:36
  • Unfortunatelly, it didn't work - it allows access to all files on the server (presumably because all conditions need to be satisfied). I tried an alternate solution: `RewriteCond %{REQUEST_URI} !^((images|upload)/.+|style.css|(special|ready|building|feedback|property).php)$` followed by `RewriteRule . - [QSA,L,R=404]`, but it didn't work either :( – Septagram Aug 22 '12 at 11:45
  • The problem with this solution is that it's possible to bypass the rewrite rules if user knows about the "_ok" bit. Not much of a weakness, but I'm just being a bit paranoid. – Septagram Aug 23 '12 at 10:02
0

In theory if none of the rules are matched (and the requested URL does not exist), it's already a 404. So I think the simplest solution is to use an ErrorDocument, then rewrite it:

RewriteEngine On

ErrorDocument 404 /404.php
RewriteRule ^404.php$ error.php?code=404 [L]

# All your other rules here...

You can do the same for any other HTTP error code.

netcoder
  • 66,435
  • 19
  • 125
  • 142
  • The point is that if none of the rules are matched, but the URL exists, I'd like to still throw 404. – Septagram Aug 21 '12 at 21:38
  • @Septagram: Ok but I need to ask... Why would you want to throw a 404 for a perfectly valid URL? – netcoder Aug 21 '12 at 21:40
  • this is to avoid accidentally creating vulnerabilities. I'd like to specify explicitly what user *can* access and assume that everything else he *cannot*. I'm relatively new to web programming and I'm not sure if that's a good or bad practice... – Septagram Aug 22 '12 at 09:02
0

The problem here is that after the mod_rewrite finishes rewriting the URL, it is resubmitted to the mod_rewrite for another pass. So, the [L] flag only makes the rule last for the current pass. As much better explained in this question, mod_rewrite starting from Apache version 2.3.9, now supports another flag - [END], that makes the current mod_rewrite pass the last one. For Apache 2.2 a number of solutions are offered, but since one of them was a bit clumsy and another didn't work, my current solution is to add another two rules that allow a specific set of files to be accessed while sending 404 for everything else:

RewriteRule ^((images|upload)/.+|style.css|(special|ready|building|feedback|property).php)$ - [QSA,L]
RewriteRule .* - [QSA,L,R=404]
Community
  • 1
  • 1
Septagram
  • 9,425
  • 13
  • 50
  • 81
-1

I think your last rule should be

RewriteRule ^(.*)$ error.php?code=404&query=$1 [QSA,L]

You could leave out the parenthesis and the $1 parameter, but maybe it's useful to know, what the user tried to achieve.

Hope, this does the trick!

ditscheri
  • 579
  • 2
  • 9