ink
ink

Reputation: 649

RewriteCond strange behavior (file exists check)

I can't understand why redirect depends on RewriteRule (not on RewriteCond).

My .htaccess:

Options +FollowSymLinks +SymLinksIfOwnerMatch

<IfModule mod_rewrite.c>
RewriteEngine on

RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ true.txt

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ false.txt
</IfModule>

Root folder contains:

true.txt (contains 'true')
false.txt (contains 'false')
test.txt (contains 'test')

If I try to open test.txt I get true and if I try to open nonexist.txt i get true too.

Now I change my .htaccess:

...
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ $1
...

And now if I try to open test.txt I get test and if I try to open nonexist.txt i get false.

UPDATE: Thanks for answers, I understood how it works but one problem still exists. If I try to check 'if file exists' in another directory it always returns false.

/files/test.txt
/script/.htaccess
/script/false.txt
/script/true.txt

now my .htaccess looks like

RewriteCond %{REQUEST_FILENAME} .*(true|false).*$
RewriteRule .* - [S=2]

RewriteCond %{DOCUMENT_ROOT}/files/%{REQUEST_FILENAME} -f
RewriteRule ^(.*)$ true.txt [L]

RewriteCond %{DOCUMENT_ROOT}/files/%{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ false.txt [L]

I always get false. I also tried RewriteCond ../files/%{REQUEST_FILENAME} and also always get false result.

If I move test.txt in script folder then and change RewriteCond %{REQUEST_FILENAME} all works fine.

Upvotes: 1

Views: 5549

Answers (3)

ritorujon
ritorujon

Reputation: 61

Instead of using "RewriteCond %{REQUEST_FILENAME} !-f" you can try:
"RewriteCond %{THE_REQUEST} !-U", which checks the if the address exists.
Sometimes the file path and the address where the file is served are different, making the former unusable.

example:

RewriteEngine On
RewriteCond %{THE_REQUEST} !-U
RewriteRule ^(.*/media/.*)\.(gif|png|jpe?g)$ https://xyz.company.com$1.$2 [NC,L,R=301]

Upvotes: 0

poncha
poncha

Reputation: 7866

RewriteEngine on

RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_URI} !(true|false)\.txt$
RewriteRule .* true.txt [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* false.txt [L]

Note the second RewriteCond to prevent rewriting true.txt and false.txt files, and L flag on the rules to stop rules execution These are to prevent rules loop

UPDATE:

%{REQUEST_FILENAME} is full path, hence if you add it to some path, you'll get false (it will try to match this, essentially: /var/www/subfolder/var/www/filename.txt

To match a file in another folder you will need a match vs URI part...

Here's how you can do it:

RewriteEngine on
RewriteBase /

RewriteCond %{REQUEST_URI} ^/([^/]+)$
RewriteCond %{DOCUMENT_ROOT}/files/%1 -f
RewriteRule .* files/$0 [L]
  • The first condition checks if the request was to some filename in the root directory (it checks that uri starts with a /, but does not contain any more slashes
  • Note that the first condition encloses everything but the slash in the beginning with parentheses - this matched subpattern will be used later
  • The second condition ensures the file, which name is saved in subpattern %1 (matched by first condition) exists in subfolder files/ inside %{DOCUMENT_ROOT}
  • If both the rules matched, the request is rewritten to that file (via sub-request - the browser is not redirected).

Upvotes: 1

S&#225;T
S&#225;T

Reputation: 3692

It's because of the way mod_rewrite works: the user requests test.txt, mod_rewrite catches the requests and rewrites the URI to false.txt, then it makes a second pass, by sending an internal request for false.txt, which is caught and rewritten to true.txt. Then a third pass is made, the request is caught and rewritten to true.txt, but since the URI stays the same, no more passes are made.

It's rather counter-intuitive, but there's logic to it. Here's the control flow diagram from the docs:

control flow diagram of mod_rewrite

The [L] flag is often advertised as a magic bullet to stop the recursion, but in fact it just ensures that once a request matches a pattern, then the execution stops and no further processing will take place in that pass, but the internal request will be sent out anyhow, so a second pass is made through the same ruleset. The execution stops only if the URI is unchanged after a pass.

re: update Your problem is, the REQUEST_FILENAME environmental variable actually holds a path (by default the full filesystem path, but there are a few twists to that), so %{DOCUMENT_ROOT}/files/%{REQUEST_FILENAME} ends up being something horrible.

As for a solution... well, it's tricky, I think. It'd be a lot easier if the .htaccess were in root. The only solution I can think of right now is:

RewriteEngine on

RewriteCond %{REQUEST_URI} script/(.*)$
RewriteCond %{DOCUMENT_ROOT}/files/%1 -f
RewriteRule .* true.txt [L]

RewriteCond %{REQUEST_URI} !(true.txt)|(false.txt)
RewriteRule .* false.txt [L]

It's rather ugly, and not very scalable or portable. In the first condition I get the file's name, in the second I check if it exists, and if it does, it's true. Everything else is false. Then again, if the files directory is also in the scope of the .htaccess, it's easier and nicer by magnitudes.

Upvotes: 7

Related Questions