In Apache how to do an external redirect to the slashless version of a URL with a subfolder .htaccess file - apache

On Apache 2.4 I have an .htaccess (in a subfolder) which rewrites slashless requests inside that folder to appropriate index files:
DirectorySlash Off
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_FILENAME}/index.html -f
RewriteRule (.*) $1/index.html [L]
This works for the slashless version exactly as expected. Now I want to redirect the slashed version externally to the slashless version. I tried adding the lines:
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} /$
RewriteRule ^(.*)/ $1 [R=302,L]
However this does not work: The redirect is issued, however it does not go to the slashless URL, but to a URL with a system specific part injected.
So, for a sample URL http://example.com/path/to/dir/ the redirected URL looks like this http://example.com/fs9e/username/sub/public/path/to/dir instead of just http://example.com/path/to/dir.
How can I fix this? Many thanks for any pointers!
PS: The real case is a little bit more complicated because I do a subdomain-to-folder rewrite in the root .htacces, but I assume this is not relevant here.

RewriteRule ^(.*)/ $1 [R=302,L]
You are missing the slash prefix (/) on the substitution string (2nd argument) - to make the substitution root-relative. Or rather, /subfolder/ (since this .htaccess file is located in a subfolder). Since this is a relative substitution string (not starting with a slash or scheme+hostname), the directory-prefix*1 (which I assume is /fs9e/username/sub/public/path/) is added back (by default*2), resulting in a malformed redirect. (This is correct for internal rewrites, but not external redirects.)
It should be like this:
RewriteRule ^(.*)/$ /subfolder/$1 [R=302,L]
Note you were also missing the end-of-string anchor ($) on the RewriteRule pattern. (This also negates the need for the preceding condition that checks that REQUEST_URI ends in a slash.)
Note also that this "redirect" should go before the earlier "rewrite".
*1 The directory-prefix is the absolute filesystem path of the location of the .htaccess file.
*2 The alternative is to set a RewriteBase /subfolder - but that then affects all relative substitutions. You could also use an environment variable to apply a specific prefix only to some rules.

Related

mod-rewrite, how to implement a second rule?

I'm trying to get a .htaccess file RewriteRule in my web application. I need a double rule to match two URL formats that might happen:
Rule 1 (not commented below and works like a charm)
http://example.com/whatever -> /index.php?page=whatever&
http://example.com/whatever/ -> /index.php?page=whatever&
http://example.com/whatever/?test=me -> /index.php?page=whatever&test=me
Rule 2 (commented out below and does not work, but should work like this)
http://example.com/api/ -> index.php?page=api&func=
http://example.com/api/whateverelse -> index.php?page=api&func=whateverelse
The folder /api/ does not exists on the server and by reading the documentation the L parameters should prevent the parsing of more rules. Yet if I uncomment the ^\/api\/(.*)$ I get an 404 response.
The goal is that if the first rule gets parsed the second rule is omitted, and the goal is that the first rule actually works.
Is there someone that could point me in the right direction here?
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#RewriteRule ^\/api\/(.*)$ //index.php?page=api&func=$1 [NC,L]
RewriteRule ^\/?([^\/]+)\/?$ //index.php?page=$1&%{QUERY_STRING} [NC,L]
I tried to debug it by adding:
LogLevel alert rewrite:trace6
but this is not allowed in the .htaccess and I do not have direct access to the server configuration files.
#RewriteRule ^\/api\/(.*)$ //index.php?page=api&func=$1 [NC,L]
This doesn't work because in .htaccess the URL-path matched by the RewriteRule pattern does not start with a slash. You have included a slash prefix on the regex, so it never matches.
In the following rule, the slash prefix is made optional with the ? quantifier, ie. ^\/? - so it will match. (The slash prefix is only required when the directive is used in a server context. By making it "optional", it would work in either.)
If you are only using .htaccess then you can remove the slash prefix from the RewriteRule pattern altogether.
For example:
RewriteRule ^api/(.*) /index.php?page=api&func=$1 [NC,L]
Additional notes...
No need to backslash escape slashes in the regex, as they carry no special meaning. (Apache uses spaces as argument delimiters.)
Not sure what the double slash prefix on the substitution string was for? ie. //index.php. You should only have at most one slash here, or none at all for an internal rewrite (and the index.php file is in the same directory as the .htaccess file). (The double slash still "works", only because Apache is reducing instances of multiple slashes when it maps the URL to the filesystem.)
The RewriteCond directives only apply to the first RewriteRule directive that follows, so you may need to repeat these for the second rule. (Or reverse the logic and stop processing when a request does map to a file/directory? However, this might depend on what other directives you have in the file.) If you don't have physical files that start /api then you can remove the conditions from that rule.
You don't need to manually append the QUERY_STRING to the substitution. This is what the QSA (Query String Append) flag is for. The QSA flag will also avoid a stray & at the end when no query string is present on the request.
The NC flag is not required on the last rule. It may not be required on the "api" rule either, unless /API, or /ApI etc. might be required - but this should be avoided.
So, bringing this together, we have....
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^api/(.*) /index.php?page=api&func=$1 [NC,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)/?$ /index.php?page=$1 [QSA,L]

Apache rewrite: How to detect & do 301 trailing slash-redirect on my own?

Short version:
I got a request (no trailing slash)
mydomain.com/slides/sessionOne
How can I figure out in a RewriteCond if '_slides/sessionOne' exists and is a directory? If so, append a trailing / (but do not prepend a _) and redirect [R=301].
Long version:
I have a fairly empty webroot:
.htaccess
_slides (dir)
_site (dir)
and a .htaccess that almost does what I want:
RewriteEngine On
RewriteBase /
# directly accessing an _underscore-Folder? – Forbid!
RewriteRule ^_ - [R=403,END]
# AAA
RewriteRule ^slides/(.+)$ _slides/$1 [END]
# prevent _site prepend before _slides
RewriteCond %{REQUEST_URI} !^/_(site|slides)
RewriteRule ^(.*)$ _site/$1 [END]
any URL under slides/ will find it's stuff in the _slides folder
any other url will go and look inside the _site folder
direct access to both underscore-folders is prevented
Directory-URLs with trailing slash work fine, just like files: For example slides/presentation1/ will internally find _slides/presentation1/index.html w/o any visible URL change, as it should.
My trouble: Directory-URLs without trailing slash:
slides/presentation1 will first get (internal, non-visible) rewritten to _slides/presentation1
since the slash is missing, but there is a directoy of that name, Apache automatically makes a visible 301 redirect, sending me to _slides/presentation1/.
But I do not want to reveal the underscore-Folders, and this (correctly) gets caught by my check.
So, I would need at the “AAA” position is in pseudo-code:
URI has no trailing slash
AND there exists a directory either under _slides oder _site?
(peeking for it)
==> make a 301 redirect!
but no underscores prepended, just a trailing slash attached...
Here's my attempt (at the AAA position), but it's just not working:
# directory w/o trailing slash inside _slides/ ?
# ==> attach slash and 301
RewriteCond %{REQUEST_URI} [^/]$
RewriteCond _slides/%{REQUEST_FILENAME} -d [OR]
RewriteCond _site/%{REQUEST_FILENAME} -d
RewriteRule ^ %{REQUEST_URI}/ [R=301,L]
Toughie, but I found a way:
generate a (server-absolute) path to peek for a valid directory (the -d does not care, if your path has 0,1 or even multiple trailing slashes). For generation I use rewrite rules, that don't rewrite anything (“-”), but store what I need in an env var. Only do that for URLs in question, that is: w/o trailing (→[^/]).
Otherwise the env var remains empty. (one of the two, certainly will. Can't match both.)
for applicable (aka non-empty) Env-Vars, check if they are is a directory, if so, append trailing slash, do a 301. (to make the slash-append visible. Like any webservers does..., just under tougher circumstances...).
if there was no 301 (resp. that run happened before), redirect as described in the question.
Code:
RewriteEngine On
RewriteBase /
# already on _underscore-Folder? – Forbid!
RewriteRule ^_ - [R=404,L,END]
# at most one of the two will become valid
RewriteRule ^(slides/.+[^/])$ - [E=slidesPeek:%{DOCUMENT_ROOT}_$1]
RewriteRule ^(?!slides/)(.*[^/])$ - [E=sitePeek:%{DOCUMENT_ROOT}_site/$1]
RewriteCond %{ENV:slidesPeek} !^$
RewriteCond %{ENV:slidesPeek} -d
RewriteRule ^.*$ $0/ [R=301,L,END]
RewriteCond %{ENV:sitePeek} !^$
RewriteCond %{ENV:sitePeek} -d
RewriteRule ^.*$ $0/ [R=301,L,END]
RewriteRule ^slides/(.+)$ _slides/$1 [L,END]
RewriteCond %{REQUEST_URI} !^/_(site|slides)
RewriteRule ^(.*)$ _site/$1 [END]
Bonus 8-) Useful for testing and debugging:
RewriteRule ^(?!DEBUG)\w+$ /DEBUG?%{DOCUMENT_ROOT} [R=301,END]
Caveat:
Flag [L] means: stop for the rest of this .htacess-file (but possible do additional rounds from start. On 301 redirects and on internal rewrites)
Flag [END] means: No additional rounds... (except for 301, which inevitably starts the whole thing). [END] does NOT imply [L]. Without [L], rules that follow would still be applied, just no additional rounds
Something, that one won't notice on more trivial cases.
Just for reference: (mind all the slashes)
%{DOCUMENT_ROOT}
/is/htdocs/user12345678/www.mydomain.de/
%{REQUEST_FILENAME}
/is/htdocs/user12345678/www.mydomain.de/abc
%{REQUEST_URI}
uri=/abc
Preceeding slashes are a constant pain by the way: RewriteRule get's the URL without initial slash, whereas %{REQUEST_URI} has one...
I honestly no longer understand, why the seconds-last line is needed. But deletion gets me 404s...
Advice:
Browsers notoriously cache your prior 301-redirects (keep sending you there again, without checking.)
Thus either you need to keep cleaning caches all the time or you do your testing from the command line, i.e.
cd someTemporaryFolder
wget http://mywhateverurl.com 2>&1
resp.
wget http://mywhateverurl.com 2>&1 | grep Location:

I can't figure out why this RewriteCond isn't working

So I'm having trouble figuring out why my RewriteRules won't trigger. These rules are in an .htaccess file at the root directory of a subdomain of my website. I've turned on detailed logging for mod_rewrite in the VirtualHost but that isn't really helping me solve what's wrong, though the first three rules seem to be working simply by coincidence since their files exist at the requested location.
The goal of this set of rules is:
sub.domain.tld/ -> passthrough/serve actual file
sub.domain.tld/?q=test -> passthrough/serve actual file with query args intact
sub.domain.tld/.well-known/* -> passthrough/serve actual file (for letsencrypt)
sub.doamin.tld/* -> process.php?project=*
sub.domain.tld/*?q=test -> process.php?project=*&q=test while handling unlimited number of query args
And the current .htaccess is:
RewriteEngine on
#serve actual file if viewing main page or doing https renewal
RewriteCond %{REQUEST_URI} ^\?.+|\/$ [OR]
RewriteCond %{REQUEST_URI} ^\.well-known.*
RewriteRule (.*) - [L,QSA]
#redirect everything else to the processing script
RewriteCond %{REQUEST_URI} ^(\w+)
RewriteRule \/(\w+) process.php?project=$1 [NC,L,QSA]
Thank you for your help!
OK, This was actually a complex one and because most of the time, %{REQUEST_URI} tests are done using the RewriteRule itself, I got a bit confused and I'm sorry about that.
It turns out:
%{REQUEST_URI} contains the leading slash
the matching part of the RewriteRule doesn't
Also, keep in mind %{REQUEST_URI} doesn't contain the query string, as stated in the Apache manual:
REQUEST_URI
The path component of the requested URI, such as "/index.html". This notably excludes the query string which is available as its own variable named QUERY_STRING.
So, a rule like RewriteCond %{REQUEST_URI} ^\?.+ is pretty much useless as you'll never have a question mark in %{REQUEST_URI}
Also, and this probably is the most confusing part, when requesting /, %{REQUEST_URI} will contain the actual index file that has been served. So, if your DirectoryIndex is set to index.php index.html (in that order) and you have an index.html file in the root folder, {REQUEST_URI} will be index.html. If you have an index.php file, it will be index.php, but never /.
That being said, we can simply your rules to:
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/(\.well-known|index\.php$)
RewriteRule (.+) process.php?project=%{REQUEST_URI} [QSA]
Note that I added the $ inside the brackets to only match the end of string character after index\.php but not after \.well-known, so anything after \.well-known will also match.
You will need to replace index\.php with index\.html if you have an html index.
Finally, you don't need 2 rules for that. It's always better to have only one and exclude some URLs from it.
PS: you'll also notice you don't need to escape / as this is not considered as a regexp delimiter.
You just need this single rule in your .htaccess:
RewriteEngine on
# skip files, directories and anything inside .well-known/ directory
RewriteRule ^(?!index\.|process\.php|\.well-known)(.+)$ process.php?project=$1 [L,QSA,NC]

How to redirect "/" to "/home.html" only if the file "/index.html" does not physically exists?

I found a way to redirect (not load, but change the URL) "/" to "/home.html". And now I want to add a RewriteCond to avoid the redirection if the file "/index.html" exists.
I tried (without the comments), but it didn't worked :
# We check that we comes from "domain.tld/"
RewriteCond %{REQUEST_URI} =/
# We check that there is no index.html file at the site's root
RewriteCond %{REQUEST_URI}index\.html !-f
# We redirect to home.html
RewriteRule ^(.*)$ %{REQUEST_URI}home\.html [R=301,L]
Help me Obi-wan Kenobi... You're my only hope!
#Gumbo
It's a little bit more complicated than the above example. In fact, I manage both localhost and production development with the same .htaccess, so I tried something like this (following your answer) :
# Redirect domain.tld/ to domain.tld/home.html (only if domain.tld/index.html does not exists)
RewriteCond %{DOCUMENT_ROOT}index\.html !-f [OR]
RewriteCond %{DOCUMENT_ROOT}domain.tld/www/index\.html !-f
RewriteRule ^$ %{REQUEST_URI}home\.html [R=301,L]
I looked at the path returned by "%{DOCUMENT_ROOT}domain.tld/www/index.html" and it's exactly the path of my index.html file... nevertheless, it didn't worked too. :(
By the way, thanks for the "^$" astuce to avoid "%{REQUEST_URI} =/" ! \o/
Any idea why ?
The file check -f requires a valid file system path. But %{REQUEST_URI}index\.html is not a file system path but a URI path. You can either use -F instead to check the existence via a subrequest. Or use DOCUMENT_ROOT to build a valid file system path:
RewriteCond %{DOCUMENT_ROOT}/index.html !-f
RewriteRule ^$ %{REQUEST_URI}home.html [R=301,L]
Furthermore, the other condition can be accomplished with the pattern of RewriteRule. As you’re using mod_rewrite in a .htaccess file, the corresponding path prefix is stripped (in case of the document root directory: /) so that the remaining path is an empty string (matched by ^$).
if you have access to httpd.conf (apaches config file) you could set the default page in there.
Something like this:
<IfModule dir_module>
DirectoryIndex index.html home.html
</IfModule>
Based on the rule set that you posted in your update, you have a bit of a logical error going on. Right now, one of your RewriteCond conditions will always be true, since it seems likely that both index files will never exist in the same environment (one exists in development, the other in production). Since you've OR'ed them together, this means that your RewriteRule will never be ignored due to the condition block.
It's simple enough to fix (I've also added additional forward slashes, since DOCUMENT_ROOT typically doesn't have a trailing slash):
RewriteCond %{DOCUMENT_ROOT}/index.html !-f
RewriteCond %{DOCUMENT_ROOT}/domain.tld/www/index.html !-f
RewriteRule ^$ %{REQUEST_URI}home.html [R=301,L]
Note too that you could setup a virtual host with a local host name so that your development and production would be similar in terms of relative paths.

.htaccess - redirect favicon

How do I redirect all requests for favicon.ico in root directory or any subdirectory to /images/favicon.ico
Try this rule:
RewriteEngine on
RewriteRule ^favicon\.ico$ /images/favicon.ico [L]
Edit    And for favicon.ico with arbitrary path segment depth:
RewriteCond $0 !=images/favicon.ico
RewriteRule ^([^/]+/)*favicon\.ico$ /images/favicon.ico [L]
For a favicon at www.mysite.com/images/favicon.ico
the most robust method would be:
RewriteCond %{REQUEST_URI} !^/images/favicon\.ico$ [NC]
RewriteCond %{HTTP_HOST} (.+)
RewriteRule ^(.*)favicon\.(ico|gif|png|jpe?g)$ http://%1/images/favicon.ico [R=301,L,NC]
Explanation:
RewriteCond %{REQUEST_URI} !^/images/favicon\.ico [NC] :
- ensures that the redirect rule does NOT apply if the correct URI is requested (eg a 301 redirect will write the correct favicon URI to browser cache - and this line avoids processing the rule if the browser requests the correct URI)
- [NC] means it's not case sensitive
RewriteCond %{HTTP_HOST} (.+) :
- retrieves the http host name - to avoid hard coding the hostname into the RewriteRule
- this means you can copy your .htaccess file between local/test server and production server without problems (or the need to re-hardcode your new site base url into your RewriteRule)
RewriteRule ^(.*)favicon\.(ico|gif|png|jpe?g)$ http://%1/images/favicon.ico [R=301, L] :
- ^ is the start of the regex
- (.*) is a wildcard group - which means that there can be zero or any number of characters before the word favicon in the URI (ie this is the part that allows root directory or any subdirectories to be included in the URI match)
- \.(ico|gif|png|jpe?g) checks that the URI extension matches any of .ico, .gif, .png, .jpg, .jpeg
- $ is the end of the regex
- http://%1/images/favicon.ico is the redirect url - and it injects the hostname we retrieved in the previous RewriteCond. Note that the %1 is a called a RewriteCond backreference this means it is the last RewriteCond that has been met. (eg %2 would be the 2nd-last RewriteCond that to have been met)
- R=301 means it's a permanent redirect - which stores the redirect in the browser cache. Be careful when testing - you'll need to delete browser cache between code changes or the redirect won't update. Probably leave this out until you know the rule works.
- L means its the last redirect to be followed in this .htaccess file - you won't need this to get the rule working since line 1 won't be met once the browser is directed to the correct url. Without the either line 1 or L the RewriteRule will result in a permanent loop (since the redirect URL will keep satisfying the RewriteRule conditions). However, it's a good idea to add the L anyway if you have other rules following the favicon rules - since on a favicon.ico request, you can (probably) ignore any following rules.
You can test .htaccess rules at http://htaccess.mwl.be/
Final Note:
- be careful that you don't have any other RewriteRule in an .htaccess file located in any of your sub-directories.
- eg if you put this answer in your www.mysite.com/ root folder .htaccess file, a RewriteRule (.*) xxx type rule in your www.mysite.com/images/ folder can mess with the results.
RewriteEngine on
RewriteRule ^(.*)favicon\.ico /images/favicon.ico [L]
I know the question is tagged .htaccessbut, why not use a symlink?
ln -s images/favicon.ico favicon.ico
This quick rewrite should do the trick:
RewriteRule ^(.*)favicon.ico /images/favicon.ico