2

I've noticed an issue with [OR] in htaccess that I don't understand. What I want to do is redirect every non-existent file and every file that is a .php file (and not /index.php) to /404/. By my understanding following two blocks should do the same thing:

# .htaccess 1
RewriteEngine on

# cond 1
RewriteCond %{REQUEST_FILENAME} !-f [OR]
# cond 2
RewriteCond %{REQUEST_FILENAME} !index\.php$
# cond 3
RewriteCond %{REQUEST_URI} \.php$
# rule 1
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]

# rule 2
RewriteRule ^(([^/\.]+)/?)*?$ /index.php [L]

and this:

# .htaccess 2
RewriteEngine on

# cond 1
RewriteCond %{REQUEST_FILENAME} !-f
# rule 1
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]

# cond 2
RewriteCond %{REQUEST_FILENAME} !index\.php$
# cond 3
RewriteCond %{REQUEST_URI} \.php$
# rule 2
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]

# rule 3
RewriteRule ^(([^/\.]+)/?)*?$ /index.php [L]

Yet, only the second block does what I want. The first .htaccess, for example - fails to redirect a non-existent file a.pdf and instead displays default server message about file not being found.

Can somebody please explain this to me?

eithed
  • 3,933
  • 6
  • 40
  • 60
  • For any URL with non-existent file, and, for example: `/a.pdf` – eithed Dec 01 '15 at 16:41
  • That's the issue - in my understanding both of these blocks should do the same. The behaviour however is correct in the second block – eithed Dec 01 '15 at 16:48
  • Please create index.php file with "A" in it. Use first .htaccess. For `/a.pdf` URL you'll get default "Not Found" server response. Use second .htaccess. You'll get "A" as `/a.pdf` will resolve to `/404/` which will get resolved to `/index.php`. Now - why are the behaviours not the same? – eithed Dec 01 '15 at 17:44
  • Because there are other rules in place and I want non-existent files and every php file to follow a known behavior which is already established. – eithed Dec 02 '15 at 08:10
  • Why? I'm providing the code to replicate the issue. All I'm asking is explanation why the behaviours are not the same. – eithed Dec 02 '15 at 09:00
  • I expect the behaviours to be the same and they're not - I'm asking why. If this is unclear after so many comments, then I'm truly at loss. – eithed Dec 02 '15 at 09:56

1 Answers1

2

Your OR conditional in the first .htaccess is in the wrong order.

The [OR] flag creates a group around the two statements, effectively writing your conditional as:

if (
    (FILE_NOT_FOUND or FILE_NAME === 'index.php')
    and REQUEST_URI ends in .php
) { ... }

Written in this order, the conditional will never match a URI that ends in an extension other than PHP.

What you actually want is:

if (
    (FILE_NOT_FOUND or REQUEST_URI ends in .php)
    and FILE_NAME !== 'index.php'
) { ... }

Which can be expressed by simply rearranging the two related rewrite conditions:

RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_FILENAME} \.php$
RewriteCond %{REQUEST_URI} !index\.php
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]

You can even catch your mistake by reading your own words (simplified to show the actual conditional you had in mind):

What I want to do is redirect every non-existent file [or] every .php file, [and] not /index.php to /404/.

Here is a good StackOverflow question/answer about the precedence of [OR] in .htaccess files if you'd like to learn more about how mod_rewrite actually works internally.

Community
  • 1
  • 1
Aken Roberts
  • 13,012
  • 3
  • 34
  • 40
  • Thank you very much - I've looked at that answer before, but I guess it must have been late, or I was tired and the precedence of [OR] didn't register properly in my brain (and looking at it from standard language precedence point of view I was at loss on what's going on). – eithed Dec 04 '15 at 19:06