mod_rewrite is ignoring rules in subdirectories - apache

Wanted rewrite behaviour (internal rewrite!)
http://<subdomain>.domain.tld/<path> -> /<subdomain>/<path>
http://www.domain.tld/path/file.php -> /www/path/file.php
http://project.domain-tld/index.php -> /project/index.php
Folder structure:
/ root
.htaccess
/www www.domain.tld
index.php
/www
file.php
/foo
/bar
file.php
/project project.domain.tld
index.php
someRandomFiles
/somesubdomain somesubdomain.domain.tld
index.php
someRandomFiles
/anothersubdomain anothersubdomain.domain.tld
index.php
someRandomFiles
Full .htaccess
# Unicode
AddDefaultCharset utf-8
# Activate mod_rewrite
RewriteEngine on
RewriteBase /
# Subdomains
# Extract (required) subdomain (%1), and first path element (%3), discard port number if present (%2)
RewriteCond %{HTTP_HOST}<>%{REQUEST_URI} ^([^.]+)\.janbuschtoens\.de(:80)?<>/([^/]*) [NC]
# Rewrite only when subdomain not equal to first path element (prevents mod_rewrite recursion)
RewriteCond %1<>%3 !^(.*)<>\1$ [NC]
# Rewrite to /subdomain/path
RewriteRule ^(.*) /%1/$1 [L]
My .htaccess seems to work. You can live test it here:
http://test.janbuschtoens.de/
rewrites to /test/
http://www.janbuschtoens.de/
rewrites to /www/
But there is some strange behaviour in subdirectories. mod_rewrite seems to ignore the rule if the first directory in the requested path has the same name as the subdomain itself. For example:
http://www.domain.tld/foo/bar/file.php -> /www/foo/bar/file.php - Fine!
http://www.domain.tld/ -> /www/ - Fine!
http://www.domain.tld/www/ -> /www/ - Should be: /www/www/
http://www.domain.tld/www/www/ -> /www/www/ - Should be: /www/www/www/
For another live test:
http://test.janbuschtoens.de/ rewrites to /test/
http://test.janbuschtoens.de/test/ rewrites to /test/
It seems like the rule gets ignored.

This is the only good rule that I was able come up with, otherwise after initial rewriting (which is very easy) it goes into the loop (and that is the problem). For example: www.domain.com/www/123.png gets properly redirected into /www/www/123.png, but then goes to the next loop, where it get's redirected to /www/www/www/123.png and then again and again.
This rule ONLY gets invoked if FINAL filename DOES EXIST.
RewriteCond %{HTTP_HOST} ^(.+)\.domain\.com$
RewriteCond %{DOCUMENT_ROOT}/%1/$1 -f [OR]
RewriteCond %{DOCUMENT_ROOT}/%1/$1 -d
RewriteRule ^(.*)$ /%1/$1 [QSA,L]
For example: if you request www.domain.com/www/123.png, and file/folder WEBSITEROOT/www/www/123.png exist, then it will be rewritten, otherwise nothing.
The same here: if you request meow.domain.com/ .. but have no WEBSITEROOT/meow/ folder on your drive, it gets nowhere.
Please note, this still will not help much if you have subfolder with the same name as subdomain. For example: if you request www.domain.com it should be rewritten to WEBSITEROOT/www/ ... but if you also have WEBSITEROOT/www/www/ then (because of loop) it will be rewritten to WEBSITEROOT/www/www/ instead.
Unfortunately I have not found the way how to bypass it. If you wish -- you can try combining your rules with mine.

Related

How can I create a redirect with .htaccess to correct path instead of page acess

I am making a multilingual dynamic site that creates a virtual path per language.
So french pages go to domain.com/fr/ english domain.com/en/page domain.com/fr/some/page but in reality these pages are in the base folder and /fr/ is converted to a query string.
This is all working with the following .htaccess:
RewriteEngine on
DirectorySlash Off # Fixes the issue where a page and folder can have the same name. See https://stackoverflow.com/questions/2017748
# Return 404 if original request is /foo/bar.php
RewriteCond %{THE_REQUEST} "^[^ ]* .*?\.php[? ].*$"
RewriteRule .* - [L,R=404]
# Remove virtual language/locale component
RewriteRule ^(en|fr)/(.*)$ $2?lang=$1 [L,QSA]
RewriteRule ^(en|fr)/$ index.php?lang=$1 [L,QSA]
# Rewrite /foo/bar to /foo/bar.php
RewriteRule ^([^.?]+)$ %{REQUEST_URI}.php [L]
My problem is that some sites (Like a Linkedin post) somehow remove the trailing / in the index page automatically. So if I put a link in my post of domain.com/fr/ somehow they make the link domain.com/fr even if it shows domain.com/fr/ but that 404's as domain.com/fr dosent exist.
So how can I redirect domain.com/fr to domain.com/fr/ or localhost/mypath/fr (There's many sites in my local workstation) to localhost/mypath/fr/.
I tried something like:
RewriteRule ^(.*)/(en|fr)$ $1/$2/ [L,QSA,R=301]
RewriteRule ^(en|fr)$ $1/ [L,QSA,R=301]
But that ended up somehow adding the full real computer path in the url:
localhost/mypath/fr becomes localhost/thepathofthewebserverinmypc/mypath/fr/
I would very much appreciate some help as I have yet to find the right rule.
Thank you
RewriteRule ^(en|fr)$ $1/ [L,QSA,R=301]
You are just missing the slash prefix on the substitution string. Consequently, Apache applies the directory-prefix to the relative URL, which results in the malformed redirect.
For example:
RewriteRule ^(en|fr)$ /$1/ [L,R=301]
The substitution is now a root-relative URL path and Apache just prefixes the scheme + hostname to the external redirect. (The QSA flag is unnecessary here, since any query string is appended by default.)
This needs to go before the existing rewrites (and after the blocking rule for .php requests).
Note that the "internal rewrite" directives are correct to not have the slash prefix.
Aside:
DirectorySlash Off
Note that if you disable the directory slash, you must ensure that auto-generated directory listings (mod_autoindex) are also disabled, otherwise if a directory without a trailing slash is requested then a directory listing will be generated (exposing your file structure), even though there might be a DirectoryIndex document in that directory.
For example, include the following at the top of the .htaccess file:
# Disable auto-generated directory listings (mod_autoindex)
Options -Indexes
UPDATE:
this worked on the production server. As the site is in the server root. Would your know how can I also try and "catch" this on my localhost ? RewriteRule ^(.*)/(en|fr)$ /$1/$2/ [L,R=301] dosent catch but with only RewriteRule ^(en|fr)$ /$1/ [L,R=301] localhost/mypath/fr becomes localhost/fr/
From that I assume the .htaccess file is inside the /mypath subdirectory on your local development server.
The RewriteRule pattern (first argument) matches the URL-path relative to the location of the .htaccess file (so it does not match /mypath). You can then make use of the REQUEST_URI server variable in the substitution that contains the entire (root-relative) URL-path.
For example:
RewriteRule ^(en|fr)$ %{REQUEST_URI}/ [L,R=301]
The REQUEST_URI server variable already includes the slash prefix.
This rule would work OK on both development (in a subdirectory) and in production (root directory), so it should replace the rule above if you need to support both environments with a single .htaccess file.

htaccess not working but check successful localhost

I have a simple .htaccessfile
DirectoryIndex index.php
RewriteEngine On
RewriteRule ^v4r.info/(.*)/(.*) v4r.info/NGOplus/index.php?NGO=$1&page=$2 [L,QSA]
I tested the file in htaccess.madewithlove.com, it gives a correct result and copy&pasting the result works flawlessly. (http://localhost/v4r.info/NGOplus/index.php?NGO=action-for-woman&page=board.list.php&ff=710;;;;;&startdate=2017-11-11)
But htaccess fails on localhost with an error:
File does not exist:
/var/www/html/public_html/v4r.info/action-for-woman/board.list.php
The test URL is
localhost/v4r.info/NGOplus/index.php?NGO=action-for-woman&page=board.list.php&ff=710;;;;;&startdate=2017-11-11
htaccess is active. (rubbish line gives "internal server error")
in another directory htaccess is working fine.
apache.conf seems ok (AllowOverride All)
Added:
The htaccess file is not in the base directory but in the 1. subdirectory (v4r.info).
What works is htaccess in v4r.info/NGOplus with a symlink 'action-for-woman' to NGOplus
RewriteRule ^(.+?)/?$ index.php?page=$1 [L,QSA]
Here, apache does a «local» rewrite, i.e. just the last part of the URL (the directory name 'action-for-woman' I have to extract from $_SERVER ...)
my .htaccess file is in v4r.info directory what is not the root directory.
In that case, your rule will never match. The RewriteRule pattern matches a URL-path relative to the directory that contains the .htaccess file.
But anyhow, rewriting is not recursive afaik.
Yes, it is "recursive" in a directory context (ie. .htaccess). In that the rewrite engine "loops" repeatedly until the URL passes through unchanged, or you have explicitly set END (Apache 2.4).
Try the following instead:
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{REQUEST_URI} !index\.php$
RewriteRule ^([^/]+)/([^/]+)$ /v4r.info/NGOplus/index.php?NGO=$1&page=$2 [L,QSA]
The check against the REDIRECT_STATUS environment variable is to ensure that only direct requests are rewritten and not already rewritten requests.
However, this pattern is still far too generic as it matches any two path segments. I put the 2nd condition that checks index.php just so you can request /v4r.info/NGOplus/index.php directly (as you were doing in your tests). However, this could be avoided by making the regex more specific.

htaccess: add path parameter (country code) to URL & serve index.html

I want to add a country code 'en-gb' to an Angular application through htaccess. The folder 'en-gb' does not exist. I only want this to show up in the url & serve index.html
When I hit http://angular.wlocal/ it should change the url to http://angular.wlocal/en-gb & serve index.html from the root folder on the server (without displaying the actual index.html file in the url)
If the requested file/folder does not exist eg. http://angular.wlocal/this-does-not-exist the url should change to http://angular.wlocal/en-gb/this-does-not-exist & serve index.html from the root folder on the server (/index.html)
If a file/folder exists eg. http://angular.wlocal/vendor.js - the url should not change & the file should be served relative to the root folder on the server ( /vendor.js )
This should also apply to directories. eg. http://angular.wlocal/css/style.css should serve /css/style.css on the the server.
My .htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
#does the folder AND file exist? (ie. /index.html & css/style.css)
#yes - serve it & pop out of htaccess
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
#does the url already have a country flag set?
#no - add country flag (ie. http://angular.wlocal/ -> http://angular.wlocal/en-gb)
RewriteCond %{REQUEST_URI} !/en-gb
RewriteRule ^(.*)$ en-gb/$1
#if file folder not found, serve index.html (ie. http://angular.wlocal/en-gb/this-does-not-exist -> should serve index.html)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.html [L]
</IfModule>
My Vhosts
<VirtualHost 192.168.56.1:80>
DocumentRoot "C:\Users\Dev\Documents\angular-10\dist"
ServerName angular.wlocal
ServerAlias angular.wlocal
RemoteIPHeader X-Forwarded-For
<Directory "C:\Users\Dev\Documents\angular-10\dist">
DirectoryIndex index.html
AllowOverride ALL
Require all granted
</Directory>
</VirtualHost>
If I remove below, the url changes, But I get The requested URL /en-gb/ was not found on this server.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.html [L]
Currently, the URL does not change to /en-gb - I think it's because RewriteRule . index.html [L] removes it.
How can I serve index.html from root while preserving this /en-gb rewrite?
.... preserving this /en-gb rewrite?
If you want the URL to change then this needs to be an external "redirect", not a "rewrite".
#does the folder AND file exist? (ie. /index.html & css/style.css)
#yes - serve it & pop out of htaccess
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
This currently isn't doing anything because the same request can't map to a file and a directory. These conditions need to be OR'd. It's simpler to test REQUEST_FILENAME in this instance, rather than concatenating two server vars.
Try the following instead:
RewriteEngine On
#does the folder OR file exist? (ie. /index.html & css/style.css)
#yes - pop out of htaccess and allow Appache to serve it
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
#does the url already have a country flag set?
#no - add country flag (ie. http://angular.wlocal/ -> http://angular.wlocal/en-gb)
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule !^en-gb/ /en-gb%{REQUEST_URI} [R=302,L]
#if file folder not found, serve index.html (ie. http://angular.wlocal/en-gb/this-does-not-exist -> should serve index.html)
RewriteRule . index.html [L]
Prefixing the /en-gb... you can avoid the condition that checks that it's not already prefixed with /en-gb by performing this check in the RewriteRule pattern and using the REQUEST_URI server variable instead of the $1 backreference in the substitution string. The check against the REDIRECT_STATUS environment variable simply ensures we are processing direct requests only and not rewritten requests to index.html.
The REDIRECT_STATUS env var is empty (not set) on the initial request from the client. However, when the request is rewritten to index.html (by the rule below) then this env var is updated to 200 (as in 200 OK HTTP status). So by checking this env var is empty (ie. ^$) we can avoid the rule being processed for internal rewrites (which would result in an endless loop in this case). An alternative is to simply use the END flag (Apache 2.4+) on the following rewrite, to prevent the rewrite engine looping, but this will likely need to be added on other rewrites in the future and the Apache version hasn't actually stated in the question.
Note that this is a 302 (temporary) redirect. If this is intended to be permanent then change this to a 301 - but only once you have confirmed that it works OK. Otherwise you can end up with caching issues.
There's no need to recheck that the request does not map to a file or directory in the last rule, since the first rule already performs this check.
The <IfModule mod_rewrite.c> wrapper is not required here and should be removed
If I remove below, the url changes, But
If you remove the last rule then your angular app is never called so you will indeed get a 404. But also, simply removing that last rule should not result in the URL changing since the earlier rule was still a rewrite and there are no other "redirects" here?

Apache htaccess rewrite root and all root folders to subfolder without redirecting

Options +FollowSymLinks -MultiViews
# Turn mod_rewrite on
RewriteEngine On
RewriteBase /
RewriteRule ^$ /subdir/ [L,NC]
I want to rewrite the root domain to subfolder without changing the URL in the browser. The above code works just for the root domain but not any folders and files.
For example, I have https://example.com/ and https://example.com/subdir/.
With the above code in .htaccess file, when I go to https://example.com/ I see the contents of https://example.com/subdir/ which is good.
But when I go to https://example.com/test.txt I should see https://example.com/subdir/test.txt but I get The requested URL was not found on this server.
Same happens when I go to https://example.com/abc expecting to see contents of https://example.com/subdir/abc
Any idea?
RewriteRule ^$ /subdir/ [L,NC]
Change this to read:
RewriteRule !^subdir/ subdir%{REQUEST_URI} [L]
Any request that does not start /subdir/ is internally rewritten to /subdir/<url>. The REQUEST_URI server variable contains the full URL-path (including the slash prefix).
I removed the slash prefix from the substitution string since you have defined a RewriteBase /. (Although neither are strictly necessary here.)
UPDATE:
...when I go to example.com/s I am being redirected to example.com/subdir/s/
s is a subfolder within subdir, does that make any difference?
Ah yes, if /s is a subdirectory then mod_dir will append the trailing slash (to "fix" the URL) with an external 301 redirect. This redirect occurs after the URL has been rewritten to /subdir/s - thus exposing the /subdir subdirectory.
To handle this situation we can add another rule (a redirect) before the existing rewrite that first checks whether the request would map to a directory within the /subdir subdirectory and append a slash if it is omitted (before mod_dir would append the slash to the rewritten URL).
For example:
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{DOCUMENT_ROOT}/subdir%{REQUEST_URI} -d
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}/ [R=301,L]
This states... for any request that:
!\.\w{2,4}$ - does not contain (what looks like) a file extension of between 2 and 4 characters (assuming your directories aren't named this way)
!/$ - and does not currently end in a slash.
-d - and exists as a physical directory in the /subdir subdirectory.
THEN redirect to append the trailing slash on the original request
Whilst this probably should be a 301 (permanent) redirect, you should first test with a 302 (temporary) redirect to avoid potential caching issues.
You will need to clear your browser cache before testing, since the erroneous 301 redirect from /s to /subdir/s/ will have been cached by the browser.
A potential optimisation is to remove the filesystem check and simply assume that any request that does not contain a file extension should map to a directory. (But this depends on whether you are handling these URLs in any other way.)
Summary
Options +FollowSymLinks -MultiViews
# Turn mod_rewrite on
RewriteEngine On
# If the requested URL exists as a directory in "/subdir" then append a slash
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{DOCUMENT_ROOT}/subdir%{REQUEST_URI} -d
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}/ [R=301,L]
# Rewrite everything to "/subdir"
RewriteRule !^subdir/ subdir%{REQUEST_URI} [L]

mod_autoindex does not respect mod_rewrite rules

I have a directory structure similar to:
public_html/
example.com/
index.php
subdir/
file.jpg
I'm using shared hosting, so http://example.com maps to /public_html/ for its root, and I can't change this. I've added a mod_rewrite rule to handle this issue:
RewriteEngine On
RewriteRule ^$ example\.com/ [L]
RewriteRule (.*) example\.com/$1/ [L]
If I browse to http://example.com/subdir (without the trailing /) it will list file.jpg , but the URL for it will be http://example.com/file.jpg. The parent directory link is http://example.com/example.com/.
If I browse to http://example.com/subdir/ (with the trailing /) it will list file.jpg with the proper URL: http://example.com/subdir/file.jpg. However, the parent directory link is http://example.com/example.com/subdir/.
I'm very confused for what's going on and I'd love any help on this.
(Note that if I take off the final / in the mod_rewrite rule then going to http://example.com/subdir without the / will redirect to the http://example.com/example.com/subdir/ variant. Also, the parent directory for the listing at http://example.com/subdir/ changes to http://example.com/example.com/, which is almost correct.)
This could be caused by a disabled DirectorySlash. So try to enable it or use this rule to do the same with mod_rewrite:
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .*[^/]$ %{REQUEST_URI}/ [L,R=301]