htaccess issue - strange [OR] behaviour - .htaccess

I've noticed an issue with [OR] in htaccess that I don't understand. What I want to do is redirect every non-existent file and every file that is a .php file (and not /index.php) to /404/. By my understanding following two blocks should do the same thing:
# .htaccess 1
RewriteEngine on
# cond 1
RewriteCond %{REQUEST_FILENAME} !-f [OR]
# cond 2
RewriteCond %{REQUEST_FILENAME} !index\.php$
# cond 3
RewriteCond %{REQUEST_URI} \.php$
# rule 1
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]
# rule 2
RewriteRule ^(([^/\.]+)/?)*?$ /index.php [L]
and this:
# .htaccess 2
RewriteEngine on
# cond 1
RewriteCond %{REQUEST_FILENAME} !-f
# rule 1
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]
# cond 2
RewriteCond %{REQUEST_FILENAME} !index\.php$
# cond 3
RewriteCond %{REQUEST_URI} \.php$
# rule 2
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]
# rule 3
RewriteRule ^(([^/\.]+)/?)*?$ /index.php [L]
Yet, only the second block does what I want. The first .htaccess, for example - fails to redirect a non-existent file a.pdf and instead displays default server message about file not being found.
Can somebody please explain this to me?

Your OR conditional in the first .htaccess is in the wrong order.
The [OR] flag creates a group around the two statements, effectively writing your conditional as:
if (
(FILE_NOT_FOUND or FILE_NAME === 'index.php')
and REQUEST_URI ends in .php
) { ... }
Written in this order, the conditional will never match a URI that ends in an extension other than PHP.
What you actually want is:
if (
(FILE_NOT_FOUND or REQUEST_URI ends in .php)
and FILE_NAME !== 'index.php'
) { ... }
Which can be expressed by simply rearranging the two related rewrite conditions:
RewriteCond %{REQUEST_FILENAME} !-f [OR]
RewriteCond %{REQUEST_FILENAME} \.php$
RewriteCond %{REQUEST_URI} !index\.php
RewriteRule [^\.]*\.[a-zA-Z0-9]+$ /404/ [L]
You can even catch your mistake by reading your own words (simplified to show the actual conditional you had in mind):
What I want to do is redirect every non-existent file [or] every .php file, [and] not /index.php to /404/.
Here is a good StackOverflow question/answer about the precedence of [OR] in .htaccess files if you'd like to learn more about how mod_rewrite actually works internally.

Related

.htaccess rewrite if nothing specified

I'm trying to modify my .htaccess file to link to three different places, based on the input after the endpoint:
"/api" - Link to the API
"/ABCD123" - If it's a 7 character
alphanumeric string, link to a specific page
"/" - If nothing specified, or for any other inputs link to the homepage.
Here is my .htaccess file:
Options -Indexes
RewriteEngine On
RewriteRule ^api/(.*) ./api/index.php [R,L]
RewriteRule ^([a-zA-Z0-9]{7})$ index.php?l=$1 [L]
RewriteRule ^(.+)$ everythingelse.php [L]
Even though I have the [L] flag specified I always seem to get redirected to the everythingelse.php route, even if I have the 7 character string.
How can I rewrite to match this correctly?
Your rules are looping and executing more than once. L flag only breaks current loop but mod_rewrite can loop again and execute all the matching rules.
Options -Indexes
RewriteEngine On
# skip all files and directories from rewrite rules below
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
RewriteRule ^api/(.*)$ /api/index.php [R,L]
RewriteRule ^([a-zA-Z0-9]{7})$ index.php?l=$1 [L,QSA]
RewriteRule . everythingelse.php [L]

htaccess redirect with unique id to title

I have URL's like this:
http://www.example.com/en/product.php?id=23&t=page-title-here
I want change URL's to something like this :
http://www.example.com/en/product23/page-title-here/
Place the following in your /.htaccess file:
RewriteEngine on
# Step 1: Redirect file-based URIs to new 'pretty permalinks' and prevent looping
RewriteCond %{ENV:REDIRECT_STATUS} !200
RewriteCond %{QUERY_STRING} ^id=(\d+)&t=([^/]+)$ [NC]
RewriteRule ^(en/product).php$ /$1%1/%2? [R=302,NE,L]
# Step 2: Rewrite above permalink to file-based URI
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(en/product)(\d+)/([^/]+)/?$ /$1.php?id=$2&t=$3 [L,QSA]
Update: If you want to match multiple languages (per your comment below), you can use a non-capture group that checks for exactly two characters instead of just en:
# Use this rule in step 1 above
RewriteRule ^((?:[a-z]{2})/product).php$ /$1%1/%2? [R=302,NE,L]
# Use this rule in step 2 above
RewriteRule ^((?:[a-z]{2})/product)(\d+)/([^/]+)/?$ /$1.php?id=$2&t=$3 [L,QSA]

htaccess rewrite/redirect if last character is NOT a digit or slash

I have a site where I have a htaccess rule set to take the entire url, and forward it to my index file, using the below rule, with everything working fine.
#################################
# Magic Re-Writes DO NOT CHANGE #
#################################
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine on
#RewriteBase /
# Do Not apply if a specific file or folder exists
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# The rules on how to rewrite the urls
RewriteRule (.*) /index.php?url=$1 [QSA,L]
</IfModule>
So the below rule forwards http://mydomain.com/players/scoresheet/singlegame
to
http://mydomain.com/index.php?url=players/scoresheet/singlegame
However, I also need to ensure I cater for people forgetting the trailing slash in the url, something normally straight forward, however, I need to be able to force the final trailing slash ONLY if that last character is not numerical (or a slash obviously).
For Example, someone types;
http://mydomain.com/players/scoresheet/singlegame
I need the url in the browser to show as: http://mydomain.com/players/scoresheet/singlegame/
but still be forwarded to: http://mydomain.com/index.php?url=players/scoresheet/singlegame/
As said the exception to this will be if the last character already has the trailing slash, or is a numerical digit.
(Hope that makes sense)
Ok, heres what I have so far...
#######################################
# Add trailing slash to url #
# unless last character is a number #
#######################################
<IfModule mod_rewrite.c>
RewriteEngine on
Rewritecond %{REQUEST_URI} [^0-9/]$
RewriteRule ^(.*)$ /$1/ [R=301,L]
</IfModule>
#################################
# Magic Re-Writes DO NOT CHANGE #
#################################
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine on
RewriteBase /
# Do Not apply if a specific file or folder exists
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# The rules on how to rewrite the urls
RewriteRule (.*) /index.php?url=$1 [QSA,L]
</IfModule>
The problem with this is although it seems to get the adding of the slash to the url, it also addes the index.php as well, so what I end up with, is:
Visit: http://mydomain.com/players/scoresheet/singlegame
get url rewritten to: http://mydomain.com/index.php?url=players/scoresheet/singlegame/
The slash is added, but I need it to do so without display the index part.
I have gone backwards and forwards, with many different outcomes (usually outright failures, or loops).
Any help would be appreciated
Your rule is correct, but it's blindly redirecting even when it's not supposed to. The URL that you have above is probably not what it's getting rewritten to. You have it as:
http://mydomain.com/index.php?url=players/scoresheet/singlegame/
But I'm willing to bet it's really something like:
# note the slash here--------v
http://mydomain.com/index.php/?url=players/scoresheet/singlegame/
Because after the URI is internally rewritten and routed to /index.php, the rewrite engine loops again and the redirect catches it, and redirects /index.php to /index.php/. So you need to add the same exclusion conditions that you have in your routing rule:
So change:
Rewritecond %{REQUEST_URI} [^0-9/]$
RewriteRule ^(.*)$ /$1/ [R=301,L]
to either:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
Rewritecond %{REQUEST_URI} [^0-9/]$
RewriteRule ^(.*)$ /$1/ [R=301,L]
or:
RewriteCond %{REQUEST_URI} !index.php
Rewritecond %{REQUEST_URI} [^0-9/]$
RewriteRule ^(.*)$ /$1/ [R=301,L]

htaccess rewrite / remove parent dir

there's been similar posts about this but I can't quite seem to find what I need.
I want my .htacess to rewrite "up one level".
The Url would be somethign like
http://www.site.com/variable_dir/
or
http://www.site.com/variable_dir/sub_dir
I need that to basically rewrite the request to
http://www.site.com/
or
http://www.site.com/sub_dir
I DO want the URL to still show the original
http://www.site.com/variable_dir/
or
http://www.site.com/variable_dir/sub_dir
I currently have
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php [L]
This redirects to where I want, but this changes the URL to
http://www.site.com/
or
http://www.site.com/sub_dir
which I don't want.
I know it's simple but I just can't seem to get there.
The rule below woule rewrite http://www.site.com/variable_dir/ to http://www.site.com/ and http://www.site.com/variable_dir/sub_dir to http://www.site.com/sub_dir
RewriteEngine on
RewriteBase /
#for a request to /variable_dir
RewriteCond %{REQUEST_URI} ^/variable_dir/(.+)$
#rewrite it to directory without variable_dir
RewriteRule . /%1 [L]
Edit:
If the directory is not literally variable_dir, the rule above will not work. However, if you have a short list of directories, you could enumerate them as below.
RewriteEngine on
RewriteBase /
#only apply if this directory does not exist
RewriteCond %{REQUEST_FILENAME} !-d
#for any direcory enumerated here
RewriteCond %{REQUEST_URI} ^/(variable_dir|dir2|dir3|etc)/(.+)$
#rewrite it to directory without variable_dir
RewriteRule . /%2 [L]
If not, then ideally the directories would all have something in common so you could limit what the rule affects. If you want a completely variable dir, nothing in common, I don't recommend it, but you can try
RewriteEngine on
RewriteBase /
#only apply if this directory does not exist
RewriteCond %{REQUEST_FILENAME} !-d
#skip any top level directory
RewriteCond %{REQUEST_URI} ^/[^/]+/(.+)$
#rewrite it to directory without variable_dir
RewriteRule . /%1 [L]
Edit:
Finally if the trailing slash is optional, as in the example in your comment, change the RewriteCond and rule above to be
#skip any top level directory, optional trailing slash
RewriteCond %{REQUEST_URI} ^/[^/]+(/(.+))?$
#rewrite it to directory without variable_dir
RewriteRule . /%2 [L]

htaccess rule is passing innecesary variable

This is the file tree:
/problem/
/problem/index.php
index.php
category.php
somefile.php
I have this 2 rules in the .htaccess that is sitting in the /
RewriteRule ^somedir$ /somefile.php [L]
RewriteRule ^([a-z-]+)$ /category.php?cat=$1 [QSA,L]
So...
http://domain.com/somedir = OK
http://domain.com/ = OK
http://domain.com/problem/ < automatically adds ?cat=problem to the querystring. I want to avoid that extra ?cat=problem
I need to add a rule that doesn't add the cat=$1 when the /dir/ exists.
Just add a RewriteCond before your second rule. Basically, don't run that catch all if it starts with product:
RewriteRule ^somedir$ /somefile.php [L]
RewriteCond %{REQUEST_URI} !^/product [NC]
RewriteRule ^([a-z-]+)$ /category.php?cat=$1 [QSA,L]
To prevent redirecting for a real file or directory, add these two lines before the rule:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

Resources