Remove trailing slash after directory with htaccess - apache

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.

Related

SilverStripe 4 - Remove slash at the end of the URL

I've been trying for some time to delete the slash at the end of the URL link, but it doesn't work. I searched a lot of examples but none of them solve my problem.
I'm using Silverstripe 4 and currently running on a local server.
I have to remove the slash for SEO reasons.
My current URL is:
www.example.com/
// Need to be like below
www.example.com
I try via htaccess
Exampe from stackoveflow question
I put in /public/.htaccess
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R] # <- for test, for prod use [L,R=301]
and when i visit homepage slash is there at the end.
I try via code in SiteTree
public function Link($action = null)
{
return rtrim(parent::Link($action), '/');
}
Above code remove slash at the end from all pages but on home page still there.
www.example.com/about-us (here removed)
www.exaple.com/ (here exists)
And also try via config file
Director::config()->set('alternate_base_url', rtrim(Environment::getEnv('SS_BASE_URL'), '/'));
But again slash exists at the end of the url on homepage.
Does someone have solution for this? Thanks!
Here is my full htaccess file
<IfModule mod_rewrite.c>
# Turn off index.php handling requests to the homepage fixes issue in apache >=2.4
<IfModule mod_dir.c>
DirectoryIndex disabled
DirectorySlash On
</IfModule>
SetEnv HTTP_MOD_REWRITE On
RewriteEngine On
# Enable HTTP Basic authentication workaround for PHP running in CGI mode
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Deny access to potentially sensitive files and folders
RewriteRule ^vendor(/|$) - [F,L,NC]
RewriteRule ^\.env - [F,L,NC]
RewriteRule silverstripe-cache(/|$) - [F,L,NC]
RewriteRule composer\.(json|lock) - [F,L,NC]
RewriteRule (error|silverstripe|debug)\.log - [F,L,NC]
# Process through SilverStripe if no file with the requested name exists.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php
# REMOVE SLASH AT THE END OF THE URL
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R] # <- for test, for prod use [L,R=301]
</IfModule>
www.example.com/
// Need to be like below
www.example.com
What you are trying to do is not possible! You are trying to remove the slash at the start of the URL-path, immediately after the hostname. This is not the same as the slash at the end of the URL-path.
There is always a slash at the start of the URL-path, even if you don't always see this in the browser's address bar (the browser often "prettifies" the URL you see in the address bar). This is necessary in order to form a valid HTTP request.
Whether you request www.example.com (no slash) or www.example.com/ (with slash), the user-agent/browser actually makes the exact same request to the server, ie. www.example.com/ (note that Google Chrome never displays the trailing slash after the hostname in the address bar, aka "omnibox", even if you type it in). If you look at the first line of the HTTP request headers you will see something like the following in both cases:
GET / HTTP/1.1
Note the first slash (delimited by spaces) - that represents the URL-path. It is not valid to have nothing here (eg. GET HTTP/1.1 is not a valid HTTP request).
This is different to removing the slash at the end of the URL-path, eg. www.example.com/about-us/ to www.example.com/about-us. In this case the trailing slash is just another character. (Although there is naturally a complication when the URL-path maps to a physical directory since mod_dir will (by default) always append a trailing slash in this instance.)
See also my answer to the following question on the Webmasters Stack for more detail:
https://webmasters.stackexchange.com/questions/35643/is-trailing-slash-automagically-added-on-click-of-home-page-url-in-browser
Further reference:
https://www.rfc-editor.org/rfc/rfc2616#section-5.1.2
I try via htaccess
Attempting to remove the slash from the start of the URL-path will result in a redirect loop since the user-agent will correct the request each time. For the request to have reached your server then there must have been a slash at the start of the URL-path (ie. after the hostname).
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R]
The homepage, ie. document root, is a directory so the condition fails and the rule is not processed.
But the pattern ^(.*)/$ will only successfully match a non-empty URL-path. And /$1 naturally redirects with a slash prefix. To omit the slash you would need to do something like this:
# DON'T DO THIS
RewriteRule ^$ https://www.example.com [R,L]
But this is nonsense and will result in a redirect loop, for the reasons mentioned above.

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 DirectorySlash Off causes "/" to be inaccessible

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.

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]