Forcing trailing backslash in .htaccess causing 404 error - apache

I'm trying to find the correct .htaccess config to force a trailing slash after every URL, but it's causing 404's in many instances.
I have the following directory structure:-
articles
post.html
portfolio
lorum1.html
lorum2.html
lorum3.html
contact.html
Example 1
So if I go to:-
myurl.com/articles/post.html or
myurl.com/articles/post or
myurl.com/articles/post/
I need these to all redirect to the .html, but with the url showing:
myurl.com/articles/post/
Example 2
So if I go to myurl.com/contact/, it needs to display the content of myurl.com/contact.html, whilst still maintaining the myurl.com/contact/ url.
What's currently happening
Here's an example using the contact path. I get a 200 response if I go to myurl.com/contact and myurl.com/contact.html, but a 404 if I go to myurl.com/contact/.
Here's what I have so far.
<IfModule mod_rewrite.c>
RewriteEngine on
# Remove .html
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
# Force trailing slash
RewriteCond %{REQUEST_URI} /+[^\.]+$
RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]
</IfModule>
I'm struggling to make sense of this problem, so thanks in advance to anybody who can help!

Your example is very close to hitting your requirements. The problem is that the first RewriteRule is matching ^(.*)$, which in regex terms is equal to matching literally anything.
Your rules can be implemented by adding the missing forward slash to the regex before the end-position metacharacter $, meaning that the regex will now match anything, as long as it ends with a forward slash: ^(.*)/$
Fixed:
<IfModule mod_rewrite.c>
RewriteEngine on
# Remove .html
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)/$ $1.html
# Force trailing slash
RewriteCond %{REQUEST_URI} /+[^\.]+$
RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]
</IfModule>
However while trying to understand Apache's syntax I think I found a much simpler solution to achieve the same:
RewriteEngine on
# Rewrite requests ending in a slash to show the html file
RewriteRule ^(.+)/$ $1.html
# If the request uri doesn't end in .html...
RewriteCond %{REQUEST_URI} !\.(html?)$
# ...rewrite requests that don't end in a slash so that they do end in a slash
RewriteRule ^(.*)([^/])$ /$1$2/ [R=303]

Related

Why does Apache throw a Forbidden error when using RewriteRule and going to an URL with a trailing slash at its end?

I want to build a search page for my application, and I am using .htaccess to make URLs look better. The search page is located at localhost/search. Furthermore, two optional parameters can be appended at the end of the URL, like this: localhost/search/post/12345. However, when no parameters are appended, only a trailing slash (localhost/search/), Apache throws a Forbidden error. I am not sure why this is happening as there is no folder named search on the server.
To better illustrate my problem, here are the links that work and the ones that don't are as follows:
Working:
localhost/search
localhost/search/post
localhost/search/post/
localhost/search/post/12345
localhost/search/post/12345/
Not working:
localhost/search/
search.php is located in the root directory along with the .htaccess file. So here is the .htaccess file:
RewriteEngine On
# Redirect HTTP traffic to HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]
# Unless directory, remove trailing slash
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)/$ $1 [R=301,L]
# Resolve .php file for extension-less php urls
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^([^/.]+)$ $1.php [L]
# Search parameters
RewriteRule ^search?/?([0-9a-zA-Z_-]+)?/?([0-9a-zA-Z_-]+)?/?$ search.php?type=$1&data=&2
# 404 error
ErrorDocument 404 /errors/404.php
I figured out the problem, following this helpful answer on StackOverflow. In the .htaccess file, I changed the following lines:
# Unless directory, remove trailing slash
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)/$ $1 [R=301,L]
to:
# Unless directory, remove trailing slash
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [L,R=301]

Redirect to index.php in root

I've written the following code in my htaccess file:
RewriteRule ^(.*)/$ $1
RewriteCond %{REQUEST_URI} !^index.php.*$
RewriteRule ^(.*)$ /index.php?route=$1 [END]
It works perfect for every path, except for directories that exist. For example, if I enter http://localhost/profilepic and such directory actually exists, it redirects to http://localhost/profilepic/?route=profilepic, but I want it to be implicitly converted to http://localhost/index.php?route=profilepic.
Thanks in advance.
The reason this is happening is because of mod_dir and the DirectorySlash directive. Essentially, if it sees a URI without a trailing slash, and it maps to an existing directory, then it'll redirect the request so that it has the trailing slash. Since mod_dir and mod_rewrite are both in different places in URL-file processing pipeline, both mod_dir and mod_rewrite get applied to the same URL. That's why you end up with a redirect and a weird URL with the query string.
If you absolutely must have directories without trailing slashes, then you need to turn of DirectorySlash. The problem with turning it off is that there is an information disclosure security concern that will make it so people can look at the contents of a directory even if you have an index file. That means you have to make up for mod_dir using mod_rewrite.
So get rid of the rule:
RewriteRule ^(.*)/$ $1
and replace it with these rules:
DirectorySlash Off
# redirect direct requests that end with a slash to remove the slash.
RewriteCond %{THE_REQUEST} \ /+[^\?\ ]+/($|\ |\?)
RewriteRule ^(.*)/$ /$1 [L,R]
# internally add the trailing slash for directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.*[^/])$ /$1/ [L]
Here is another way you can have your rules without turning off DirectorySlash (considered a security hole):
RewriteEngine On
# remove trailing slash for non-directories
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{THE_REQUEST} \s(.+?)/+[?\s]
RewriteRule ^(.+?)/$ /$1 [R=301,L]
# routing for directories
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+?)/$ /index.php?route=$1 [L]
# routing for non directories
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+?)/?$ /index.php?route=$1 [L]

Apache mod_rewrite for SEO urls, but rewrite to 404 if file doesn't exist

I'm unable to find the answer for this, so please let me know if it's been resolved before.
I'm using mod_rewrite to do "pretty" URLs, but if you request a file that doesn't exist (like, a typo) it will redirect and add .php a bunch of times and then fail. The code I have below:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://inquisito.rs/$1/ [R=301,l]
RewriteRule ^(.*)/$ /$1.php [L]
So if you go to http://inquisito.rs/aion/ it will show you the aion page, but if you go to, lets say, inquisito.rs/aio/ on accident, it gives this
http://inquisito.rs/aio.php.php.php.php.php.php.php.php.php.php.php.php.php.php.php.php.php.php.php.php/
Thanks in advance, I can't tell you how many times I've used information from here to resolve issues at work and at home.
Using the example you've given, this is how the rules are applied:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f # /aio/ is not a file, so this matched
RewriteCond %{REQUEST_URI} !(.*)/$ # This DOES NOT match, because you have a trailing slash
RewriteRule ^(.*)$ http://inquisito.rs/$1/ [R=301,L] # This rule doesn't run, because the condition above wasn't met
# This rule is separate from the RewriteConds above
RewriteRule ^(.*)/$ /$1.php [L] # This does match because of the lack of RewriteConds and because you have a trailing slash
Try this (untested) set of rules instead:
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f # Make sure no matching file exists
RewriteCond %{REQUEST_URI} !\.php$ # Don't match requests that already end .php
RewriteCond %{REQUEST_URI} !(.*)/$ # Check for missing trailing slash
RewriteRule ^(.*)$ http://inquisito.rs/$1/ [R=301,L] # Redirect with trailing slash
# Separate rule
RewriteCond %{REQUEST_URI} !\.php$ # Don't match requests that already end .php
RewriteRule ^(.*)/$ /$1.php [L] # Internal redirect to matching PHP file
It's important to note that all matching RewriteRules cause a new request to be processed by htaccess again.

Apache Rewrite Rules - Path Style

I am coming from the IIS world to Apache and would appreciate some help on the rewrite rules.
I want this relative path:
/index.php?go=order&id=12345
to be rewritten as:
/go/order/id/12345
Also, if there are more parameters, they should be converted to path format:
/index.php?go=order&id=12345&action=process
becomes
/go/order/id/12345/action/process
How do I achieve this please? Thanks for any input.
Try putting this in your vhost config:
RewriteEngine On
# Start converting query parameters to path
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\?[^\ ]+
RewriteCond %{QUERY_STRING} ^([^=]+)=([^&]+)&?(.*)$
RewriteRule ^(.*)$ $1/%1/%2?%3 [L]
# done converting, remove index.php and redirect browser
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index\.php\?[^\ ]+
RewriteCond %{QUERY_STRING} ^$
RewriteRule ^/index.php/(.*)$ /$1 [R=301,L]
# internally rewrite paths to query strings
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/index\.php
RewriteRule ^/([^/]+)/([^/]+)/?(.*) /$3?$1=$2 [L,QSA]
# No more path, rewrite to index.php
RewriteRule ^/$ /index.php [L]
These rules will make it so if you type in a URL like http://yourdomain.com/index.php?a=b&1=2&z=x in your browser, the browser will get redirected to http://yourdomain.com/a/b/1/2/z/x. When the clean looking URL gets requested, the 2nd set of rules internally rewrites it back to /index.php?a=b&1=2&z=x. If you want to put these rules in an htaccess file (in your document root), you need to remove all the leading slashes in the RewriteRule's. So ^/ needs to be ^ in the last 3 rules.
Note that if you simply go to http://yourdomain.com/index.php, without a query string, nothing gets rewritten.

RewriteRule not working fine

I wrote the following rule in .htaccess
Options +FollowSymLinks
RewriteEngine on
RewriteRule ^(.*)/$ profile.php?business=$1
When i enter the URL like
http://www.abc.com/mujeeb/
page is correctly transfered to profiles page and page looks fine.
But i enter this in URL
http://www.abc.com/mujeeb
page doesn't show.
Can you please tell why? Or write the rule for this? i tried many times but not sucessful.
Mujeeb.
page doesn't show. because you specified that you RewriteRule is applied to the URL's ending with / at the end. Rewrite it as
RewriteRule ^(.*)/?$ profile.php?business=$1 [L]
And I hope that you have additional RewriteCond statements in order to prevent the infinite loop with redirects.
ps: basically you can prevent loop in two way
1) checks that requested url does not correspond to the existing file or directory. it is, probably, the best way to do (read comments to the second method)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/?$ profile.php?business=$1 [L]
2) checks that you are requesting not the file from RewriteRule. This method is not good, because for each request, even for existing files and directories, it calls profile.php script
RewriteCond %{REQUEST_URI} !profile\.php$
RewriteRule ^(.*)/?$ profile.php?business=$1 [L]
It is because you check for the trailing slash with ^(.*)/$. If you add a question mark, the trailing slash will be optional.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.*)/?$ profile.php?business=$1
The RewriteCond is neccessary to make sure the Rule will only be applied once. Otherwise Apache will be caught in an infinite loop.
Try this:
Options +FollowSymLinks
RewriteEngine on
RewriteRule ^(.*)[/]?$ profile.php?business=$1
That makes the last slash optional.
Well you rule is checking for a trailing slash in URI and that's the reason /mujeeb/ works but /mujeeb does not. Change your code to:
Options +FollowSymLinks -MultiViews
# Turn mod_rewrite on
RewriteEngine On
# If the request is not for a valid file
#RewriteCond %{REQUEST_FILENAME} !-d
# If the request is not for a valid directory
#RewriteCond %{REQUEST_FILENAME} !-f
# your rule without trailing slash
RewriteRule ^(.*)$ profile.php?business=$1 [L,QSA]
Plenty of good answers already. My answer is a bit different.
This is what I usually do. If the requested URL doesn't end with a /, I make the browser redirect to a URL with the trailing /. This is consistent with the default behaviour of Apache (due to mod_dir). So, this is how I solve this problem.
RewriteEngine On
# Canonicalize http://example.com/mujeeb to http://example.com/mujeeb/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)([^/])$ /$1$2/ [R=307,L]
# Let profile.php process http://example.com/mujeeb/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ profile.php?business=$1