1

I've setup a .htaccess to redirect requests against the containing directory /htdocs/foo to the file bar.php.

1st of all: I know how to achieve this in general, but for reasons out of my control the .htaccess also wants to check if the URL resolves to a regular file and redirects if not.

Doesn't sound too complicated, but I can't work this out. :[

This is my .htaccess:

RewriteRule ^$ http://www.example.com/foo/bar.php

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* doh.php

These rules don't redirect www.example.com/foo to bar.php but doh.php instead.

I checked the error.log and for some reason the Rewrite Condition is matched although the file bar.php is present.

RewriteCond: input='http://www.example.com/foo/bar.php' pattern='!-f' => matched

Apparently Apache doesn't consider bar.php as regular file if a rewritten URL points to it. www.example.com/foo/bar.php works as expected.

Any ideas why?

Edit #1:

For clarification, this is the file structure:

/ (File system root)
|
+ htdocs (Document root)
  |
  + foo
    |
    + .htaccess
    + bar.php
    + doh.php

Edit #2:

I'm running Apache 2.4.

Solution:

The RewriteCond expects REQUEST_FILENAME to contain a local path. Due the missing L flag the RewriteRule sets REQUEST_FILENAME to a URL which obviously is not a local path, so the test for !-f matches.

Ultimately, attaching the L flag to the RewriteRule produces the desired behavior:

RewriteRule ^$ http://www.example.com/foo/bar.php [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* doh.php

After the URL is rewritten to http://www.example.com/foo/bar.php a new process loop is initiated, remapping the rewritten URL to the local path /htdocs/foo/bar.php which doesn't match the RewriteCond's !-f test. And /htdocs/foo/bar.php is passed through. :)

Thorsten
  • 13
  • 3
  • What version of Apache are you using? – MrWhite Jun 15 '19 at 00:09
  • @MrWhite: I'm running Apache 2.4, good sir. – Thorsten Jun 15 '19 at 09:06
  • "After the URL is rewritten to `http://www.example.com/foo/bar.php` a new process loop is initiated, remapping the rewritten URL..." - not exactly. After the URL is "rewritten" to an absolute URL, an _external redirect_ is triggered (not an _internal rewrite_). A 302 response is sent back to the browser and the browser makes an entirely new request to `http://www.example.com/foo/bar.php` - it's at the start of this 2nd (entirely separate) request that Apache maps the requested URL to the filesystem (ie. `/htdocs/foo/bar.php`). – MrWhite Jun 17 '19 at 23:11

1 Answers1

1
RewriteCond: input='/htdocs/foo/bar.php' pattern='!-f' => matched

Not sure if this has been over-exemplified, but unless there is "something else" going on, this is not as expected, given the above directives.

Since your initial (implied) redirect is missing the L flag, processing immediately continues onto the next directive. At this point I would expect to see something like the following in the logs:

RewriteCond: input='http://www.example.com/foo/bar.php' pattern='!-f' => matched

Immediately after the first RewriteRule, the REQUEST_FILENAME directive is updated as-is to the output of the last RewriteRule ie. http://www.example.com/foo/bar.php. It is not remapped to the filesystem at this stage. (Unless you had specified a relative path substitution, in which case the directory-prefix would be prepended - which appears to be what you are seeing in the logs - but that does not correspond to the directive in the question.)

So, the above condition is indeed "matched" - the "absolute URL" cannot possibly exist as a file on the filesystem.

You then get a corrupted 302 "rewrite" to doh.php. The request is internally rewritten to doh.php, but the HTTP status is set to 302 from the earlier "redirect", but there is no Location header, so there is no external redirect. (Although you seem to imply there is a "redirect" to doh.php?)

RewriteRule ^$ http://www.example.com/foo/bar.php

In order to trigger an external redirect (given the later rewrite), you need to include the L (last) flag to stop the current round of processing.

You should also be explicit and include the R flag. A 302 redirect is implied here because you have explicitly included a scheme + hostname in the RewriteRule.

For example:

RewriteRule ^$ http://www.example.com/foo/bar.php [R,L]

Aside:

These rules don't redirect www.example.com/foo

Note that if you request /foo, without a trailing slash, and /foo is a physical directory then mod_dir will first implicitly 301 redirect to /foo/ in order to "fix" the URL by appending the trailing slash.

MrWhite
  • 11,643
  • 4
  • 25
  • 40
  • 1
    Thank you for your elaborate answer! You are right about the log entry. The input for the rewrite condition is indeed `http://www.example.com/foo/bar.php`. Do you care to explain why the rewritten URL isn't mapped to the file system? Looks like this is the crucial part I'm missing. – Thorsten Jun 15 '19 at 09:24
  • Ah, now I get it: The `RewriteCond` expects `REQUEST_FILENAME` to contain a **local path**. And due the missing `L` flag the `RewriteRule` sets `REQUEST_FILENAME` to a URL which obviously is not a local path, so the test for `!-f` matches. – Thorsten Jun 15 '19 at 14:13
  • "...why the rewritten URL isn't mapped to the file system?" - The issue here is that you are "rewritting" to an _absolute URL_. AFAIK, this is only mapped to the filesystem at the start of the request. But an absolute URL here results in an external redirect (unless overridden by a later rewrite - as would is happening here) so will not ordinarily result in a second phase of rewriting. A 302 redirect response is sent to the client instead. – MrWhite Jun 17 '19 at 23:39
  • If instead, you had `RewriteRule ^$ bar.php` then it would _rewrite_ the request (no _redirect_) and the following filesystem check would be evaluated correctly. However, including the `L` flag is preferable in both cases. – MrWhite Jun 17 '19 at 23:39
  • "The `RewriteCond` expects `REQUEST_FILENAME` to contain a **local path**" - Strictly speaking it's the `-f` function that requires an absolute filesystem path in order to check the existence of a regular file. This cannot be determined from a URL. – MrWhite Jun 17 '19 at 23:44