7

I have a weird scenario with this rewrite rule:

RewriteCond img/$2/$3/$4/$1 -f
RewriteRule ^img/(([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})[a-z0-9]{28}\.\w+)$ img/$2/$3/$4/$1 [L]

The directory structure for this is (simplified):

var/
  images/
    ..
  www/
    .htaccess
    img -> /var/images

In other words, images are stored outside the webroot in a general data-storage area and are linked into the webroot's img directory via a symbolic link. Image names are hashes. For efficiency, they're stored in a three-level deep directory hierarchy. For example:

0a808e34edaaeeffd973e4138789a4957d6b6a26.jpg

is stored at

images/0a80/8e34/edaa/0a808e34edaaeeffd973e4138789a4957d6b6a26.jpg

The rewrite rule simply rewrites the file name into the nested directory structure.

Now, the weird thing is that the RewriteCond works fine on my local systems but fails on a test server. The Apache debug log explicitly says that the "-f pattern doesn't match" for this condition. If I simply remove this condition the rewrite rule works fine and the image is served.

What could cause -f to fail for files which clearly exist?

FollowSymLinks is allowed. Apache version 2.2.22-6ubuntu2 installed via apt and hardly modified. Works on a local install of Apache 2.2.23 (via homebrew). Couldn't see any significant changes between the two versions that should cause this.


Some more possibly significant details about the actual directory structure:

$ ls -l /var/www/myapp/current
[snip] /var/www/myapp/current -> /var/www/myapp/releases/20130418090750

$ ls -la /var/www/myapp/current/webroot
[snip]
[snip] .htaccess
[snip] img -> /usr/local/var/myapp/images

The Apache webroot is configured as:

DocumentRoot /var/www/myapp/current/webroot

If I write the RewriteCond as:

RewriteCond /usr/local/var/myapp/images/$2/$3/$4/$1 -f

it works. I would prefer not to hardcode absolute paths though if at all avoidable.

May Apache be confused by the several levels of symlinks?

deceze
  • 483
  • 1
  • 6
  • 20
  • What about MultiViews? – Felipe Alameda A Apr 12 '13 at 19:00
  • MultiViews are enabled. Could that be the cause? How so? – deceze Apr 13 '13 at 00:26
  • Interesting. I'll check if that makes a difference. – deceze Apr 13 '13 at 05:37
  • Nope, sorry to report that it didn't change a thing. – deceze Apr 15 '13 at 00:41
  • It was worth trying. I think the regex should be: `^img/([a-z0-9]{4})/([a-z0-9]{4})/([a-z0-9]{4})/([a-z0-9]{40})\.(\w+)`. Slashes are missing and the hash has 40 characters, not 28 (Decimal, not hex). Additionally, grouping the groups is not necessary and a 5th group with the extension was missing. I guess is needed in the condition, like this: `/img/$2/$3/$4/$1.$5 -f`. – Felipe Alameda A Apr 15 '13 at 08:14
  • Nope, sorry, you're misunderstanding the regex. It rewrites `img/abcdef...0.jpg` into `img/abcd/ef../../abcdef...0.jpg`. And that works just fine. Only the `RewriteCond` does not seem to want to `FollowSymLinks`, even though the `RewriteRule` will. – deceze Apr 15 '13 at 08:24
  • Got it. The path is assembled in the substitution URL. But the condition shouldn't have the extension? It is a file what it is supposed to look for, not a directory.? – Felipe Alameda A Apr 15 '13 at 08:29
  • It *has* the extension! `$1` contains the whole filename, including extension. – deceze Apr 15 '13 at 08:37
  • Perfect. Had it all backwards, sorry. Nice way to make the path with the same string, though. But, the question remains. Hope somebody can help. – Felipe Alameda A Apr 15 '13 at 09:33
  • Your question isn't clear.. –  Apr 15 '13 at 14:12
  • 1
    @Servant What isn't clear, please? The `RewriteCond` `-f` rule doesn't see the rewritten file, even though the `RewriteRule` does. The question is *why?* – deceze Apr 15 '13 at 23:13
  • @deceze Lets talk in my room: http://chat.stackoverflow.com/rooms/28210/servant –  Apr 16 '13 at 08:08
  • I am still curious about this problem. Have you tried `RewriteCond img/$2/$3/$4/$1 -f [OR]` `RewriteCond img/$2/$3/$4/$1 -l` (`-l` is lowercase L)? You mention it is a symbolic link, maybe that condition is missing. – Felipe Alameda A Apr 18 '13 at 04:37
  • @faa I'll give that a go, probably together with `-F`... – deceze Apr 18 '13 at 04:41
  • @faa Unfortunately that didn't work either. I added some more details to the question though. – deceze Apr 19 '13 at 01:14

3 Answers3

1

Actually, I've never seen yet an article that have a tutorial about RewriteCond to get a variable from the RewriteRule below it.. However, you can still try to add a RewriteBase directive on the top of your .htaccess file:

RewriteBase /www/

Or this, just give both a try:

RewriteBase /var/www/

Or you can try one of these sets of directives below but EXCLUDING any RewriteBase directive that listed above:

RewriteCond /www/img/$2/$3/$4/$1 -f
RewriteRule ^www/img/(([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})[a-z0-9]{28}\.\w+)$ /www/img/$2/$3/$4/$1 [L]

Or you can even give this a try:

RewriteCond /var/www/img/$2/$3/$4/$1 -f
RewriteRule ^var/www/img/(([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})[a-z0-9]{28}\.\w+)$ /var/www/img/$2/$3/$4/$1 [L]

I'm sorry for copying the format of your rewrite condition and rule, because you said that “it works on your local development system.” But this is the simply rule that I recommend to you if you just want to rewrite /$var1.$var2 into /images/0a80/8e34/edaa/$var1.$var2, you can even add the others statically, one by one:

RewriteRule ^([a-zA-Z0-9_-]+).([a-zA-Z]{3})$ /images/0a80/8e34/edaa/$var1.$var2

Or you can even try this if you want to rewrite /img/0a808e34edaaeeffd973e4138789a4957d6b6a26.jpg dynamically into /img/0a80/8e34/edaa/0a808e34edaaeeffd973e4138789a4957d6b6a26.jpg with condition if the dynamic URL where it remapping is a file:

RewriteCond /img/$2/$3/$4/$1\.$5 -f
RewriteRule ^img/(([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})[a-z0-9]{28})\.([a-z]{3})$ /img/$2/$3/$4/$1.$5

Remember that the characters on "0a80", "8e34" and "edaa" must be exact as 4 and the chars. in "eeffd973e4138789a4957d6b6a26" must be as 28 and in "jpg" must be as 3. I hope that this will work, but I doubt..

  • 1
    `RewriteBase` is for specifying a *URL*, this doesn't really apply in this case. Using the absolute path in the `RewriteCond` does work, but to avoid having system-specific .htaccess files I'd like to avoid that. I'm also simply interested in an explanation for why it doesn't work, since I don't really need the `RewriteCond` at all. – deceze Apr 19 '13 at 01:17
1

If it works on localhost, but not test.com, I would guess its a problem with your filesystem or it is related to more general site configuration that you may be taking for granted. Did you install both operating systems, and then install the same packages, using the same procedure on both machines? What did each of the default configurations look like that shipped with each Apache? What about packages that modified default configuration before you even got a look?

Maybe your apache versions are near similar, but if they were from different sources, default configuration may have been different and may have been subsequently modified differently. Verify your configuration starting from the top. What is different? Trying making them look exactly the same (not just your rewrites, but Virtual-Host or Server contexts.)

The context of your Rewrite directives would likely help (as I see this being an issue of scope). Are the rewrites in the VirtualHost, Document-Root, Directory context?

The biggest difference I see from your posts between success and failure is this:

Relative

RewriteCond img/$2/$3/$4/$1 -f

Absolute

RewriteCond /usr/local/var/myapp/images/$2/$3/$4/$1 -f

Apache is capable of finding the file, so it shouldn't be permissions or any such. Are you using any Alias directives (localhost vs test.com)? If not, maybe you should try to Alias this specifically:

/var/www/myapp/current -> /var/www/myapp/releases/20130418090750

Put at least one hard path in your root configuration that doesn't require a symlink. You can wrap up your rewrites in a Directory context...

<Directory /var/www/myapp/current/webroot>
    rewrite statements...
</Directory>

What happens if you maintain your broken configuration on test.com, but where you currently have symlinks, try copying actual directories (temporarily) starting at the root. Just verify there is not some loose Symlink setting floating. Get rid of as many symlinks as possible and put them back in 1 by 1.

user2097818
  • 111
  • 2
0

The backreferences are "up" (rewriteCond) to "down" (RewriteRule). In the example you publish is like "down" to "up". And, the backreferences are with % not with $.

RewriteCond backreferences: These are backreferences of the form %N (0 <= N <= 9). %1 to %9 provide access to the grouped parts (again, in parentheses) of the pattern, from the last matched RewriteCond in the current set of conditions. %0 provides access to the whole string matched by that pattern.

You could try this:

RewriteBase /
RewriteCond %{REQUEST_URI} /img/([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{4})([a-z0-9]{28}\.\w+)$
RewriteCond %{DOCUMENT_ROOT}/test/%1/%2/%3/%4 -f
RewriteRule .* img/%1/%2/%3/%4 [L]

First the regular expression to find the variables. The path to the file containing the variables is constructed and verified if the file exists

I use the %{DOCUMENT_ROOT} for test on my local server. If you don't has this image files in the same DocumentRoot you need the FULL PATH like:

RewriteCond /var/www/cache_images/test/%1/%2/%3/%4 -f
  • 1
    Rewrites are evaluated in the order *RewriteRule match, RewriteCond match, RewriteRule rewrite*. So "down to up" works. RewriteRule back references are referred to by `$n` and RewriteCond back references are referred to by `%n`. It actually works. – deceze Feb 01 '14 at 19:56
  • 1
    You're right. You could edit your question to place the final version of your settings, so the solution would be clear. I'll edit my answer to clear the error (up and down), and in any case, my example also works :) – Gabriel Pérez S. Feb 01 '14 at 20:12
  • Using `%{DOCUMENT_ROOT}` to get an absolute path that's environment-independent was helpful. Combining that with using the `$N` matches from the `RewriteRule`, as OP did, makes a concise solution. – Walf Aug 22 '23 at 06:09