Home > Mobile >  Send all requests to subfolder using Apache
Send all requests to subfolder using Apache

Time:08-27

I'm trying to get all requests to my website passed on to a subfolder in my webroot.

Here's my folder structure:

/webroot
    current
    releases
        release1
            index.html
            en
                index.html
        release2
            index.html
            en
                index.html
        ...
    .htaccess

The current folder is a symlink pointing to a folder in the releases folder. The idea is that when I make a new release, a new folder is created in the releases folder and the current symlink is then pointing to this new folder.

In my .htaccess I'm then trying to pass on all requests to the current symlink. So if a user requests, for example, /en the requests should be routed through the symlink and while the browser URL will look like https://example.com/en the actual request will end up at https://example.com/current/en

Now, this works perfectly as long as the request ends with a trailing slash, for example https://example.com/en/ but if I remove the trailing slash the routing works BUT the URL in the browser will be https://example.com/current/en

Here's the rewrites I'm doing in my .htaccess:

Options  FollowSymlinks -MultiViews -Indexes

DirectorySlash On

RewriteEngine on
RewriteBase /

RewriteCond %{REQUEST_URI} !^/current/
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.*)$ current/$1 [L]

And here's the logfile

[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'en'
[perdir /path/to/webroot/] RewriteCond: input='/en' pattern='!^/current/' => matched
[perdir /path/to/webroot/] RewriteCond: input='' pattern='^$' => matched
[perdir /path/to/webroot/] rewrite 'en' -> 'current/en'
[perdir /path/to/webroot/] add per-dir prefix: current/en -> /path/to/webroot/current/en
[perdir /path/to/webroot/] trying to replace prefix /path/to/webroot/ with /
strip matching prefix: /path/to/webroot/current/en -> current/en
add subst prefix: current/en -> /current/en
[perdir /path/to/webroot/] internal redirect with /current/en [INTERNAL REDIRECT]
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en -> current/en
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en'
[perdir /path/to/webroot/] RewriteCond: input='/current/en' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en/ -> current/en/
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en/'
[perdir /path/to/webroot/] RewriteCond: input='/current/en/' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en/
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en/index.php -> current/en/index.php
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en/index.php'
[perdir /path/to/webroot/] RewriteCond: input='/current/en/index.php' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en/index.php
[perdir /path/to/webroot/] strip per-dir prefix: /path/to/webroot/current/en/index.html -> current/en/index.html
[perdir /path/to/webroot/] applying pattern '^(.*)$' to uri 'current/en/index.html'
[perdir /path/to/webroot/] RewriteCond: input='/current/en/index.html' pattern='!^/current/' => not-matched
[perdir /path/to/webroot/] pass through /path/to/webroot/current/en/index.html

CodePudding user response:

but if I remove the trailing slash the routing works BUT the URL in the browser will be https://example.com/current/en

I assume you mean https://example.com/current/en/ (with a trailing slash).

This happens because mod_dir tries to "fix" (with a 301 redirect) the URL by appending a trailing slash to physical directories when omitted (this is necessary for directory indexes to work correctly). This "fix" occurs after the URL has been rewritten to the subdirectory (since the URL-path /en does not initially map to a subdirectory).

You can disable this behaviour of mod_dir, so that trailing slashes are not appended; but that gets messy (you then need to manually append the trailing slash as required) and could potentially raise security issues.

Instead, you need to make sure you are always linking to the URL with a trailing slash and manually correcting any URL that omits the trailing slash in .htaccess before mod_dir does so.

Your existing rewrite that rewrites to the /current subdirectory looks OK, although you don't necessarily need to check that the request is not already for /current/ (the first condition). (By checking that the request does not already start /current/ then you are allowing direct requests to the actual symlink'd filesystem location - although maybe that's a requirement?)

So, before your existing rewrite, add the following redirect:

# Fix any requests for directories that omit the trailing slash
RewriteCond %{DOCUMENT_ROOT}/current/$1 -d
RewriteRule ^(. [^/])$ /$1/ [R=301,L]

Given a request for /example, the above first checks whether /current/example exists as a directory (albeit a symlink'd directory). If if does then it issues a 301 redirect to append the trailing slash to the original URL, not the rewritten URL. eg. /example is redirected to /example/, which is then later rewritten to /current/example/.

Test first with 302 (temporary) redirects to avoid potential caching issues.

You will need to clear your browser cache, since the erroneous 301 (permanent) redirect by mod_dir will have been cached by the browser.


UPDATE:

it should not be possible to directly access any files or folders in the "current" folder. Currently, that's possible...

As hinted at above, you could simply remove the first condition that checks whether the REQUEST_URI does not already start with /current/. So all direct requests are unconditionally rewritten to /current/. A request for /current/en/ would therefore be rewritten to /current/current/en, resulting in a 404.

However, a user could still potentially request the filesystem location directly, bypassing the symlink entirely. eg. /releases/release1/en/.

To block both these locations from direct access you could add the following as the first rule:

# Block direct access to "/current/" and "/releases/"
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(current|releases)($|/) - [F]

Requesting /current/... or /releases/... directly would result in a "403 Forbidden". Or change the F to R=404 to serve a "404 Not Found" instead (no redirect occurs, despite the use of the R flag).

The check against the REDIRECT_STATUS environment variable (as in your original rule) ensures that the request can still be internally rewritten to these locations. (The REDIRECT_STATUS env var is empty on the initial request from the client, but set to the HTTP response status (eg. "200") after the first rewrite.)

  • Related