Forum Moderators: phranque

Message Too Old, No Replies

htaccess "or" statement equivalent

or alternative solutions

         

Skier88

7:07 pm on Oct 25, 2010 (gmt 0)

10+ Year Member



Part of my main .htaccess needs to redirect to a script if the requested page exists either as a directory or a file. I can think of three ways to do this:
1) an "or" statement equivalent. I actually have this working (see below), but my solution is ugly and long ... there's got to be a better way to do this. Right?
2) a tag I don't know of. -f matches a file and -d matches a directory; is there another tag (-?) that matches either a file and a directory?
3) the reason I need this is that I want to handle errors in htaccess and not the script. So if there is some way to tell htaccess to stop if an errordocument applies (were it a rewriterule I'd just append [L]) I don't need to put any conditions on the redirect.

Here's my current code.

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^.*$ - [S=1]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.*$ - [S=1]
RewriteRule !\.[a-z]{2,4}$ _structure/page.php [NC]


The first part of the second rewriterule ("!\.[a-z]{2,4}$") is necessary for other reasons.

Any suggestions? Thanks for reading.

sublime1

7:29 pm on Oct 25, 2010 (gmt 0)

10+ Year Member



Hi --

Well, there is an [OR] flag you can put on the end of a RewriteCond -- so you can either say "if the file exists OR the directory exists", or you could say "if it's not a file and not a directory". The latter is the more common variant because it is more efficient -- any failure will stop processing.

Do this by putting multiple RewriteCond statements, one after the other.

And to add one little additional optimization, it has been asserted here that the file/directory exists check is expensive, so best avoided -- if you know that any files with an extension like gif, jpeg, png, css, js will always be served as static files, your statement could be something like this:


RewriteCond %{REQUEST_FILENAME} !\.(gif|jpe?g|png|css|js)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule !\.[a-z]{2,4}$ _structure/page.php [L]


The last line (the RewriteRule) has one little nuance: your regex looks for a 2-4 lowercase character, but the [NC] flag say "ignore case" -- this would be redundant. If you want a case-insensitive match use [a-zA-Z].

I have also added an L flag on the rewrite to prevent others from executing.

I am not sure if I have captured all of the details of your requirements. The short answer is: multiple RewriteCond statements in a row, preceding a RewriteRule will be processed and implicitly ANDed; add the [OR] flag if you have no way of checking for the negated condition.

Tom

jdMorgan

8:03 pm on Oct 25, 2010 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



That logically simplifies to:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ - [S=1]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule !\.[a-z]{2,4}$ _structure/page.php [NC,L]

But then another logic problem comes up, in that you're apparently looking for a directory that is a file that exists but does not have a file extension. So I suspect that perhaps this is not really what you intended.

Perhaps you were looking for something like this:

# Skip the next rule if the requested URL-path does not
# resolve to a physically-existing directory or file
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ - [S=1]
# Else if the requested URL-path resolves to a physically-existing directory
# or extensionless file, rewrite it to the /_structure/page.php script
RewriteRule !\.[a-z]{2,4}$ /_structure/page.php [L]

in which case, the code can be further simplified to:

# If the requested URL-path resolves to a physically-existing directory
# or extensionless file, rewrite it to the /_structure/page.php script
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule !\.[a-z]{2,4}$ /_structure/page.php [L]

So here, we're assuming that the goal is to pass requested URL-paths to the script only if they do not end with a filetype extension and only if they resolve to physically-existing files or directories (which I assume the script will open and process for some purpose).

Note that the -d and -f functions test only for physically-existing files on the disk. As a result, if my assumption about passing existing resolvable file- and directory-paths to the script is wrong, then there is no way for .htaccess to know ahead of time (for example) that /_structure/page.php will not be able to find the data it needs to produce a page, and that a 404 should therefore be generated. That type of error must be handled in the script itself.

Jim

Skier88

10:03 pm on Oct 25, 2010 (gmt 0)

10+ Year Member



Wow, thanks for the quick and helpful replies. If the other sections of this forum were like this I'd have no web design problems left.

But now I feel really stupid ... yes, [OR] is what I was looking for. Thanks for mentioning it. I do have a few questions / comments though, as both of you brought up interesting points.

Tom, how would you structure an equivalent "and" statement? The example you gave redirects only errors to the script, while I want to redirect only valid urls. The only way I can see to reverse it is with a skip rule, which decreases efficiency. eg:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ - [S=1]
RewriteRule !\.[a-z]{2,4}$ _structure/page.php


Your comment about the regex has me confused ... what is [NC] used for then? I thought it was equivalent to a regex parser's case sensitivity option, eg in php where you put an "i" after the end delimiter.

Jim, I think you must have looked at the code as I was editing it. I accidentally posted the wrong code for the first few minutes; the new code simplifies into an [OR] statement. Sorry to waste your time.

You got the intent right though - your last two examples would work great. I had actually just written the above simplification when I saw you'd already posted it, with one difference: I didn't know you could just match "^". It looks like that would be a lot more efficient than matching the entire string. Also, I think that the simpler [OR] version is still less efficient, despite the skip rule, based on Tom's comments.

As for the script, I actually don't need to build error functions into it as it is just a shell function. All it does is surround the content with a header and footer, with some other useful bits - the point is, it doesn't matter what the content is, it just displays it.

Thanks again for both of your extensive posts.