mod_rewrite: redirect `x -> /legacy/x` if file _does not exist_ in `/` but _does exist_ in `/legacy/`

Viewed 46

For over 10 years I uploaded all sorts of files to the root of my webserver.

/
  oldphoto.jpg
  oldjunk.txt
  oldfolder/
    oldfile.txt
  newpage.html
  newimg.png

There's now ~1800 files in the root. My FTP client is slow to retrieve directory listing, so managing the website is difficult.

I'd like to tidy this up. I know which files I want to be stored at root-level (new stuff). Everything else, I've moved into a legacy/ folder.

/
  legacy/
    oldphoto.jpg
    oldjunk.txt
    oldfolder/
      oldfile.txt
  newpage.html
  newimg.png

Problematically: some of those old files are still accessed today, from various external websites.

I want to make a cunning mod_rewrite rule that works like this:

file didn't exist?
okay, does it exist in the legacy/ folder?
then, I'll redirect you to the appropriate file in the legacy/ folder

So the following cases need to work:

#[A] file does not exist in /, exists in legacy folder: redirect
http://example.com/oldphoto.jpg -> http://example.com/legacy/oldphoto.jpg

#[B] file does not exist in /, exists in legacy folder: redirect
http://example.com/oldfolder/oldfile.txt -> http://example.com/legacy/oldfolder/oldfile.txt

#[C] file does not exist in /, does not exist in legacy folder: 404 as usual
http://example.com/not-exist.txt -> 404

#[D] file exists in /: serve page as usual
http://example.com/newpage.html -> http://example.com/newpage.html

I got pretty close to getting this working:

RewriteEngine on

# No such file exists:
RewriteCond %{SCRIPT_FILENAME} !-f
# No such directory exists:
RewriteCond %{SCRIPT_FILENAME} !-d

# Capture the head of REQUEST_URI into %2 backreference; this tells us the absolute path to our web root
RewriteCond %{REQUEST_URI}::%{SCRIPT_FILENAME} ^(.*?)::(.*)\1$

# File exists web_root/legacy/REQUEST_URI OR
RewriteCond %2/legacy/%{REQUEST_URI} -f [OR]
# Directory exists web_root/legacy/REQUEST_URI OR
RewriteCond %2/legacy/%{REQUEST_URI} -d

# Redirect to /legacy/REQUEST_URI
RewriteRule .* /legacy/%{REQUEST_URI} [L,R=301]

But this only solves cases A,C, and D. The nested case (B) fails, because %{SCRIPT_FILENAME} is not what I thought it was.

I am testing the redirect like so:

curl -sI 'http://example.com/oldphoto.jpg' | grep Location | sed 's/^Location: //'
http://example.com/legacy//oldphoto.jpg

Here is what the macros expand to:

# when requesting 'http://example.com/oldphoto.jpg':
SCRIPT_FILENAME:  /customer/homepages/13/c12345678/htdocs/user/oldphoto.jpg
REQUEST_FILENAME: /customer/homepages/13/c12345678/htdocs/user/oldphoto.jpg
DOCUMENT_ROOT:    /var/www/html
REQUEST_URI:      /oldphoto.jpg
THE_REQUEST: HEAD /oldphoto.jpg HTTP/1.1

# when requesting 'http://example.com/oldfolder/oldfile.txt':
SCRIPT_FILENAME:  /customer/homepages/13/c12345678/htdocs/user/oldfolder
REQUEST_FILENAME: /customer/homepages/13/c12345678/htdocs/user/oldfolder
DOCUMENT_ROOT:    /var/www/html
REQUEST_URI:      /oldfolder/oldfile.txt
THE_REQUEST: HEAD /oldfolder/oldfile.txt HTTP/1.1

We can see that %{SCRIPT_FILENAME} does not give the full path to my file.
We can also see that %{DOCUMENT_ROOT} cannot be relied upon to give us the absolute path to my web root.

If somebody requests http://example.com/oldfolder/oldfile.txt:

  • how do I check for the existence of /customer/homepages/13/c12345678/htdocs/user/legacy/oldfolder/oldfile.txt?
  • how do I redirect the user to http://example.com/legacy/oldfolder/oldfile.txt?

I assume that /customer/homepages/13/c12345678/htdocs/user/ is likely to change (I have managed web hosting), so I would prefer not to hard-code it.
I am surprised that %{DOCUMENT_ROOT} does not give me this. Maybe it gives logical web root instead of physical web root.

I am also surprised that %{SCRIPT_FILENAME} gives me …/oldfolder rather than the path suggested in %{REQUEST_URI}: …/oldfolder/oldfile.txt.

1 Answers
Related