Rewrite rules for CMS fail after installing ssl certificate - apache

I wrote a content management system that uses rewrite rules to map urls to controller, action and argument query strings.
I use two .htaccess files. One is in my site's root directory. This one forwards all requests to a sub directory, which depends on the domain name used for the request:
SetEnv HTTP_MOD_REWRITE On
RewriteEngine on
# mapp requests that don't start with www to https://www
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^(.*)$ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# BEGIN Domain to folder mapping
########################
# pointing example.com to subfolder 'example'
ReWriteCond %{HTTP_HOST} (www\.)?example.com
ReWriteCond %{REQUEST_URI} !example/
ReWriteRule ^(.*)$ example/$1 [L]
# END Domain to folder mapping
A second .htaccess file that maps the request to the actual query string is in the subdirectory that gets mapped to in the first rewrite step:
SetEnv HTTP_MOD_REWRITE On
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ([^/]*)([/]*)([^/]*)([/]*)(.*) index.php?controller=$1&action=$3&args=$3 [L,QSA]
The result of all this, before I installed my ssl certificates, was that a request that looks like this:
example.com/hello/foo/123
would map to:
http://www.example.com?controller=hello&action=foo&args=123
Now I just get sent to the index file. If I enter the actual query string, I get the correct result.
I looked through my server's rewrite logs and it looks like there is an additional rewrite step that gets executed without me being able to figure out why. I think this is the relevant stuff from the rewrite logs:
[rewrite:trace3] [www.example.com/sid#8021c1788][rid#807418748/initial/redir#1] [perdir /fs6c/example/public/example/] strip per-dir prefix: /fs6c/example/public/example/hello -> hello
[rewrite:trace3] [www.example.com/sid#8021c1788][rid#807418748/initial/redir#1] [perdir /fs6c/example/public/example/] applying pattern '([^/]*)([/]*)([^/]*)([/]*)(.*)' to uri 'hello'
[rewrite:trace2] [www.example.com/sid#8021c1788][rid#807418748/initial/redir#1] [perdir /fs6c/example/public/example/] rewrite 'hello' -> 'index.php?controller=hello&action=&args='
[rewrite:trace3] [www.example.com/sid#8021c1788][rid#807418748/initial/redir#1] split uri=index.php?controller=hello&action=&args= -> uri=index.php, args=controller=hello&action=&args=
I am assuming the [L] flag is the culprit, but I don't understand why this wasn't an issue before I switched to ssl.
I am using a shared hosting service, and they had to install the certificates for me, since they don't expose the necessary parts of the server to the user.

I still don't know WHY this is happening, but I was able to find a way to make it work with the ssl certificate. These are the relevant rewrite rules in the htaccess file located in the root directory:
# for existing files, redirect to subdirectory
ReWriteCond %{HTTP_HOST} ^(www\.)?example.com
ReWriteCond %{REQUEST_URI} !example/
RewriteCond %{DOCUMENT_ROOT}/example/$1 -f
ReWriteRule ^(.*) example/$1 [L]
# for non-existing files, map to index.php with CMS arguments
ReWriteCond %{HTTP_HOST} ^(www\.)?example.com
# file exists, but is index.php
ReWriteCond %{REQUEST_URI} index.php [OR]
# file doesn't exist
RewriteCond %{REQUEST_FILENAME} !-f
ReWriteRule ^([^/]*)([/]*)([^/]*)([/]*)(.*) example/index.php?controller=$1&action=$3&args=$5 [L,QSA]
I eliminated the second htaccess file.
I hope this helps someone in the future. The hosting provider is NearlyFreeSpeech. If anyone has any clue why my old rules didn't work, I would really appreciate to know!

Related

Dynamically pointing multiple domains to a single domain's subfolder with .htaccess

In this example, I manage domain.com. Inside it, I have a store template in php that loads the selected store dinamically:
domain.com/store1
domain.com/store2
domain.com/store3
The nice urls are being generated by the .htaccess below. What's really being loaded in the back, is this:
domain.com/store/index.php?store=store1
domain.com/store/index.php?store=store2
domain.com/store/index.php?store=store3
Each store has internal links, such as these:
domain.com/store1/catalogue
domain.com/store1/catalogue/instruments
domain.com/store1/catalogue/instruments/guitars
domain.com/store1/electric-guitar/1337
It works exactly as expected. These are the .htaccess rules to make that work in domain.com. The store query string (store1, store2, store3) in each RewriteRule determines which store is being loaded:
# permalinks
RewriteEngine on
# errors
ErrorDocument 404 /error.php?error=404
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]
#################################################
# store
RewriteRule ^([0-9a-zA-Z-]{2,50})/?$ store/index.php?store=$1 [QSA,L]
# store: catalogue
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/?$ store/catalogue.php?store=$1 [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=$1&cat=$2 [QSA,L]
RewriteRule ^([0-9a-zA-Z-]{2,50})/catalogue/([0-9a-zA-Z-]{1,75})/([0-9a-zA-Z-]{1,75})/?$ store/catalogue.php?store=$1&cat=$2&subcat=$3 [QSA,L]
# store: products
RewriteRule ^([0-9a-zA-Z-]{2,50})/([0-9a-zA-Z-]{1,150})/([0-9]+)$ store/product.php?store=$1&slug=$2&id_product=$3 [QSA,L]
Now, here comes the tricky part. Some of the clients, would like to use their own domains, so that store1.com/* loads the same content of domain.com/store1/* (without using a redirect).
Example:
store1.com => domain.com/store1/
store1.com/catalogue => domain.com/store1/catalogue
store1.com/catalogue/instruments => domain.com/store1/catalogue/instruments
store1.com/catalogue/instruments/guitars => domain.com/store1/catalogue/instruments/guitars
store1.com/electric-guitar/1337 => domain.com/store1/electric-guitar/1337
You get the idea.
All the domains (domain.com, store1.com, store2.com, store3.com) are configured in the same Apache Web Server environment (using virtual hosts).
The question is: Can this be implemented in a .htaccess file in each one of the store's domain root path dynamically or inside the virtualhost .conf file? If so, how?
Make sure all your custom "store" domains (eg. store1.com, store2.com, etc.) are defined as ServerAlias in the domain.com vHost and so resolve to the same place as domain.com.
You can then rewrite requests for the custom store domain to prefix the URL-path with the store-id (the domain name) and then use your existing rules unaltered.
For example, a request for store1.com/catalogue/instruments is internally rewritten to /store1/catalogue/instruments and then processed by the existing rules as usual.
The following directives should go before the existing # store rules:
# Internally rewrite the request when a custom domain is used
# - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 !^/([^/]+)/.*#\1
RewriteRule (.*) %1/$1
And that's basically it, although you may also decide to implement a canonical redirect - see below.
Explanation of the above directives:
The first condition simply excludes requests for the main domain.com (which should already have the relevant store-id prefixed to the URL-path).
%{REQUEST_URI} !\.\w{2,4}$ - The second condition avoids rewriting requests for static resources (images, JS, CSS, etc.). Specifically, it excludes any request that ends in - what looks like - a file extension.
%{HTTP_HOST} ^([^.]+) - The third condition captures the requested domain name before the TLD which is then accessible using the %1 backreference later and used to prefix the URL-path. This assumes there is no www subdomain (as stated in comments). eg. Given a request for store1.com or store2.co.uk, store1 or store2 respectively are captured.
%{REQUEST_URI}#%1 !^/([^/]+)/.*#\1 - The fourth condition checks that the URL-path is not already prefixed with the domain name (captured above). This is primarily to ensure that rewritten requests are not rewritten again, causing a rewrite loop. This is achieved using an internal backreference (\1) that compares the first path-segment in the URL-path against the previously captured domain name (%1).
(.*) %1/$1 - Finally, the request is internally rewritten, prefixing the domain name to the requested URL-path.
Ignore existing files (may not be required)
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .* - [L]
You've not stated how you are referencing your static resources (images, JS, CSS, etc.), but you may need to modify this rule to also match requests for existing files. (Although the condition I added to the rule above may already be sufficient to exclude these requests.) For example:
# ignore existing directories or files
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ - [L]
Canonical redirect (optional)
You should also consider redirecting any direct requests of the form store1.com/store1/catalogue/instruments back to the canonical URL store1.com/catalogue/instruments - should these URLs ever be exposed/discovered. A request of the form store1.com/store2/catalogue/instruments will naturally result in a 404, so is not an issue.
For example, the following would go immediately after the ErrorDocument directive in your existing rules:
# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 ^/([^/]+)/.*#\1
RewriteRule ^(?:[^/]+)(/.*) $1 [R=301,L]
Test first with a 302 (temporary) redirect to avoid potential caching issues.
This is basically the reverse of the above rewrite, but applies to direct requests only. The check against the REDIRECT_STATUS env var ensures that only direct requests from the client and not rewritten requests by the later rewrite are processed.
Summary
With the two rule blocks in place...
# permalinks
RewriteEngine on
# errors
ErrorDocument 404 /error.php?error=404
# Redirect to remove the "/store1" URL-prefix when the "store1.com" domain is requested.
# - Only applies to direct requests.
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteCond %{HTTP_HOST} !^domain\. [NC]
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 ^/([^/]+)/.*#\1
RewriteRule ^(?:[^/]+)(/.*) $1 [R=301,L]
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Internally rewrite the request when a custom domain is used
# - the URL-path is prefixed with the domain name
RewriteCond %{HTTP_HOST} !^(www\.)?domain\. [NC]
RewriteCond %{REQUEST_URI} !\.\w{2,4}$
RewriteCond %{HTTP_HOST} ^([^.]+)
RewriteCond %{REQUEST_URI}#%1 !^/([^/]+)/.*#\1
RewriteRule (.*) %1/$1
#################################################
# store
:
: existing directives follow
:

Rewrite request in subdirectory after rewriting into that subdirectory

I have following file structure in my server:
/
source/ # source PHP files (not accessible)
public/ # publicly accessible files (media)
.htaccess # second level htaccess to rewrite path into variable
index.php # request collector
.htaccess # first level htaccess to redirect flow into public dir
In first level htaccess I have rewrite rule moves flow into "public" directory.
RewriteRule ^(?!public/).*$ public/$0 [L,NC]
"public/.htaccess" contains following code:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{QUERY_STRING} !index.php
RewriteRule ^(.*)$ index.php/$1 [QSA,L]
So if requesting existing file it is served. Otherwise request is rewrited like "example.com/namespace/controller/action?var=blah" to "example.com/index.php/namespace/controller/action?var=blah"
And that request should be handled by "public/index.php". However it seems that multiple rewriting is not allowed. Am I right? Or maybe I am making something wrong. I have to admit I can not understand mod_rewrite.

htaccess pretty urls not working

Folder structure:
- assets
- all css / js
- calsses
- all models, db ant etc
- views
- admin
- app
- index.php
- customers.php
.......
my .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^(www.)?localhost:8080$
RewriteRule ^(.*)$ /views/$1
RewriteRule ^(/)?$ /views/index.php [L]
address : localhost:8080/app/ - working fine, but then I try to add pretty url for example in my customers.php - localhost:8080/app/customers.php?id=5 change to localhost:8080/app/customers/id/5
htaccess added new line:
RewriteRule /id/(.*) customers.php?id=$1
It's not working, it always return 500 Internal Server Error there could be the problem?
plus Need all urls without .php extend
You'd have to include those conditions for every rule. You'd be better off just rewriting everything to, say views/router.php then using PHP to include the different controllers, or serve a 404 when the URL isn't valid.
RewriteRule !^views/router\.php$ views/router.php [NS,L,DPI]
I agree with Walf in that handling routes through a router class is a better idea (especially in the long run!) than using .htaccess redirects.
However, as your question seems to be more about why is this not working than about how you should do it, here is an explanation for what is going on.
I will be using these URLs as examples:
localhost:8080
localhost:8080/app
localhost:8080/app/customers/id/5
Your first rule:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTP_HOST} ^(www.)?localhost:8080$
RewriteRule ^(.*)$ /views/$1
As you intended, this RewriteRule will match any URL which is not a file, not a directory, and made to localhost:8080.
localhost:8080 # not matched because it leads to a directory.
localhost:8080/app -> localhost:8080/views/app
localhost:8080/app/customers/id/5 -> localhost:8080/views/app/customers/id/5
Your next rule:
RewriteRule ^(/)?$ /views/index.php [L]
It is important to realize that RewriteCond statements apply only to the first RewriteRule following them, thus all that is being checked here is the path.
Side note: ^(/)?$, as you are not using $1, can be simplified to ^/?$.
localhost:8080 -> localhost:8080/views/index.php
localhost:8080/views/app # not matched
localhost:8080/views/app/customers/id/5 # not matched
As the L flag is specified, Apache will immediately stop the current iteration and start matching again from the top. The documentation is badly worded. Thus, localhost:8080/views/index.php will be run through the first rule, fail to match, be run through this rule, fail to match, and then as no other rules exist to check (yet) no rewrite will be done.
Now lets look at what happens when you add your broken rule.
RewriteRule /id/(.*) customers.php?id=$1
There are a few problems here. First, as you don't require that the URL start with /id/ the rule will always match a URL that contains /id/, even if you have already rewritten the URL. If you amended this by using ^/id/(.*), then you would still have issues as the string that the rewrite RegEx is tested against has leading slashes removed. Lastly and most importantly, customers.php does not exist in your root directory.
localhost:8080/views/index.php # not matched
localhost:8080/views/app # not matched
localhost:8080/views/app/customers/id/5 -> localhost:8080/customers.php?id=5
This is the last rule in your file currently, so now Apache will start over. customers.php does not exist in your directory, so it will be rewritten to views/customers.php. No other rules matched, but the URL has changed and so Apache will start over again, as /views/customers.php does not exist, it will be rewritten to /views/views/customers.php ... This pattern will repeat until you hit the maximum iteration limit and Apache responds with a 500 error.
You can solve this several ways. Here would be my preferred method, but only if you cannot use a router.
RewriteEngine on
# Rewrite the main page, even though it is a directory
RewriteRule ^/?$ views/index.php [END]
# Don't rewrite any existing files or directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .? - [S=999,END]
RewriteRule ^app/?$ views/app/index.php [END]
RewriteRule ^app/id/(.*)$ views/app/customers.php?id=$1 [END]
TL;DR Use a PHP based router. .htaccess rules can be incredibly confusing.
Please refer to the question, How to make Clean URLs
I think this is what you needed.
you can use RewriteRule ^(.*)$ index.php [QSA,L]
Having another crack.
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{HTTP_HOST} !^(?:www\.)?localhost:8080$ [OR]
RewriteCond $0 =views
RewriteRule [^/]* - [END]
RewriteRule ^(app|admin)/([^/]+) views/$1/$2.php [DPI,END]
RewriteRule ^(app|admin)/?$ views/$1/index.php [DPI,END]
You may have to use L instead of END flags if your Apache is older. Set up an ErrorDocument for 404s, too.
Don't muck around with query strings, just parse $_SERVER['REQUEST_URI'] in PHP, e.g. start by exploding it on /. Then you'll have all the parameters of the original pretty URL. You can do that part in an include so each controller can reuse the same code.
I tried your structure and .htaccess file myself and found an endless loop in the apache logs. I bet you got something like this:
Mon Nov 28 19:57:32.527765 2016] [core:error] [pid 10] [client 172.18.0.1:35048] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
I could fix it by adding the last rule like:
RewriteRule id/(.*) /views/app/customers.php?id=$1
The leading / is not needed for the match and the target needs the full path. Note that I got the id double (e.g. 123/123) on the url: http://localhost:8080/id/123.
This is caused by one of the 2 previous rules (removing them fixes it) so you might need to change them.
Here is what you want :
RewriteEngine On
RewriteBase /app/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^\/?$ views/index.php [L]
RewriteRule ^([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)\/([a-zA-Z0-9]+)\/?$ views/$1.php?$2=$3 [L]
RewriteRule ^([a-zA-Z0-9]+)\/?$ views/$1.php [L]

www I added to HTTP_HOST via htaccess file disappears when redirecting to second htaccess file

I am struggling with an htaccess file. Actually, it is two files.
But first things first, so my questions are:
1) Why does my .htaccess(1) file add the www at the beginning of the HTTP_HOST and the slash at the end of folder REQUEST_URI IF AND ONLY IF the .htaccess(2) file is not there (deleted or renamed)?
2) What is wrong with the RewriteRule and conditions that I wrote in .htaccess(2) to redirect the REQUEST_URI to /publicfolder/REQUEST_URI? Conditions doesn't seem to work and when I surf to domain.com/nonpublicfolder it goes to domain.com/domainfolder/publicfolder/nonpublicfolder.
My website is structured as follows:
/
.htaccess(1)
domainfolder/
.htaccess(2)
publicfolder/
genericfolder/
index.extention
file.extention
nonpublicfolder/
So I have one htaccess file in the root folder ( .htaccess(1) ) where I:
add 'www' at the beginning of the HTTP_HOST;
add '/' at the end of REQUEST_URI if it does not end with a file extension;
redirect domain.com/anyfolder/anyfile.extention to domain.com/domainfolder/anyfolder/anyfile.extention;
like so:
<IfModule mod_rewrite.c>
# System symbolic links are allowed.
Options +FollowSymlinks
# Runtime rewriting engine enabled.
RewriteEngine On
# HTTP_HOST starts with 'www'.
RewriteCond %{HTTP_HOST} ^(?!www\.) [NC]
RewriteRule ^.*$ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,NC]
# Folder requests end with '/'.
RewriteCond %{REQUEST_URI} ![^/]+\.[a-zA-Z0-9]+$ [NC]
RewriteRule [^/]$ %{REQUEST_URI}/ [R=301,NC]
# Files and folders are in the 'domainfolder' folder.
RewriteCond %{REQUEST_URI} !^/?domainfolder/ [NC]
RewriteRule ^.*$ /domainfolder%{REQUEST_URI} [R=301,NC]
</IfModule>
And then I have my .htaccess(2) file - in the domainfolder folder - where I redirect files and folders requests to the publicfolder folder IF AND ONLY IF they are not pointing to the notpublicfolder folder or to the Google Site Verification file:
<IfModule mod_rewrite.c>
# Runtime rewriting engine enabled.
RewriteEngine On
# Public files and folders are in the 'publicfolder' folder.
RewriteCond %{REQUEST_URI} !^/?domainfolder/publicfolder/ [NC]
RewriteCond %{REQUEST_URI} !^/?domainfolder/nonpublicfolder/ [NC]
RewriteCond %{REQUEST_URI} !^/?domainfolder/googlexxx.html$ [NC]
RewriteRule ^/?(.+)$ /publicfolder/$1 [R=301,NC,L]
</IfModule>
Thank you very much for your time and patience.
(1) mod_rewrite does multiple passes and on each by default it will only open the first .htaccess file it finds walking up the folder hierarchy from the requested file. read my Tips for debugging .htaccess rewrite rules for more discussion of this. Yes you can use a Options setting to change this behaviour but this has a performance hit and I would suggest that you avoid doing so.
(2) When using hierachical .htaccess files, mod_rewrite has to associate URI path to the current directory and can get this wrong. The RewriteBase directive tells mod_rewrite what the true association is, so use this.
Rule order is important. If you don't have a local Apache instance where you have root privilege and can enable rewrite logging, you need to build up your access file(s) incrementally rule-by-rule, testing at each step because you only get a work/doesn't work return. Again my tips explains how to do this.

http/https Rewrite not working, adds index.php to path

I'm trying to add some secure pages to a site. The links in the site all use the current protocol (i.e. are protocol independent, paths start with //).
I need the paths /info/season-tickets/* and /ticketcontroller/* to use https, and all others using http.
I've tried building rules to do the following (ignoring the ticketcontroller part for now):
If Port==80 and Path==/info/season-tickets/, rewrite with https
If Port==443 and Path!=/info/season-tickets/, rewrite with http
However, when I access /info/season-tickets/, rather than redirecting to the https version, I get example.com/index.php/info/season-tickets
The .htaccess is below - my attempts are below # Force https on certain pages and # Force http everywhere else, and the other bits are from the Kohana framework
# Turn on URL rewriting
RewriteEngine On
# Installation directory
RewriteBase /
# Force https on certain pages
RewriteCond %{SERVER_PORT} 80
RewriteCond %{REQUEST_URI} ^/info/season-tickets/?
RewriteRule ^(.*)$ https://www.example.com/$1 [R,L]
# Force http everywhere else
RewriteCond %{SERVER_PORT} 443
RewriteCond %{REQUEST_URI} !^/info/season-tickets/?
RewriteRule ^(.*)$ http://www.example.com/$1 [R,L]
# Protect hidden files from being viewed
<Files .*>
Order Deny,Allow
Deny From All
</Files>
# Protect application and system files from being viewed
RewriteRule ^(?:application|modules|system)\b index.php/$0 [L]
# Allow any files or directories that exist to be displayed directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Rewrite all other URLs to index.php/URL
RewriteRule .* index.php/$0 [PT]
I tried re-ordering the rules to see if that fixed it, but it didn't.
Any ideas why this isn't working (http://htaccess.madewithlove.be/ shows that it should work)...
Thanks!
I've solved this issue, not with htaccess, but in the index.php file (all requests go through this).
By default, I assume port 80. Then, if $_SERVER['REQUEST_URI'] is in an array of secure paths, I switch the variable to 443.
Then, if $required_port != $_SERVER['SERVER_PORT'], I redirect and exit().