.htaccess DirectorySlash Off causes "/" to be inaccessible - apache

I am currently using the following code in my .htaccess:
I have (A|B) because I have A.php, B.php, /A, and /B so I want the URL to redirect to A.php when /A is called.
RewriteEngine On
DirectorySlash Off
RewriteCond %{REQUEST_FILENAME} (A|B)/$
RewriteRule ^(.*)/$ $1
RewriteCond %{REQUEST_FILENAME}\.php -f
RewriteRule ^(.*)$ $1.php
However, with
DirectorySlash Off
I cannot access my root directory without a trailing slash. For e.g
If I have the files under directory /site, I can access /site/, /site/index, and /site/index.php but I cannot access using /site as it gives me a 403 error. I have seen on other SO questions that with DirectorySlash Off it will skip /site.
Is there a way to write a rule such that it applies to all files/directories apart from the root /site?
If not is there a way to remove trailing slashes for URLs where a .php file is involved?
I am asking this because when DirectorySlash is On, I may try /site/A?id=824 and the URL will become /site/A/?id=824.

It's probably because you have autoindexes turned off, so if you try to access a directory without the trailing slash, it tries to list the contents of the directory and returns a 403 instead because it's not allowed to auto index.
Turning directory slash off means if someone goes to /site without the trailing slash, you get a list of contents of the /site directory, and not the index file (e.g. /site/index.php). This is why Directory Slash exists.
Instead of stripping off the slash, you'll have to check for the slash and remove it at the same time you're checking for the php file:
RewriteCond %{REQUEST_URI} ^/(.*?)/?$
RewriteCond %{DOCUMENT_ROOT}/%1.php -f
RewriteRule ^ /%1.php [L]
And don't turn off DirectorySlash.

Related

Remove trailing slash after directory with htaccess

I want to remove / when I want to get access to the index file in a subdirectory folder. For example: www.example.com/test/dashboard/ to www.example.com/test/dashboard.
I tried this:
RewriteEngine On
# Remove "/" to "/dashboard"
RewriteCond %{REQUEST_URI} !index.php
RewriteCond %{QUERY_STRING} !^$
RewriteCond %{QUERY_STRING} ^(.*)$
RewriteRule (.*) $1%1/ [L]
It will not remove the / from the subdirectory.
Can you please show me an example of how I can remove the / with .htaccess when I want to get access to my subdirectory?
# Remove "/" to "/dashboard"
RewriteCond %{REQUEST_URI} !index.php
RewriteCond %{QUERY_STRING} !^$
RewriteCond %{QUERY_STRING} ^(.*)$
RewriteRule (.*) $1%1/ [L]
This doesn't "remove" anything. In fact, it will append a trailing slash to the end of the URL-path and query string, which seems a bit random?
However, you can't simply remove the trailing slash that occurs after a physical directory in the URL-path, since mod_dir will try to append it with a 301 redirect in order to "fix" the URL.
You can prevent mod_dir from appending the trailing slash with the DirectorySlash Off directive. However, you then need to manually append the trailing slash to the directory with an internal rewrite in order to correctly serve the "index file" (ie. the DirectoryIndex document).
I'm assuming you are linking to the directory without a trailing slash in your internal links.
Try the following instead:
# Disable directory listings (mod_autoindex)
Options -Indexes
# Prevent mod_dir appending trailing slash to directories
DirectorySlash Off
RewriteEngine On
# Rewrite the URL to append a trailing slash to directories (internally)
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule (.*[^/])$ $1/ [L]
A request for /dashboard (no trailing slash) that maps to a physical directory will be internally rewritten to /dashboard/, which will allow the "index file" to be served (by mod_dir also).
For security reasons, you need to ensure that directory listings (mod_autoindex) are disabled, otherwise, directory listings could potentially be generated for directories even when they contain a directory index document. See the security warning in the Apache docs under the DirectorySlash directive.
You need to ensure that your browser cache is cleared before testing since the 301 (permanent) redirect by mod_dir (to append the trailing slash) will have certainly been cached by the browser.
Remove the trailing slash (optional)
You could implement a canonical redirect to actually "remove" the trailing slash from the URL, should there be any requests from third parties (or search engines) that include the trailing slash. (It should already be removed on all your internal links, so this is not required to make your site "work", however, it could be required for SEO to avoid potential duplicate content.)
I'm assuming you don't want the trailing slash on any URL.
You should add the following "redirect" before the rewrite above, immediately after the RewriteEngine directive.
# Remove the trailing slash, should it appear on any 3rd party requests
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule (.+)/$ /$1 [R=301,L]
The check against the REDIRECT_STATUS environment variable is to ensure we do not redirect the already written request (that appends the trailing slash) by the later rewrite, during the second pass of the rewrite engine. Alternatively, you could use the END flag (Apache 2.4) on the later rewrite.

.htaccess removing file extension is conflicting with folders of the same name

I'm removing file extensions (like .html) from the url with a .htaccess file. The code in the file is working fine, but as soon as there is a folder with the same name as the file without extension, it redirects to the folder instead of redirecting to the file. For example, if I have a demo.html file and a demo folder in the same directory, as soon as I type in the searchbar of the browser www.example.com/demo, it redirects to the folder, instead of the file. If I delete the folder and I type the same thing again, it works perfectly! Any help would be appreciated :)
Here's the code in the .htaccess file:
RewriteCond %{THE_REQUEST} /([^.]+)\.html [NC]
RewriteRule ^ /%1 [NC,L,R]
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^ %{REQUEST_URI}.html [NC,L]
This is caused by a conflict with mod_dir. When you request a directory without a trailing slash, mod_dir will "fix" the URL and append a trailing slash with a 301 redirect. After which it will attempt to serve a DirectoryIndex document. This takes priority over your internal rewrite.
To resolve this you need to disable this behaviour with DirectorySlash Off.
For example:
# Ensure that directory listings are disabled
Options -Indexes
# Prevent mod_dir appending a slash to physical directories
DirectorySlash Off
# Redirect to remove the ".html" extension
RewriteCond %{THE_REQUEST} /([^.?]+)\.html [NC]
RewriteRule ^ /%1 [NC,L,R=301]
# Rewrite request to append ".html" extension if it exists
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule (.*) $1.html [L]
Directory listings (mod_autoindex) need to be disabled when you disable DirectorySlash because if mod_autoindex is enabled then when you request the directory without a slash, a directory listing will be generated, regardless of whether you have a DirectoryIndex document (eg. index.html) in that directory that would ordinarily prevent the directory listing being generated.
Also, I've "fixed" your existing rules that remove and append the .html extension. The first rule that removes the .html extension could have potentially matched an instance of .html that appeared in the query string. And the second rule that appends the .html extension would have resulted in a rewrite-loop (500 error) if requesting /demo/<does-not-exist> - where demo is a directory and a file basename (as in your example).
See my answer to a related question on ServerFault for more information on this potential rewrite-loop:
https://serverfault.com/questions/989333/using-apache-rewrite-rules-in-htaccess-to-remove-html-causing-a-500-error

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]

Force subfolders to follow parent htaccess redirect rules?

I am currently using the following .htaccess code on my server to enable me to host the primary domain files from a subfolder in the public_html folder:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www.)?example.com$
RewriteCond %{REQUEST_URI} !^/subdirectory/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /subdirectory/$1
RewriteCond %{HTTP_HOST} ^(www.)?example.com$
RewriteRule ^(/)?$ subdirectory/index.php [L]
This doesn't solve the problem of subfolders of this new root, however. For example, I have two folders:
public_html\example\ - which should correspond to www.example.com
and
public_html\example\subfolder - which should correspond to www.example.com/subfolder
My problem is that navigating to www.example.com/subfolder in the browser redirects me to www.example.com/example/subfolder.
EDIT: Further to the response from #Jon, this is only occurring when navigating to the URL without a trailing slash. Navigating to www.example.com/subfolder/ is working as expected.
How do I prevent the redirect to www.example.com/example/subfolder?
When you go to a URL that's a folder and you are missing the trailing slash, apache will redirect the browser to so that there's a trailing slash at the end. This can sometimes mess with rewrite rules that are internally rewriting requests.
You either must go to example.com/subfolder/ or turn off the directory slash function. However, turning this off can be very dangerous:
Security Warning
Turning off the trailing slash redirect may result in an information disclosure. Consider a situation where mod_autoindex is active (Options +Indexes) and DirectoryIndex is set to a valid resource (say, index.html) and there's no other special handler defined for that URL. In this case a request with a trailing slash would show the index.html file. But a request without trailing slash would list the directory contents.
This means, if someone goes to: example.com/subfolder, they'll see your directory contents, eventhough there's an index.php file there. You can turn off indexes but then they'll just see a 403, and still won't see your index.php.

htaccess Silent Redirect to Subdirectory: Subdirectory showing when no trailing '/'

I have dug high and low around Google and StackOverflow to try and figure out my problem, trying countless solutions but nothing has completely worked.
I'm looking to move the web root of the main domain on my server to a sub-directory. What I have currently for a server path to my web root:
/home/user/public_html/MyWebFilesHere
What I'm looking to have:
/home/user/public_html/subdir/MyWebfilesHere
When I browse to mydomain.com, there should be no visible difference though (i.e. "subdir" not visible after redirect).
Unfortunately, I am restricted to doing this purely with a .htaccess file since I'm on shared hosting and don't have access to Apache config files and such. :(
What I currently have in my .htaccess in public_html is:
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www\.)?mydomain\.com$
RewriteCond %{REQUEST_URI} !^/subdir/
RewriteRule ^(.*)$ /subdir/$1 [L]
This successfully redirects all queries to the sub-directory, however there's a really weird issue. If I go to
mydomain.com/Contact/
it works great, redirecting the query to the path /subdir/Contact/ but leaving the address bar alone. If I go to
mydomain.com/Contact
(Note the lack of a trailing '/') though, what shows in the address bar is
mydomain.com/subdir/Contact/
which isn't what I want since "subdir" is showing.
For a working example on my actual site, try browsing to
colincwilliams.com/Contact/
compared with
colincwilliams.com/Contact
Do you guys have any ideas on how to make this work silently both with and without a trailing slash?
This is probably happening because mod_dir (the module that automatically redirects the browser if a request for a directory is missing a trailing slash to the same thing with a trailing slash. See the DirectorySlash directive in mod_dir
What's happening is:
You request: mydomain.com/Contact
mod_dir doesn't touch this since /Contact isn't a directory
/Contact gets rewritten to /subdir/Contact and internally redirected
mod_dir sees that /subdir/Contact is a directory and missing the trailing slash so it redirects the browser to mydomain.com/subdir/Contact/
So now, your browser's location bar has the /subdir/ in it.
You can add DirectorySlash off in your .htaccess to turn off mod_dir from redirecting. But if you want directories to have trailing slashes, you can add a separate condition for it. Based on what you already have, we can expand it to this:
RewriteEngine on
# Has a trailing slash, don't append one when rewriting
RewriteCond %{HTTP_HOST} ^(www\.)?mydomain\.com$
RewriteCond %{REQUEST_URI} !^/subdir/
RewriteCond %{THE_REQUEST} ./\ HTTP/1\.[01]$ [OR]
# OR if it's a file that ends with one of these extensions
RewriteCond %{REQUEST_URI} \.(php|html?|jpg|gif|css)$
RewriteRule ^(.*)$ /subdir/$1 [L]
# Missing trailing slash, append one
RewriteCond %{HTTP_HOST} ^(www\.)?mydomain\.com$
RewriteCond %{REQUEST_URI} !^/subdir/
RewriteCond %{THE_REQUEST} [^/]\ HTTP/1\.[01]$
# But only if it's not a file that ends with one of these extensions
RewriteCond %{REQUEST_URI} !\.(php|html?|jpg|gif|css)$
RewriteRule ^(.*)$ /subdir/$1/ [L]
Note: I changed !^/mydomain/ to !^/subdir/, figured it was a typo because without it, mod_rewrite would loop internally indefinitely (foo -> /subdir/foo -> /subdir/subdir/foo -> /subdir/subdir/subdir/foo, etc). If I got that wrong, you can change it back.
Edit: See my additions of RewriteCond's matching against \.(php|html?|jpg|gif|css). These are the file extensions that get passed through without getting trailing slashes added. You can add/remove to suit your needs.
Jon Lin's answer was very helpful in determining what was causing the problem in my very similar setup. For completeness I will include the relevant information from his answer:
This is probably happening because mod_dir (the module that automatically redirects the browser if a request for a directory is missing a trailing slash to the same thing with a trailing slash. See the DirectorySlash directive in mod_dir
What's happening is:
You request: mydomain.com/Contact
mod_dir doesn't touch this since /Contact isn't a directory
/Contact gets rewritten to /subdir/Contact and internally redirected
mod_dir sees that /subdir/Contact is a directory and missing the trailing slash so it redirects the browser to mydomain.com/subdir/Contact/
So now, your browser's location bar has the /subdir/ in it.
In my case, I had requests being redirected to /subdir with a few exceptions and didn't want to have to re-enable DirectorySlash for each of those exceptions.
By allowing RewriteEngine to continue after the initial redirect to /subdir, it's possible to mimic what mod_dir would be doing while also taking /subdir into account, before mod_dir gets to see it.
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/exception1|exception2|...
RewriteRule ^(.*)$ /subdir/$1
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^subdir/(.*) $1/ [R,L]
Note: You may need to be careful about allowing RewriteEngine to continue if there are further rules. Not matching the second rule will continue on to any further rules which may produce a different result.
This can be avoided by using a third rule to stop RewriteEngine processing if the redirect into /subdir has happened:
RewriteCond %{REQUEST_URI} ^subdir
RewriteRule .* - [L]