mod-rewrite, how to implement a second rule? - apache

I'm trying to get a .htaccess file RewriteRule in my web application. I need a double rule to match two URL formats that might happen:
Rule 1 (not commented below and works like a charm)
http://example.com/whatever -> /index.php?page=whatever&
http://example.com/whatever/ -> /index.php?page=whatever&
http://example.com/whatever/?test=me -> /index.php?page=whatever&test=me
Rule 2 (commented out below and does not work, but should work like this)
http://example.com/api/ -> index.php?page=api&func=
http://example.com/api/whateverelse -> index.php?page=api&func=whateverelse
The folder /api/ does not exists on the server and by reading the documentation the L parameters should prevent the parsing of more rules. Yet if I uncomment the ^\/api\/(.*)$ I get an 404 response.
The goal is that if the first rule gets parsed the second rule is omitted, and the goal is that the first rule actually works.
Is there someone that could point me in the right direction here?
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#RewriteRule ^\/api\/(.*)$ //index.php?page=api&func=$1 [NC,L]
RewriteRule ^\/?([^\/]+)\/?$ //index.php?page=$1&%{QUERY_STRING} [NC,L]
I tried to debug it by adding:
LogLevel alert rewrite:trace6
but this is not allowed in the .htaccess and I do not have direct access to the server configuration files.

#RewriteRule ^\/api\/(.*)$ //index.php?page=api&func=$1 [NC,L]
This doesn't work because in .htaccess the URL-path matched by the RewriteRule pattern does not start with a slash. You have included a slash prefix on the regex, so it never matches.
In the following rule, the slash prefix is made optional with the ? quantifier, ie. ^\/? - so it will match. (The slash prefix is only required when the directive is used in a server context. By making it "optional", it would work in either.)
If you are only using .htaccess then you can remove the slash prefix from the RewriteRule pattern altogether.
For example:
RewriteRule ^api/(.*) /index.php?page=api&func=$1 [NC,L]
Additional notes...
No need to backslash escape slashes in the regex, as they carry no special meaning. (Apache uses spaces as argument delimiters.)
Not sure what the double slash prefix on the substitution string was for? ie. //index.php. You should only have at most one slash here, or none at all for an internal rewrite (and the index.php file is in the same directory as the .htaccess file). (The double slash still "works", only because Apache is reducing instances of multiple slashes when it maps the URL to the filesystem.)
The RewriteCond directives only apply to the first RewriteRule directive that follows, so you may need to repeat these for the second rule. (Or reverse the logic and stop processing when a request does map to a file/directory? However, this might depend on what other directives you have in the file.) If you don't have physical files that start /api then you can remove the conditions from that rule.
You don't need to manually append the QUERY_STRING to the substitution. This is what the QSA (Query String Append) flag is for. The QSA flag will also avoid a stray & at the end when no query string is present on the request.
The NC flag is not required on the last rule. It may not be required on the "api" rule either, unless /API, or /ApI etc. might be required - but this should be avoided.
So, bringing this together, we have....
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^api/(.*) /index.php?page=api&func=$1 [NC,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/]+)/?$ /index.php?page=$1 [QSA,L]

Related

In Apache how to do an external redirect to the slashless version of a URL with a subfolder .htaccess file

On Apache 2.4 I have an .htaccess (in a subfolder) which rewrites slashless requests inside that folder to appropriate index files:
DirectorySlash Off
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} !/$
RewriteCond %{REQUEST_FILENAME}/index.html -f
RewriteRule (.*) $1/index.html [L]
This works for the slashless version exactly as expected. Now I want to redirect the slashed version externally to the slashless version. I tried adding the lines:
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_URI} /$
RewriteRule ^(.*)/ $1 [R=302,L]
However this does not work: The redirect is issued, however it does not go to the slashless URL, but to a URL with a system specific part injected.
So, for a sample URL http://example.com/path/to/dir/ the redirected URL looks like this http://example.com/fs9e/username/sub/public/path/to/dir instead of just http://example.com/path/to/dir.
How can I fix this? Many thanks for any pointers!
PS: The real case is a little bit more complicated because I do a subdomain-to-folder rewrite in the root .htacces, but I assume this is not relevant here.
RewriteRule ^(.*)/ $1 [R=302,L]
You are missing the slash prefix (/) on the substitution string (2nd argument) - to make the substitution root-relative. Or rather, /subfolder/ (since this .htaccess file is located in a subfolder). Since this is a relative substitution string (not starting with a slash or scheme+hostname), the directory-prefix*1 (which I assume is /fs9e/username/sub/public/path/) is added back (by default*2), resulting in a malformed redirect. (This is correct for internal rewrites, but not external redirects.)
It should be like this:
RewriteRule ^(.*)/$ /subfolder/$1 [R=302,L]
Note you were also missing the end-of-string anchor ($) on the RewriteRule pattern. (This also negates the need for the preceding condition that checks that REQUEST_URI ends in a slash.)
Note also that this "redirect" should go before the earlier "rewrite".
*1 The directory-prefix is the absolute filesystem path of the location of the .htaccess file.
*2 The alternative is to set a RewriteBase /subfolder - but that then affects all relative substitutions. You could also use an environment variable to apply a specific prefix only to some rules.

I can't figure out why this RewriteCond isn't working

So I'm having trouble figuring out why my RewriteRules won't trigger. These rules are in an .htaccess file at the root directory of a subdomain of my website. I've turned on detailed logging for mod_rewrite in the VirtualHost but that isn't really helping me solve what's wrong, though the first three rules seem to be working simply by coincidence since their files exist at the requested location.
The goal of this set of rules is:
sub.domain.tld/ -> passthrough/serve actual file
sub.domain.tld/?q=test -> passthrough/serve actual file with query args intact
sub.domain.tld/.well-known/* -> passthrough/serve actual file (for letsencrypt)
sub.doamin.tld/* -> process.php?project=*
sub.domain.tld/*?q=test -> process.php?project=*&q=test while handling unlimited number of query args
And the current .htaccess is:
RewriteEngine on
#serve actual file if viewing main page or doing https renewal
RewriteCond %{REQUEST_URI} ^\?.+|\/$ [OR]
RewriteCond %{REQUEST_URI} ^\.well-known.*
RewriteRule (.*) - [L,QSA]
#redirect everything else to the processing script
RewriteCond %{REQUEST_URI} ^(\w+)
RewriteRule \/(\w+) process.php?project=$1 [NC,L,QSA]
Thank you for your help!
OK, This was actually a complex one and because most of the time, %{REQUEST_URI} tests are done using the RewriteRule itself, I got a bit confused and I'm sorry about that.
It turns out:
%{REQUEST_URI} contains the leading slash
the matching part of the RewriteRule doesn't
Also, keep in mind %{REQUEST_URI} doesn't contain the query string, as stated in the Apache manual:
REQUEST_URI
The path component of the requested URI, such as "/index.html". This notably excludes the query string which is available as its own variable named QUERY_STRING.
So, a rule like RewriteCond %{REQUEST_URI} ^\?.+ is pretty much useless as you'll never have a question mark in %{REQUEST_URI}
Also, and this probably is the most confusing part, when requesting /, %{REQUEST_URI} will contain the actual index file that has been served. So, if your DirectoryIndex is set to index.php index.html (in that order) and you have an index.html file in the root folder, {REQUEST_URI} will be index.html. If you have an index.php file, it will be index.php, but never /.
That being said, we can simply your rules to:
RewriteEngine on
RewriteCond %{REQUEST_URI} !^/(\.well-known|index\.php$)
RewriteRule (.+) process.php?project=%{REQUEST_URI} [QSA]
Note that I added the $ inside the brackets to only match the end of string character after index\.php but not after \.well-known, so anything after \.well-known will also match.
You will need to replace index\.php with index\.html if you have an html index.
Finally, you don't need 2 rules for that. It's always better to have only one and exclude some URLs from it.
PS: you'll also notice you don't need to escape / as this is not considered as a regexp delimiter.
You just need this single rule in your .htaccess:
RewriteEngine on
# skip files, directories and anything inside .well-known/ directory
RewriteRule ^(?!index\.|process\.php|\.well-known)(.+)$ process.php?project=$1 [L,QSA,NC]

How do I redirect a specific URL pattern when Drupal Clean URLs are on?

I have a Drupal 5.23 installation using clean URLs with Apache and the mod_rewrite module. I am using an .htaccess file for the clean URLs functionality with the following configuration:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
</IfModule>
I am going to be disabling the Localization/Internationalization plugins on the website, which is going to change every single page's URL on the website from http://www.example.com/en/url-to-a-page to http://www.example.com/url-to-a-page (the /en portion is being stripped out).
I would like to add a mod_rewrite rule to give an HTTP 301 Redirect response for any incoming URLs with the /en portion in the URL so they are directed to the correct page.
I've tried adding the following lines to my .htaccess file both above and below the existing rules, but in both cases visiting a page with /en results in an HTTP 404 Not Found response:
RewriteRule ^en/(.+)$ http://www.example.com/$1 [R=301]
If I comment out the existing rules, my rule works just fine. I've also tried to add a condition to the rule, but this doesn't appear to have an effect either:
RewriteCond %{REQUEST_URI} =/en/*
This came up for me when writing all of my custom redirects, and it turns out the solution was to add an "L" to the redirect line. Give the following at try:
RewriteRule ^en/(.+)$ http://www.example.com/$1 [L,R=301]
Note the "L" near the end of the line. That, according to the Apache RewriteRule docs, means "Stop the rewriting process here and don't apply any more rewrite rules".
In addition to what sillygwailo suggest, I'd recommend you to make sure that your RewriteCond (needed, I think) actually matches..
from the apache docs:
=CondPattern' (lexicographically equal)
Treats the CondPattern as a plain string and compares it lexicographically to TestString. True if TestString is lexicographically equal to CondPattern (the two strings are exactly equal, character for character). If CondPattern is "" (two quotation marks) this compares TestString to the empty string.
So, It could possibly match only an URL containing an actual '*'..? Not sure, but you could also try this:
RewriteCond %{REQUEST_URI} ^/en/.*

How do I get apache RewriteRule working correctly for a subdomain?

I just setup a subdomain with the following RewriteCond:
RewriteCond $1 !^search.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/?([^/]+)$ search.php?q=$1 [L,NS]
I'm using the same rewrite condition on my main domain and it works perfectly. However, when I set it up on the subdomain, it simply outputs "index.php" when going to http://sub.domain.com
Every page on the subdomain outputs the page name in the body instead of processing the code, except for the search page, which appears to be working correctly.
What can I do to correct this issue?
I haven't played with your exact regex with mod_rewrite, but if I was looking at writing that regex in another engine, I would have to escape the slash. Also, given that $ is used to indicate a back reference, would that need escaping too (would your $ symbols in the regex be necessary as there is likely to be more text in the URI and it is not matched at the end of a string)?
I would try
RewriteCond $1 !^search.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/?([^\/]+)$ search.php?q=$1 [L,NS]
One other thing. Normally $ at the end of a regex means "only match if this is the end of the string". So from that, if RewriteCond is matching on ^search.php$ but the URL is search.php?q=... then I would think that this wouldn't match because search.php is not the end of the string. So that would look like the following (assuming you don't need to change anything else from your original).
RewriteCond $1 !^search.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/?([^/]+)$ search.php?q=$1 [L,NS]
In the main config the path always begins with / and you need an absolute path:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !^search.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/([^/]+)$ %{DOCUMENT_ROOT}/search.php?q=$1 [L]
In an .htaccess you need a RewriteBase which is stripped from the url (no / in the Rule now) and the path is relative.
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !^search.php$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/]+)$ search.php?q=$1 [L]
Several things come to mind here:
I have a few suggestions/comments/gotchas. Hopefully one of them is useful to you:
Make sure search.php isn't just echoing out its $_GET parameters. While this sounds obvious in retrospect, it's one of the more overlooked solutions.
RewriteRule works slightly differently when you specify it in a server configuration file than if you specify it in an .htaccess. Specifically, ^/ is wrong in a server config version as the entire URL is used (http://sub.domain.com/blah).
Make sure no other rewrite rules are being processed for this subdomain first, either in the main httpd.conf / apache2.conf or .htaccess.
Make sure RewriteEngine On appears in your configuration, as it is activated per-VirtualHost.
The NS flag will ignore redirects done using a relative Redirect or relative RewriteRule.
It sounds like the pattern '^/?([^/]+)$' may not be matching at all.
I'd activate RewriteLog, crank RewriteLogLevel to level 3 or above, and see if your pattern is matching at all. If not, start with a simpler pattern, and then work your way to a more complex pattern.
Or, something else is matching the pattern, so the request never gets to 'RewriteRule ^/?([^/]+)$' at all. You will see this in the RewriteLog.
I believe I recently had a problem where '^/' didn't match in certain cases on a Virtual Host. But '/' worked. The folks in the #httpd on Freenode.org helped me. If I can find this in my notes, I'll post it here.

Trying to put an exception to RewriteRule in .htaccess

I am redirecting all requests like so:
RewriteRule ^sitemap.xml$ sitemap.php?/ [QSA,L]
# the line below is the one I'm having trouble with
RewriteCond %{REQUEST_URI} !^market-reports$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) /index.php?section=$1 [QSA,L]
All my incoming links are meant to go to index.php, as you can see. But now I want to stop one from going there. I've never written my own RewriteCond before, so I'm a little unsure if what I am doing is correct.
Basically what I'm trying to say is: "If incoming URL is a file, directory or /market-reports/ do nothing. Otherwise send on the URL to index.php?section="
What am I doing wrong? Thanks
So you just need to ignore http://yourdomain.com/market-reports (in addition to files/directories?). You should be fine with:
RewriteCond %{REQUEST_URI} !^/market-reports/?$
This will (not) match "http://yourdomain.com/market-reports" as well as "http://yourdomain.com/market-reports/" as the question mark "?", in the Perl Compatible Regular Expression vocabulary that mod_rewrite uses, makes the match optional (a wildcard) before the end of the string anchor, which is represented with the literal dollar sign "$".
The "^" symbol acts as an anchor matching the beginning of the string and the "!" negates the match, so that any string URL that does not match the rest of the expression will be rewritten to the other specified rules.
See mod_rewrite regex vocabulary