Apache mod_rewrite for specific folders and paths - apache

I have found dozens of articles online on how to setup mod_rewrites but for the love of God I can't figure out how to PROPERLY force HTTPS on ALL pages and after that force HTTP on certain directories or (already rewritten) pages.
Now this one gets really tricky as I need HTTPS on this directory, except for two cases, such as "/surf" which actually is rewritten from "surf.php", and "promote-([0-9a-zA-Z-]+)$" which is rewritten from "promote.php?user=$1" :
<Directory /home/rotate/public_html/ptp/>
AllowOverride None
Order Deny,Allow
Allow from all
Options +SymLinksIfOwnerMatch
ErrorDocument 404 "<h1>Oops! Couldn't find that page.</h1>"
RewriteEngine On
RewriteRule ^promote-([0-9]+)$ promote.php?user=$1 [QSA,NC,L]
RewriteRule ^([^.?]+)$ %{REQUEST_URI}.php [L]
</Directory>
I have tried some stuff but which only resulted in some weird redirection loops...
RewriteCond %{HTTPS} on
RewriteRule !^(surf|promote-([0-9]+)$) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
So basically I need to force HTTPS everywhere in /ptp/ except /ptp/surf (which is rewritten from surf.php AND /ptp/promote-123 which is rewritten from promote.php?user=123
Currently I'm using PHP to redirect to HTTP or HTTPS as per my needs but I know that it would be much faster if I could manage to do it via rewrites.
Any pointers, tips, suggestions? Thanks.
UPDATE2: This worked:
RewriteCond %{HTTPS} off
RewriteRule !^(surf|promote(-[0-9]+)?) https://%{HTTP_HOST}%{REQUEST_URI} [R=301]
RewriteRule ^promote-([0-9]+)$ promote.php?user=$1 [NC,L]
RewriteRule ^([^.?]+)$ %{REQUEST_URI}.php
However, the resources such as javascript, fonts etc, were being blocked by the browser, unless I specified absolute HTTPS paths. Note that this never happened when redirecting through PHP...

I changed a little bit and it works perfectly
Changes
Remove the Change the RewriteRule to match file to .php to bottom.
Remove the $ sign that is End of the pattern
As Said in the update promote-1111 will redirect to promote.php?user=$1 change the promote-[0-9]+ to promote(-[0-9]+)? otherwise it will override in the second redirection as you redirecting it to promote.php?user=$1
The Code
RewriteCond %{HTTPS} off
RewriteRule !^(surf|promote(-[0-9]+)?) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
RewriteRule ^([^.?]+)$ %{REQUEST_URI}.php [L]
The page surf
The Page Index
Never mind the error message shown in this image. Since I tried it from my localhost, it won't have a certificate.
Will work with servers

Your rules aren't in the "update" working because of side effects of using <Directory> context. Each substitution starts processing again.
When you request /promote-123 and rewrite it to put the numbers in the query string, you can't then match the numbers as if they're still in the path. You'll need to match the rewriten path and the numbers with RewriteCond %{QUERY_STRING} (if you care about the numbers)

Related

apache RewriteRule not working

I have this url http://www.example.com/Courses/get/38789/my-course, i added this rule to the .htaccess file
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/Courses/get
RewriteRule ^Courses/get/(.*)/(.*)$ course-$1-$2 [R=301,L]
but when i go to http://www.example.com/Courses/get/38789/my-course nothing happens, i stay on the same page, there is no redirect.
p.s the link is just an example not the actual link
A more efficient method would be to use the following:
RewriteRule ^Courses/get/(\d+)/([^/]+)/?$ /course-$1-$2 [R=301,L]
Now, keep in mind that this rule should come before any rules that may rewrite the request to, say, an index.php file. This would be naturally true if the code you posted in your question was all of your code. If not, please post your entire .htaccess file so we can be sure it is being placed in the right location.
Be sure the mod_rewrite is turned on, and the you have set AllowOverride All in your virtual host/Apache site configuration. If you're running on a shared production server, this would not apply to you.
Side note: Whilst it does work, you need not use RewriteEngine on twice - only once at the beginning of your file will suffice. You also do not need RewriteCond %{REQUEST_URI} ^/Courses/get - it is essentially redundant as you are already using an expression to test against the request.

Rewrite part of query string with apache mod_rewrite

Need help with rewriting part of query string with mod_rewrite.
Looked through a lot of resources and have a general understanding of how stuff works but cannot figure out correct solution
This link:
http://example.com/?param=home&shop_id=1000005620&ate=bow&b_uid=-1&tg=one
Has to become this
http://example.com/?param=home/#/shop/1000005620?ate=bow&b_uid=-1&tg=one
If shorter, then this part of query string
&shop_id=1000005620& transforms into /#/shop/1000005620?
UPDATE:
Answer gave me a clear understanding what I had to do.
Exact rules that fixed my issue were like this:
<IfModule mod_rewrite.c>
RewriteCond %{QUERY_STRING} ^(.*)&shop_id=([0-9]{10,12})(?:&)(.*)$
RewriteRule ^(.*)$ https://%{SERVER_NAME}/$1?%1/#/shop/%2\?%3 [NE,L,R]
</IfModule>
<IfModule mod_rewrite.c>
RewriteCond %{QUERY_STRING} ^shop_id=([0-9]{10,12})$
RewriteRule ^(.*)$ https://%{SERVER_NAME}/#/shop/%1? [NE,L,R]
</IfModule>
Reason for this rewrite rules is hash handling with safari and ie
I have links that are used by external pages and also I have a redirection to https if site request http. If there is a hash in the link and request is sent from Safari or IE hash goes away from the URL and does not come back after redirection. I want to note very important fact that Chrome, Firefox do not have problems with keeping the URL with hash even after redirect to https. This involved to reconstruct our URL but it is worth it and now everything is working as it should.
You can try:
RewriteEngine On
RewriteCond %{QUERY_STRING} ^(.*)&shop_id=([^&]+)&?(.*)$
RewriteRule ^(.*)$ /$1?%1/#shop_id=%2?%3 [L,R,NE]
EDIT:
what about if I only what to rewrite http://example.com/?shop_id=1000005620 into http://example.com/#/shop/1000005620 what the rewrite rule be then?
Just change the relevant parts of the regex pattern:
RewriteEngine On
RewriteCond %{QUERY_STRING} ^shop_id=([^&]+)$
RewriteRule ^(.*)$ /$1/#shop_id=%2? [L,R,NE]

Apache .htaccess RewriteRule

Here's my situation. I have a web root and several subdirectories, let's say:
/var/www
/var/www/site1
/var/www/site2
Due to certain limitations, I need the ability to keep one single domain and have separate folders like this. This will work fine for me, but many JS and CSS references in both sites point to things like:
"/js/file.js"
"/css/file.css"
Because these files are referenced absolutely, they are looking for the 'js' and 'css' directories in /var/www, which of course does not exist. Is there a way to use RewriteRules to redirect requests for absolutely referenced files to point to the correct subdirectory? I have tried doing things like:
RewriteEngine on
RewriteRule ^/$ /site1
or
RewriteEngine on
RewriteRule ^/js/(.*)$ /site1/js/$1
RewriteRule ^/css/(.*)$ /site1/css/$1
But neither of these work, even redirecting to only one directory, not to mention handling both site1 and site2. Is what I'm trying possible?
EDIT: SOLUTION
I ended up adapting Jon's advice to fit my situation. I have the ability to programatically make changes to my .htaccess file whenever a new subdirectory is added or removed. For each "site" that I want, I have the following section in my .htaccess:
RewriteCond %{REQUEST_URI} !^/$
RewriteCond %{REQUEST_URI} !^/index.php$
RewriteCond %{HTTP_COOKIE} sitename=site1
RewriteCond %{REQUEST_URI} !^/site1/
RewriteRule ^(.*)$ /site1/$1 [L]
Index.php is a file that lists all my sites, deletes the "sitename" cookie, and sets a cookie of "sitename=site#" when a particular one is selected. My RewriteConds check,
If the request is not for /
If the request is not for /index.php
If the request contains the cookie "sitename=site1"
If the request does not start with "/site1/"
If all of these conditions are met, then the request is rewritten to prepend "/site1/" before the request. I tried having a single set of Conds/Rules that would match (\w+) instead of "site1" in the third Condition, and then refer to %1 in the fourth Condition and in the Rule, but this did not work. I gave up and settled for this.
If the RewriteRules are in your .htaccess file, you need to remove the leading slashes in your match (apache strips them before sending it to mod_rewrite). Does this work?
RewriteEngine on
RewriteRule ^js/(.*)$ /site1/js/$1
RewriteRule ^css/(.*)$ /site1/css/$1
EDIT: To address the comment:
Yes, that works, but when I do RewriteRule ^(.*)$ /site1/$1, it causes Apache to issue internal server errors. But to me, it seems like that should just be a generic equivalent of the individual rules!
What's happening with that rule is when /something/ gets rewritten to /site/something/, and apache internally redirects, it gets rewritten again, to /site/site/something/, then again, then again, etc.
You'd need to add a condition to that, something like:
RewriteCond %{REQUEST_URI} !^/site/
RewirteRule ^(.*)$ /site/$1 [L]
You need to set up symlinks, which the rewrite rules will use so your absolute links at the server level can follow the symbolic links to the central site hosting account.

mod_rewrite ignores [L]

I want to be able to rewrite this
http://localhost/.../identicon/f528764d624db129b32c21fbca0cb8d6.png
to
http://localhost/.../identicon.php?hash=f528764d624db129b32c21fbca0cb8d6
so I add to the /.../.htaccess so this is it:
RewriteEngine On
RewriteRule ^resource/ - [L]
RewriteRule ^identicon/(.+)\.png$ identicon.php?hash=$1 [QSA,L]
RewriteRule ^(.*)$ index.php?t=$1 [QSA,L]
Which doesn't work for some reason because it redirects it to index.php?t=identicon.php; even though the L flag is set! Why?
Add a condition to the last rule to exclude requests that can be mapped to existing files:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?t=$1 [QSA,L]
That is necessary because the L flag generates an internal redirect with the new URL as the request URL:
Remember, however, that if the RewriteRule generates an internal redirect (which frequently occurs when rewriting in a per-directory context), this will reinject the request and will cause processing to be repeated starting from the first RewriteRule.
(Not correct answer; left for reference)
I just figured out what may be the issue - it's something that thwarted me for a long time.
Depending on your server settings, it very well may be interpreting identicon/xxx.png as a request to identicon.php/xxx.png, assuming that the PHP extension is what you wanted. Try going to /index instead of /index.php - if it loads the PHP file, this is the issue affecting you.
This is the MultiViews Apache option, and it's stupid, but it has to be enabled specifically. Go into your site configuration file and see where it is enabled, and remove it.
If you don't have total control over your server configuration, the following may work in .htaccess (depending, ironically, on your server configuration).
Options -Multiviews

Hidden features of mod_rewrite

There seem to be a decent number of mod_rewrite threads floating around lately with a bit of confusion over how certain aspects of it work. As a result I've compiled a few notes on common functionality, and perhaps a few annoying nuances.
What other features / common issues have you run across using mod_rewrite?
Where to place mod_rewrite rules
mod_rewrite rules may be placed within the httpd.conf file, or within the .htaccess file. if you have access to httpd.conf, placing rules here will offer a performance benefit (as the rules are processed once, as opposed to each time the .htaccess file is called).
Logging mod_rewrite requests
Logging may be enabled from within the httpd.conf file (including <Virtual Host>):
# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2
Common use cases
To funnel all requests to a single point:
RewriteEngine on
# ignore existing files
RewriteCond %{REQUEST_FILENAME} !-f
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# map requests to index.php and append as a query string
RewriteRule ^(.*)$ index.php?query=$1
Since Apache 2.2.16 you can also use FallbackResource.
Handling 301/302 redirects:
RewriteEngine on
# 302 Temporary Redirect (302 is the default, but can be specified for clarity)
RewriteRule ^oldpage\.html$ /newpage.html [R=302]
# 301 Permanent Redirect
RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
Note: external redirects are implicitly 302 redirects:
# this rule:
RewriteRule ^somepage\.html$ http://google.com
# is equivalent to:
RewriteRule ^somepage\.html$ http://google.com [R]
# and:
RewriteRule ^somepage\.html$ http://google.com [R=302]
Forcing SSL
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R,L]
Common flags:
[R] or [redirect] - force a redirect (defaults to a 302 temporary redirect)
[R=301] or [redirect=301] - force a 301 permanent redirect
[L] or [last] - stop rewriting process (see note below in common pitfalls)
[NC] or [nocase] - specify that matching should be case insensitive
Using the long-form of flags is often more readable and will help others who come to read your code later.
You can separate multiple flags with a comma:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
Common pitfalls
Mixing mod_alias style redirects with mod_rewrite
# Bad
Redirect 302 /somepage.html http://example.com/otherpage.html
RewriteEngine on
RewriteRule ^(.*)$ index.php?query=$1
# Good (use mod_rewrite for both)
RewriteEngine on
# 302 redirect and stop processing
RewriteRule ^somepage.html$ /otherpage.html [R=302,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# handle other redirects
RewriteRule ^(.*)$ index.php?query=$1
Note: you can mix mod_alias with mod_rewrite, but it involves more work than just handling basic redirects as above.
Context affects syntax
Within .htaccess files, a leading slash is not used in the RewriteRule pattern:
# given: GET /directory/file.html
# .htaccess
# result: /newdirectory/file.html
RewriteRule ^directory(.*)$ /newdirectory$1
# .htaccess
# result: no match!
RewriteRule ^/directory(.*)$ /newdirectory$1
# httpd.conf
# result: /newdirectory/file.html
RewriteRule ^/directory(.*)$ /newdirectory$1
# Putting a "?" after the slash will allow it to work in both contexts:
RewriteRule ^/?directory(.*)$ /newdirectory$1
[L] is not last! (sometimes)
The [L] flag stops processing any further rewrite rules for that pass through the rule set. However, if the URL was modified in that pass and you're in the .htaccess context or the <Directory> section, then your modified request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand this, it often looks like your [L] flag had no effect.
# processing does not stop here
RewriteRule ^dirA$ /dirB [L]
# /dirC will be the final result
RewriteRule ^dirB$ /dirC
Our rewrite log shows that the rules are run twice and the URL is updated twice:
rewrite 'dirA' -> '/dirB'
internal redirect with /dirB [INTERNAL REDIRECT]
rewrite 'dirB' -> '/dirC'
The best way around this is to use the [END] flag (see Apache docs) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent passes). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag.
For earlier versions, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine.
# Only process the following RewriteRule if on the first pass
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ...
Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
if you need to 'block' internal redirects / rewrites from happening in the .htaccess, take a look at the
RewriteCond %{ENV:REDIRECT_STATUS} ^$
condition, as discussed here.
The deal with RewriteBase:
You almost always need to set RewriteBase. If you don't, apache guesses that your base is the physical disk path to your directory. So start with this:
RewriteBase /
Other Pitfalls:
1- Sometimes it's a good idea to disable MultiViews
Options -MultiViews
I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active, because one of its properties is to try and 'guess' an extension to a file that it thinks I'm looking for.
I'll explain:
Suppose you have 2 php files in your web dir, file1.php and file2.php and you add these conditions and rule to your .htaccess :
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1
You assume that all urls that do not match a file or a directory will be grabbed by file1.php. Surprise! This rule is not being honored for the url http://myhost/file2/somepath. Instead you're taken inside file2.php.
What's going on is that MultiViews automagically guessed that the url that you actually wanted was http://myhost/file2.php/somepath and gladly took you there.
Now, you have no clue what just happened and you're at that point questioning everything that you thought you knew about mod_rewrite. You then start playing around with rules to try to make sense of the logic behind this new situation, but the more you're testing the less sense it makes.
Ok, In short if you want mod_rewrite to work in a way that approximates logic, turning off MultiViews is a step in the right direction.
2- enable FollowSymlinks
Options +FollowSymLinks
That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.
Equation can be done with following example:
RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]
Dynamic Load Balancing:
If you use the mod_proxy to balance your system, it's possible to add a dynamic range of worker server.
RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
A better understanding of the [L] flag is in order. The [L] flag is last, you just have to understand what will cause your request to be routed through the URL parsing engine again. From the docs (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (emphasis mine):
The [L] flag causes mod_rewrite to stop processing the rule set. In
most contexts, this means that if the rule matches, no further rules
will be processed. This corresponds to the last command in Perl, or
the break command in C. Use this flag to indicate that the current
rule should be applied immediately without considering further rules.
If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of
how the rules are processed. The simplified form of this is that once
the rules have been processed, the rewritten request is handed back to
the URL parsing engine to do what it may with it. It is possible that
as the rewritten request is handled, the .htaccess file or <Directory>
section may be encountered again, and thus the ruleset may be run
again from the start. Most commonly this will happen if one of the
rules causes a redirect - either internal or external - causing the
request process to start over.
So the [L] flag does stop processing any further rewrite rules for that pass through the rule set. However, if your rule marked with [L] modified the request, and you're in the .htaccess context or the <Directory> section, then your modifed request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand what happened, it looks like your first rewrite rule with the [L] flag had no effect.
The best way around this is to use the [END] flag (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent reparsing). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag. In this case, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine. Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.
Another great feature are rewrite-map-expansions. They're especially useful if you have a massive amout of hosts / rewrites to handle:
They are like a key-value-replacement:
RewriteMap examplemap txt:/path/to/file/map.txt
Then you can use a mapping in your rules like:
RewriteRule ^/ex/(.*) ${examplemap:$1}
More information on this topic can be found here:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc
mod_rewrite can modify aspects of request handling without altering the URL, e.g. setting environment variables, setting cookies, etc. This is incredibly useful.
Conditionally set an environment variable:
RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]
Return a 503 response:
RewriteRule's [R] flag can take a non-3xx value and return a non-redirecting response, e.g. for managed downtime/maintenance:
RewriteRule .* - [R=503,L]
will return a 503 response (not a redirect per se).
Also, mod_rewrite can act like a super-powered interface to mod_proxy, so you can do this instead of writing ProxyPass directives:
RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]
Opinion:
Using RewriteRules and RewriteConds to route requests to different applications or load balancers based on virtually any conceivable aspect of the request is just immensely powerful. Controlling requests on their way to the backend, and being able to modify the responses on their way back out, makes mod_rewrite the ideal place to centralize all routing-related config.
Take the time to learn it, it's well worth it! :)