Applying multiple rules with .htaccess - .htaccess

My current .htaccess looks like this, and currently has the function of ensuring https-access:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
But, I want it to do more than that, as my current link-structure isn't really SEO-friendly. Basically, I want to make changes so that I can type in the URL on the left, and have the URL on the right loaded.
https://nexuscompendium/heroes/li-li/ --> https://nexuscompendium.com/hero.php?h=li-li
https://nexuscompendium/heroes/ --> https://nexuscompendium.com/heroes/
I've come up with the following that seems to work just fine... as long as the HTTPS-rule above isn't also active, as the other rules simply removes the "https://" again.
RewriteRule heroes\/role\/([a-zA-Z]+)(\/?)$ herorole.php?r=$1 [L]
RewriteRule heroes\/([a-zA-Z\-]+)(\/?)$ hero.php?h=$1 [L]
RewriteRule battlegrounds\/([a-zA-Z\-]+)(\/?)$ battleground.php?b=$1 [L]
RewriteRule rotations\/([0-9\-]+)(\/?)$ rotation.php?r=$1 [L]
RewriteRule sales\/([0-9\-]+)(\/?)$ sale.php?s=$1 [L]
RewriteRule predictions\/([0-9\-]+)(\/?)$ predictions.php?w=$1 [L]
RewriteRule universes\/([a-zA-Z\-]+)(\/?)$ universe.php?u=$1 [L]
RewriteRule subuniverses\/([a-zA-Z\-]+)(\/?)$ subuniverse.php?s=$1 [L]
RewriteRule events\/seasonal\/([a-zA-Z\-]+)(\/?)$ seasonal.php?s=$1 [L]
RewriteRule events\/([a-zA-Z0-9\-]+)(\/?)$ event.php?e=$1 [L]
RewriteRule skins\/([a-zA-Z0-9\-]+)(\/?)$ skin.php?s=$1 [L]
RewriteRule mounts\/([a-zA-Z0-9\-]+)(\/?)$ mount.php?m=$1 [L]
RewriteRule ([a-zA-Z]+)(\/?)$ $1.php
Long story short: Can I combine the rules above - and, if so, how? If not, is there a way to ensure the HTTPS-redirect otherwise?
Thanks in advance ❤
EDIT: Thanks to the knowledge that .htaccess loops until no more rules can be completed (along with the slight addition of ^ in the last RewriteRule, I got it working, as follows:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
<IfModule mod_rewrite.c>
RewriteRule ^heroes\/role\/([a-zA-Z]+)(\/?)$ herorole.php?r=$1 [L]
RewriteRule ^heroes\/([a-zA-Z\-]+)(\/?)$ hero.php?h=$1 [L]
RewriteRule ^battlegrounds\/([a-zA-Z\-]+)(\/?)$ battleground.php?b=$1 [L]
RewriteRule ^rotations\/([0-9\-]+)(\/?)$ rotation.php?r=$1 [L]
RewriteRule ^sales\/([0-9\-]+)(\/?)$ sale.php?s=$1 [L]
RewriteRule ^predictions\/([0-9\-]+)(\/?)$ predictions.php?w=$1 [L]
RewriteRule ^subuniverses\/([a-zA-Z\-]+)(\/?)$ subuniverse.php?s=$1 [L]
RewriteRule ^universes\/([a-zA-Z\-]+)(\/?)$ universe.php?u=$1 [L]
RewriteRule ^events\/seasonal\/([a-zA-Z\-]+)(\/?)$ seasonal.php?s=$1 [L]
RewriteRule ^events\/([a-zA-Z0-9\-]+)(\/?)$ event.php?e=$1 [L]
RewriteRule ^skins\/([a-zA-Z0-9\-]+)(\/?)$ skin.php?s=$1 [L]
RewriteRule ^mounts\/([a-zA-Z0-9\-]+)(\/?)$ mount.php?m=$1 [L]
RewriteRule ^([a-zA-Z]+)(\/?)$ $1.php
</IfModule>

I chose to offer another answer here. Not because yours is wrong, it is great that you figured out a working setup yourself!
However your implementation can be optimized quite a bit. Yes, what you implemented is perfectly possible, but the more rules get applied the more the http server get's slowed down (we are talking about regular expression matching here, that is quite an expensive operation). So it might be worth to condense this a bit:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
RewriteRule ^/?heroes/role/([a-zA-Z]+)(/?)$ /herorole.php?r=$1 [END]
RewriteRule ^/?events/seasonal/([a-zA-Z-]+)(/?)$ /seasonal.php?s=$1 [END]
RewriteCond %{REQUEST_URI} ^/?(\w+)s/([\w-]+)(/?)$
RewriteCond /%1.php -f
RewriteRule ^ /%1.php?h=%2 [END]
RewriteRule ^/?([a-zA-Z]+)(/?)$ /$1.php
</IfModule>
Here some of the optimizations:
the pattern in the redirection to https is not required, since you do not use what it captures anyway. So use the most simple pattern there is: ^ which always matches, since every string has a beginning.
Using ^/? at the start of a pattern in a rewriting rule makes it work in dynamic configuration files (".htaccess") or i nthe real http server configuration. More on that further down.
most of the escape characters (\) are not required. Leaving them away makes the pattern much easier to read.
redirect to absolute path if possible, that can help to prevent confusion.
use [END] instead of [L] in most cases if your http server is not too old ... It terminates the whole rewriting process if that rule gets applied, while the older [L] flag only terminates this run of the rewriting process, so the process loops again. That wastes quite some time, since certainly in this situation no other rule will get applied. So you save one run for every request.
you can combine some of the rules to a more general one which again reduces the number of rules. This is especially true the more rules come together over time...
do you really need case insensivity ([a-zA-Z])? You know the references you hand out, you control them. And no one types those by hand, right? Also you often can use \w instead of [a-z], easier, though not exactly the same. As said: often, but it makes things easier to read again.
also decide if you really need the <IfModule mod_rewrite.c>. It prevents an internal error when the rewriting module is not installed, sure. But what for? Usually the application won't work without anyway...
OK, there is certainly more that can be said here. But this might be enough now give you a starting point. So I will end with a few general remarks:
It is a good idea to start out with a 302 temporary redirection and only change that to a 301 permanent redirection later, once you are certain everything is correctly set up. That prevents caching issues while trying things out...
In case you receive an internal server error (http status 500) using the rule above then chances are that you operate a very old version of the apache http server. You will see a definite hint to an unsupported [END] flag in your http servers error log file in that case. You can either try to upgrade or use the older [L] flag, it probably will work the same in this situation, though that depends a bit on your setup.
This implementation will work likewise in the http servers host configuration or inside a dynamic configuration file (".htaccess" file). Obviously the rewriting module needs to be loaded inside the http server and enabled in the http host. In case you use a dynamic configuration file you need to take care that it's interpretation is enabled at all in the host configuration and that it is located in the host's DOCUMENT_ROOT folder.
And a general remark: you should always prefer to place such rules in the http servers host configuration instead of using dynamic configuration files (".htaccess"). Those dynamic configuration files add complexity, are often a cause of unexpected behavior, hard to debug and they really slow down the http server. They are only provided as a last option for situations where you do not have access to the real http servers host configuration (read: really cheap service providers) or for applications insisting on writing their own rules (which is an obvious security nightmare).

Thanks to the knowledge from arkascha that .htaccess loops until no more rules can be completed (along with the slight addition of ^ in the last RewriteRule), I got it working, as follows:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
<IfModule mod_rewrite.c>
RewriteRule ^heroes\/role\/([a-zA-Z]+)(\/?)$ herorole.php?r=$1 [L]
RewriteRule ^heroes\/([a-zA-Z\-]+)(\/?)$ hero.php?h=$1 [L]
RewriteRule ^battlegrounds\/([a-zA-Z\-]+)(\/?)$ battleground.php?b=$1 [L]
RewriteRule ^rotations\/([0-9\-]+)(\/?)$ rotation.php?r=$1 [L]
RewriteRule ^sales\/([0-9\-]+)(\/?)$ sale.php?s=$1 [L]
RewriteRule ^predictions\/([0-9\-]+)(\/?)$ predictions.php?w=$1 [L]
RewriteRule ^subuniverses\/([a-zA-Z\-]+)(\/?)$ subuniverse.php?s=$1 [L]
RewriteRule ^universes\/([a-zA-Z\-]+)(\/?)$ universe.php?u=$1 [L]
RewriteRule ^events\/seasonal\/([a-zA-Z\-]+)(\/?)$ seasonal.php?s=$1 [L]
RewriteRule ^events\/([a-zA-Z0-9\-]+)(\/?)$ event.php?e=$1 [L]
RewriteRule ^skins\/([a-zA-Z0-9\-]+)(\/?)$ skin.php?s=$1 [L]
RewriteRule ^mounts\/([a-zA-Z0-9\-]+)(\/?)$ mount.php?m=$1 [L]
RewriteRule ^([a-zA-Z]+)(\/?)$ $1.php
</IfModule>

Related

Simple rewritecond in htaccess doesn't work as expected

This is super simple but it's driving me crazy! I have a website at http://example.org/ and a subdirectory at http://example.org/ccc/
I want to redirect anything outside of the /ccc/ directory to a different website.
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/ccc/?.*
RewriteRule ^(.*)$ https://new-website.com/$1 [L]
But this code doesn't work, it redirects the /ccc/ directory. According to my research and testing with this htaccess tester, it should not redirect because the RewriteCond is checking against /ccc with optional slash and other characters after it.
What is happening? Does this look correct?
Edit: This method from this answer is also not working, the CCC domain is being redirected:
RewriteEngine on
RewriteRule ^ccc index.php [L]
RewriteRule (.*) https://new-website.com/$1 [R=301,L]
PHP 5.4.45, Apache/2.2.31
Assuming ccc/ directory doesn't have a separate .htaccess, you may use this rule:
RewriteEngine on
RewriteCond %{THE_REQUEST} !\s/ccc[/?\s] [NC]
RewriteRule ^ https://new-website.com%{REQUEST_URI} [L,R=301,NE]
THE_REQUEST variable represents original request received by Apache from your browser and it doesn't get overwritten after execution of other rewrite directives. An example value of this variable is GET /index.php?id=123 HTTP/1.1
It looks like [L] isn't behaving normally and I'm guessing it's the old version of Apache (2.2.31) because these rules worked on a separate website. I found this solution which seemed to work for this case, the third line below:
RewriteEngine on
RewriteRule ^ccc/? index.php [L]
RewriteCond %{ENV:REDIRECT_STATUS} != 200
RewriteRule ^(.*)$ https://new-website.com/$1 [L]
Explanation from that question:
The problem is that once the [L] flag is processed, all the next RewriteRules are indeed ignored, however, the file gets processed AGAIN from the begin, now with the new url.
This magic Condition will not process the catch all if the file was already redirected.

Need Assistance for this Htaccess Rewrite Rule

I have a problem with my .htaccess, a short explanation I would like to set http://example.com/newest on my website. However, it always redirects to http://example.com/postname. Where I just need the exact "newest" page. Here is my code:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteBase /
RewriteRule ^[^/]+$ %{REQUEST_URI}/ [L,R=301]
RewriteRule ^/category/(.*)$ page.php?f=$1
RewriteRule ^/search/(.*)$ search.php?f=$1
RewriteRule ^(.*)/$ post.php?f=$1 <- If this is removed, my post htaccess will not work
RewriteRule ^newest/$ index.php?f=newest <- I want to execute this code
I really don't know what this is called, I have been looking for the whole stackoverflow but I did not get any answer. Please remain me if this is a duplicate question.
As Mohammed implied in comments, your directives are in the wrong order. The line above your "newest" rewrite is a catch-all and rewrites all requests, so the last line will never match.
http://example.com/newest
Note that your rules imply that your URLs should end in a trailing slash. So, you should be linking to http://example.com/newest/ (with a trailing slash), not http://example.com/newest, otherwise your users will get a lot of unnecessary redirects.
However, you appear to be under the belief that the RewriteCond directive applies to all the directives that follow. This is not the case. It only applies to the first RewriteCond directive. You also need some L flags to prevent further processing.
You also have a slash prefix on the "category" and "search" rewrite patterns, so these would never match in a .htaccess context.
Try something like the following instead:
RewriteEngine On
RewriteBase /
# Don't process the request further if it maps to an existing file
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
# Append trailing if omitted
# Although strictly speaking this only redirects if there are no slashes at all in the URL
RewriteRule ^[^/]+$ %{REQUEST_URI}/ [L,R=301]
RewriteRule ^category/(.*)$ page.php?f=$1 [L]
RewriteRule ^search/(.*)$ search.php?f=$1 [L]
RewriteRule ^newest/$ index.php?f=newest [L]
RewriteRule ^(.*)/$ post.php?f=$1 [L]

htaccess not redirecting url

So I've been stuck on this a bit, and its more or less put a top on my project.
Just started up at a new job, and my current assignment is to create an API system for a new project we're launching. Its based on a modified version of wordpress, and my objective here is to take any request that goes to /contrib/api and instead have it direct to a file I have setup to sort out the incoming URL and redirect accordingly.
Here's what I currently have:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /contrib/
RewriteCond %{REQUEST_URI} ^/api/
RewriteRule ^/content/apis/apis\.php$ - [L]
RewriteRule ^index\.php$ - [L]
# uploaded files -- no longer needed since we're serving files directly via apache
# RewriteRule ^([_0-9a-zA-Z-]+/)?files/(.+) wp-includes/ms-files.php?file=$2 [L]
# add a trailing slash to /network and /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin/network$ $1wp-admin/network/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) wordpress/$2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ wordpress/$2 [L]
RewriteRule . /contrib/index.php [L]
</IfModule>
This was all here before me, except for near the top:
RewriteCond %{REQUEST_URI} ^/api/
RewriteRule ^/content/apis/apis\.php$ - [L]
My goal, as stated, is to take any request from /contrib/api/ and redirect it to my file instead of continuing. Instead, it seems to skip on and go ahead and point to the wp-admin (after a redirect I think). Regardless, it seems my command isn't getting caught, and I'm not sure why.
I thought that the commands are processed first come, but if its continuing on, would moving my code to the end make it work?
I know this is an easy thing to test, but I was hoping I could get some insight on what I'm doing/doing wrong, plus I don't yet have access to the server to actually upload/test code without going through my boss, and I'm thinking 2 weeks in, probably not a good idea to ask my boss to test code every 5 minutes, heh :(
EDIT: As per Bob Vale's notice, fixed the path issue.
You don't need the RewriteCond as the rule will be doing the match anyhow, you just need
RewriteRule ^contrib/api/ /content/apis/apis.php [L]
This will rewrite any url starting with contrib/api to the file /content/apis/apis.php

RewriteRule For Matching Arbitrary PHP Files

I'm somewhat new to htaccess rewrite rules, and have been scratching my head for the past few days on what's happening here. No amount of Googling seemed to help, so hopefully somebody knows the answer.
I have a site that can be accessed as:
www.site.com
www.site.com/684
www.site.com/684/some-slug-name-here
All of these scenarios should go to index.php and pass in the optional id=684 and slug=some-slug-name-here
Which works fine.
My problem is I have a separate file. Right now it's called admintagger.php - but this fails when I call it anything. 21g12fjhg2349yf234f.php has the same issue.
The problem is that that I would like to be able to access admintagger.php from www.site.com/admintagger
but it seems to be matching my rule for index, and taking me there instead.
Here is my code:
Options +FollowSymLinks
RewriteEngine On
RewriteBase /
RewriteRule ^imagetagger$ /imagetagger.php [NC,QSA]
RewriteRule ^([0-9]+)/?(.*)?/?$ index.php?id=$1&slug=$2 [NC,L,QSA]
If you want to arbitrarily be able to access php files via the name (sans extension) then you need to create a general rule for it. But you need to be careful otherwise you may be rewriting legitimate requests for existing resources (like a directory, or a slug). Try this instead:
# make sure we aren't clobbering legit requests:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# see if appending a ".php" to the end of the request will map to an existing file
RewriteCond %{REQUEST_FILENAME}.php -f
# internally rewrite to include the .php
RewriteRule ^(.*)$ /$1.php [L]
Then you can have your routing to index.php right after that:
RewriteRule ^([0-9]+)/?(.*)?/?$ index.php?id=$1&slug=$2 [NC,L,QSA]
Although you may be better off create a separate rule for each of your 3 cases:
RewriteRule ^([0-9]+)/([^/]+)/?$ /index.php?id=$1&slug=$2 [NC,L,QSA]
RewriteRule ^([0-9]+)/?$ /index.php?id=$1 [NC,L,QSA]
RewriteRule ^$ /index.php [L]

.htaccess not working for a specific case

I am facing a strange issue while working with .htaccess file.
Here is my file:
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine On
RewriteRule ^(admin)($|/) - [L]
RewriteRule ^$ ./web/view/
RewriteRule ^([A-Za-z0-9]+)$ ./web/view/?module=$1
RewriteRule ^([A-Za-z0-9]+)/$ ./web/view/?module=$1
RewriteRule ^([A-Za-z0-9]+)/([A-Za-z0-9]+)/([0-9]+)$ ./web/view/?module=$1&id1=$2&id2=$3
RewriteRule ^([A-Za-z0-9]+)/([A-Za-z0-9]+)/([0-9]+)/$ ./web/view/?module=$1&id1=$2&id2=$3
RewriteRule ^([A-Za-z0-9]+)/([A-Za-z0-9]+)$ ./web/view/?module=$1&id1=$2
RewriteRule ^([A-Za-z0-9]+)/([0-9]+)/$ ./web/view/?module=$1&id1=$2
ErrorDocument 404 err.php
</IfModule>
Now in the last rule, if I add [a-z] along with [0-9], I am getting a 500 error. E.g., the below line will give me 500 error:
RewriteRule ^([A-Za-z0-9]+)/([a-z0-9]+)/$ ./web/view/?module=$1&id1=$2
However, if I use A-Z, it is working fine.
RewriteRule ^([A-Za-z0-9]+)/([A-Z0-9]+)/$ ./web/view/?module=$1&id1=$2
Even [NC] is also giving me same error.
Can you help me to identify and correct this problem?
Now in the last rule, if I add [a-z] along with [0-9], I am getting a 500 error. E.g., the below line will give me 500 error:
The reason is because the rewrite engine is going into an infinite loop:
request URI looks like /something/id/ (note the trailing slash)
it matches the rule: RewriteRule ^([A-Za-z0-9]+)/([a-z0-9]+)/$ ./web/view/?module=$1&id1=$2
URI gets rewritten to /web/view/?module=something&id=id
new URI is put back into the rewrite engine, new URI = /web/view/
URI matches the (same) rule: RewriteRule ^([A-Za-z0-9]+)/([a-z0-9]+)/$ ./web/view/?module=$1&id1=$2
URI gets rewritten to /web/view/?module=web&id=view
repeat from step 4
You need to add a condition to prevent it from looping:
RewriteCond %{REQUEST_URI} !/web/view
RewriteRule ^([A-Za-z0-9]+)/([A-Za-z0-9]+)/$ ./web/view/?module=$1&id1=$2
My first instinct is to add a [L] at the end of each rewrite line, for example:
RewriteRule ^([A-Za-z0-9]+)$ ./web/view/?module=$1 [L]
This works just fine since your rewrites are independent of each other.
The second step is to consolidate each group of RewriteRule directives where the forward slash (/) is optional, so instead of:
RewriteRule ^([A-Za-z0-9]+)$ ./web/view/?module=$1
RewriteRule ^([A-Za-z0-9]+)/$ ./web/view/?module=$1
It is just as safe to say:
RewriteRule ^([A-Za-z0-9]+)/?$ ./web/view/?module=$1
The question mark (?) means 0 or 1 but not more than the preceding character, group or expression.
Lastly you may consider optimizing the group of RewriteRule directives:
<IfModule mod_rewrite.c>
Options +FollowSymlinks
RewriteEngine On
RewriteRule ^(admin)($|/) - [L]
RewriteRule ^$ ./web/view/ [L]
RewriteRule ^([A-Za-z0-9]+)/?((([A-Za-z0-9]+)/?)?(([A-Za-z0-9]+)/?)?)?$ ./web/view/?module=$1&id1=$4&id2=$6 [L]
ErrorDocument 404 err.php
</IfModule>
And as bonus information, it may be worth your while to use the QSA (query string append) flag, for example:
RewriteRule ^$ ./web/view/ [L,QSA]
You may also consider using gskinner's RegExr tool to help you create, test or troubleshoot your regular expressions.
Lasty, in your .htaccess file, depending on how the AllowOverrides directive is defined, you may be able to turn on mod_rewrite logging:
RewriteLog "/path/to/your/home/dir/rewrite.log"
RewriteLogLevel 4
However, depending on your apache server version or configuration, you may be required to use the per-module logging configuration since RewriteLog/Level is deprecated:
LogLevel alert rewrite:trace4
As a disclaimer, this level of logging will slow down your webserver, so do not enable it for too long nor on a production environment.

Resources