Apache2 rewrite module, flag [L] - php

I have next rewrite rules:
RewriteEngine ON
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([a-z]+)/(js|css|img)/(.+\.jpg|gif|png|js|css)$ media/myfiles/$1/$2/$3 [L]
RewriteRule .* index.php
I my application i have route class, that can process url's for my needs.
When i try to open file, that contains extension that will match to rewrite rule, i move to next rewrite rule, and my router class process this url...
Any ideas why apache doesn't stop after rule match first time?
P.S. first rule works after disabling second rule.

Take a look here: http://httpd.apache.org/docs/2.2/rewrite/flags.html
If you are using RewriteRule in either .htaccess files or in
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
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.
(emph mine)
So what I think happens is that your last rule hits, and redirects. It doesn't call the bottom line. But then, the request is handled like any other request, your regexp DOESN"T hit, and in this run the bottom line DOES come into play.
This is also why it works when you disable the bottom rule: the second time around there is nothing to do, so nothing happens.

Related

htaccess and php routing

I was learning today about Rewrite and PHP routing. I wrote the following .htaccess file:
RewriteEngine On
RewriteRule ^(.+)$ testing.php?url=$1 [NC,L]
The testing.php has the following code:
<?php
echo $_GET["url"];
?>
When I enter the following URL for example: http://localhost/tests/Hello
The following is shown in the browser:
testing.php
But I am expecting "Hello" to show instead of "testing.php". I tried to change NC to QSA and it worked successfully but why the first case is not working?
Thank you.
It's likely that the URL is being rewritten more than once.
The first time rewrites /Hello to testing.php but then internally a separate request is made to testing.php and ran through the rewrite engine again. That will give you testing.php as the URL parameter.
Using QSA means that on the second request it is appending to the query string as opposed to overwriting it. If you looked at $_SERVER['QUERY_STRING'] you would likely see something like url=testing.php&url=Hello (or similar) and php is just using the last value in url.
Using NC is not necessary as the pattern you are using has no case (.+ matches one or more of any character regardless of case).
Using L means to stop processing rules in this request, but doesn't stop processing rules in subsequent requests (including transparent/internal subsequent requests).
Usually this is fixed by telling mod_rewrite to ignore the request if it is made to a file that exists using a RewriteCond like:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)$ testing.php?url=$1
Which tells apache that if the requested filename is not a file and not a directory, process the rule. With this in place, the request to testing.php will see the file exists, condition fails and so the rule isn't processed additional times. Nothing would stop additional rules from applying though if they exist. And also, just an FYI, conds only apply to the very next rule. So if you multiple rules, you need these cond for each of them.
QSA stands for Query-String-Append, which means that the Query string that was first sent gets appended. With NC, you were only doing nocase, meaning that the RewriteRule will be matched in a case-insensitive manner.
L just means that RewriteRule will stop processing it after that rule.

.htaccess url rewrite behavior being overwritten if filename minus ext. same as url

I'm trying to tidy up the URLs and remove the .php extensions from them and such. I'm in the base folder for the website, so there are no parent .htaccess files that could be taking priority or anything. Here's my htaccess code.
RewriteEngine On
RewriteRule ^give/?$ give.php [NC,L]
This part gives no real problems, because whatever behavior is overwriting it behaves the same way. But when I add in this other line so it will account for url variables,
RewriteRule ^give/([0-9]+)/?$ give.php?step=$1 [NC,L]
It completely ignores it. However, if I rename give.php to anything else, so it doesn't match the url, it works. For example, using given.php causes it to heed the .htaccess rules. I've never run across this issue before. Is there some server setting I can change so I don't have to give my files odd names?
Also note, I tried changing that first line to redirect to another file, but without changing the actual file name give.php. It ignored my redirect and still loaded the give.php file.
Edit: I've tried changing the order of the 2 rules, I've tried commenting both one rule, then the other to see if they are conflicting with eachother. The best I can figure, the server has some sort of default behavior that directs /give/ to /give.php if no directory of /give/ exists. Because even if I remove both rules, going to /give/ still redirects me to /give.php. Only when I change the filename to given.php will it break that default behavior. I also tried setting the simpler rule to this:
RewriteRule ^give/?$ resources.php [NC,L]
And as long as the file give.php existed, it still redirected to give.php. If I removed give.php or changed its name, going to /give would then redirect to resources.php
You may have a conflict with the MultiViews option of mod_negotiation.
Your request may not be hitting the RewriteRule at all, and is instead being handled by the give.php because of mod_negotiation. From the docs:
... if the server receives a request for /some/dir/foo, if /some/dir has MultiViews enabled, and /some/dir/foo does not exist, then the server reads the directory looking for files named foo.*, and effectively fakes up a type map which names all those files, assigning them the same media types and content-encodings it would have if the client had asked for one of them by name. It then chooses the best match to the client's requirements.
Try adding this to your .htaccess and see if things change:
Options -MultiViews
So some changes, first I would try some file conditions before the rule. Second the params as you wrote are only for numbers, and last QSA for more query.
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^give/(.+)$ give.php?step=$1 [QSA, L]

How can I get the RewriteCond flag "-f" to recognise already-rewritten files?

We're currently migrating our monstrous legacy PHP application to PHRoute - it's made up of around ten or twenty thousand top level scripts. We've spiked a few isolated pages, but now I'm trying to integrate it with the whole system.
I'm using the following rewrite rules to exclude any existing files and directories from being re-routed, so it doesn't try to route any of the existing top level scripts.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?path=$1 [NC,L,QSA]
This works fine with one exception: for multi-tenancy/custom branding reasons we have rewrite rules for our client-specific files like logos, CSS, etc. The conditions of the router rewrite rules don't pick up the rewritten client files, and so it tries to re-route, for example, static/theme.css when it should just rewrite it to /clients/[client]/css/theme.css and skip the router entirely.
How can I adjust the above rules to pick up the rewritten files as a "real" file (-f)?
Turns out I was missing the [L] or [last] flag from the client specific file rewrites, which meant the router rules were running against the original URL (static/theme.css) instead of the rewritten one (/clients/[client]/css/theme.css).
Adding the L flag means it now processes the client rewrite immediately instead of waiting until the end. So by the time it gets to the router rewrites it's now checking against the real file location, and thus successfully recognises the real file.
Source: https://httpd.apache.org/docs/current/rewrite/flags.html#flag_l

PHP & URL Rewriting

I need a little help figuring out what the following URL rewrite rule means. I can understant the first three lines, but I got stuck with the index.php/$1 part. What does exactly the / means in this rule? The only thing I would always expect to see after a file name would be a query-string separator (?). This is the first time I am seeing the / as a separator. What does it exactly mean?
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [PT,L]
</IfModule>
The <IfModule mod_rewrite.c>...</IfModule> block ensures that everything contained within that block is taken only into account if the mod_rewrite module is loaded. Otherwise you will either face a server error or all requests for URL rewriting will be ignored.
The following two lines are conditions for the RewriteRule line which follows them. It means that the RewriteRule will be evaluated only if these two conditions are met.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
These lines simply state that rewriting (RewriteRule line) will occur only if there are no existing files or folders on the server which match the URI. If they do exist then they will be served instead, unless there is some other directive that prevents it, otherwise rewriting will occur.
The last line will do the actual rewriting. It will take whatever is following the website domain name and append it to a rewritten request which will begin with index.php/.
Here is an example.
Lets say you make a request for example.com/example-page.html.
If there is no existing file or folder in the virtual hosts root folder named example-page.html the rewrite rule at the end will rewrite the request to look like example.com/index.php/example-page.html.
The main reason why applications rewrite requests like this is to ensure that they have a single point of entry, often called a bootstrap, which is considered to be a good practice from the security standpoint and also offers more granular control of requests.
Here is in my opinion a very good beginner friendly tutorial for mod_rewrite.
It's just rewritting the url name.
For example, this url:
http://www.example.com/something/else
Will be the same as:
http://www.example.com/index.php/something/else

Rewrite to index.php best practices

I notice that there are a few common ways to setup RewriteRules for MVC based PHP applications. Most of which contain:
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
Followed by a RewriteRule:
RewriteRule ^(.*)$ /index.php?$1 [L,QSA]
or
RewriteRule .* /index.php/$0 [PT,L]
I realize that L = LAST, QSA = query string appended, PT = pass through but as I don't have the real world experience of using these yet, could anyone inform me which flags and URI they would go with and why?
The latter rule contains a slash before the $0, I'm assuming because this forces it so the PATH CGI variable is populated, as often times I don't see it populated. Does the PT actually serve somewhat of the same purpose as the QSA, indirectly? Or how else would one use query strings? Basically, what are the pros and cons of these?
And just to confirm, if I wanted to add say an ErrorDocument directive would the L flag matter? Let's say a request to '/non-existing-link/' is made, my application cannot pick it up from the defined routes I have, nor is there an existing directory as such, would the L have any effect if I placed the ErrorDocument below the RewriteRule? Should I place it before the entire snippet? Same with 301s, 302s. And if I were to actually manually invoke 3xx/4xx codes, I would be using the header() function within my application, right? I kind of have a feeling this is quite dirty but is probably the most practical and only way of doing it hence it probably isn't dirty.
When the htaccess is read by the server, it goes line-by-line, trying to find a match. Without the L flag it will check every rule in the htaccess (though I'm not sure what happens if it finds multiple matches here).
If you include the L flag, when it gets to that rule, it will stop processing rules and serve the request. However, the gotcha here is that when it serves the request it will process the htaccess file from the beginning again with the new, rewritten URL. This page explains it well, with an example.
The ErrorDocument rule will be independent from the rewrite rules, so it doesn't matter where it comes (I usually put it at the top so it's obvious and not buried under a bunch of rewrites).
However, note that if a rewrite rule matches a valid file or script, the error document won't fire, even if the data/querystring is bogus. For example if a URL gets written to /index.php?page=NON_EXISTENT_PAGE then the server believes it has found the document. You will need to handle the parameter in the PHP script. Setting 404 headers in the PHP script won't automatically serve up the 404 document (but you can include it from the PHP script).
I have used zend framework suggestion for MVC application.
http://framework.zend.com/manual/en/zend.controller.html
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
The ErrorDocument setting will have no effect. If files are not found by Apache, the request is handled by PHP (as defined by these rewrite rules). Once inside PHP, you have to stay inside. Setting the response code to an error value with header() will not invoke Apache's error handling. You have to make your own code to present a decent error page.

Categories