I got stuck, and even reading through tons of forum posts didn't help me.
The challenge:
I need URIs to be rewritten and queries to be maintained
Examples 1:
example.com/test/23/result/7
shall be redirected to a script under
example.com/test/
That works quite well with an .htaccess entry like this:
RewriteCond %{QUERY_STRING} ^$
RewriteRule ^test/(.+)$ test/?s=$1
The URI is displayed unaltered. The called script is called, and the additional subdirectory definitions can be retrieved in PHP either through variable $_GET['s'] or $_SERVER['REQUEST_URI']. All is fine so far. The problem starts when adding a query string:
Example 2:
example.com/test/23/result/7?id=16
shall be redirected to the same script under
example.com/test/?id=16
Even when I add [QSA] to the rewrite rule, the URI is not parsed correctly. I tried several ways to initiate a redirect. All failed. The redirect either points to a non-existing address or the query string gets lost. Besides the initial URI subdirectory information, here I would need the query string to be evaluated in my script. Both pieces of data need to be transferred to it.
Does anyone have a solution?
Thanks a lot for sharing your expertise!
I would go with following htaccess Rules. This assumes that you have index.php file which is taking care of non-existing pages request in later your Rules.
RewriteEngine ON
##Rules for handling index.php url here.
RewriteCond %{THE_REQUEST} \s/([^/]*)/.*\?index\.php\s [NC]
RewriteRule ^ %1?%{QUERY_STRING} [NC,L]
##Rules for non-existing pages here.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
###Rest of your rules go here.....
I want to redirect all content to:
www.example.com/public/...
but prevent direct access to
www.example.com/public/file1/
www.example.com/public/file2/
etc
The final URL should be:
www.example.com/file1/
I've tried this for redirecting and it works - but I dont know how to prevent direct access:
ReWriteCond %{REQUEST_URI} !/public/
RewriteRule ^(.*) public/$1 [L]
After spending an inordinate amount of time trying to solve this problem, I found that the solution lies with the under-documented REDIRECT_STATUS environment variable.
Add this to the beginning of your top-level /.htaccess code, and also to any .htaccess files you have under it (e.g. /public/.htaccess):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{ENV:REDIRECT_STATUS} !=200
RewriteRule ^ /public%{REQUEST_URI} [L]
</IfModule>
Now, if the user requests example.com/file1 then they are served the file at /public/file1. However, if they request example.com/public/file1 directly then the server will attempt to serve the file at /public/public/file1, which will fail (unless you happen to have a file at that location).
IMPORTANT:
You need to add those lines to all .htaccess files, not just the top-level one in the web root, because if you have any .htaccess files below the web root (e.g. /public/.htaccess) then these will override the top-level .htaccess and users will again be able to access files in /public directly.
Note about variables and redirects:
Performing a redirect (or a rewrite) causes the whole process to start again with the new URI, so any variables that you set before the redirect will no longer be set afterwards. This is done deliberately, because usually you do not want the final result to depend on how you got there (i.e. whether it was via a direct request or via a redirect).
However, for those special occasions where you do want to know how you got to a particular URI, you can use REDIRECT_STATUS. Also, any environment variables set before the redirect (e.g. with SetEnvIf) will still be available after the redirect, but with REDIRECT_ prefixed to the name of the variable (so MY_VAR becomes REDIRECT_MY_VAR).
Maybe you should clarify what's the expected behaviour when user tries to reach the real URL:
www.example.com/public/file1/
If by prevent you mean forbid, you could add a rule to respond with a 403
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !/public/
RewriteRule ^(.*)$ /public/$1 [L]
RewriteCond %{REQUEST_URI} /public/
RewriteRule ^(.*)$ / [R=403,L]
</IfModule>
Update: The solution above doesn't work!
I realized my previous solution always throws the 403 so it's worthless. Actually, this is kinda tricky because the redirection itself really contains /public/ in the URL.
The solution that really worked for me is to append a secret query string to the redirection and check for this value on URL's containing /public/:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} !/public/
RewriteRule ^(.*)$ /public/$1?token=SECRET_TOKEN [L]
RewriteCond %{REQUEST_URI} /public/
RewriteCond %{QUERY_STRING} !token=SECRET_TOKEN
RewriteRule ^(.*)$ / [R=403,NC,L]
</IfModule>
This way www.example.com/file1/ will show file1, but www.example.com/public/file1/ will throw a 403 Forbidden error response.
Concerns about security of this SECRET_TOKEN are discussed here: How secure is to append a secret token as query string in a htaccess rewrite rule?
If your URL's are expected to have it's own query string, like www.example.com/file1/?param=value be sure to add the flag QSA.
RewriteRule ^(.*)$ /public/$1?token=SECRET_TOKEN [QSA,L]
I am trying to use .htaccess to redirect all url requests of a certain subpath ("URL/somefolders/main/..") to one basefile named "_index.php". So I implemented the following .htaccess to the "folder" URL/somefolders/main/ :
<IfModule mod_rewrite.c>
DirectoryIndex index.php
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^?]*)$ /main/_index.php?oldpath=$1 [NC,L,QSA]
</IfModule>
The redirection works fine for all non-existant files, but if the file exists then it is called without redirection. I suppose this is because I ordered to do so by the "!" in the RewriteCond, but all my tries to change it failed.
How do I have to change the above code to redirect all files (existant or not) ?
Edit:
All my tries still end up uneffective or erroneous.
The latter with the Apache log error:
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.
Currently I fear that in fact I did correctly allow non-existant files in most of my before tries but get me a problem with an endless loop due to included files - is this possible? And if so can the .htaccess distinguish between "internal" and "external" file requests?
Your original rules are the most common implementation you'll see, where REQUEST_FILENAME is checked for existing files or directories to prevent things like CSS and images from being rewritten. But that's not what you want.
So you correctly attempted to remove the RewriteCond directives but ended up with an infinite rewrite loop. That is likely because the subsequent RewriteRule is also attempting to rewrite _index.php back to itself.
You can fix that by adding a RewriteCond which specifically matches _index.php to prevent it from looping on itself.
<IfModule mod_rewrite.c>
DirectoryIndex index.php
RewriteEngine on
# Don't apply the rewrite to _index.php to prevent looping
RewriteCond %{REQUEST_URI} !main/_index\.php
RewriteRule ^([^?]*)$ /main/_index.php?oldpath=$1 [NC,L,QSA]
</IfModule>
I'll also simplify the matched group in RewriteRule. ([^?]*) captures everything up to the first ?, but the expression received by RewriteRule will never include the query string or ? anyway. You may instead simply use (.*) to capture whatever is present.
RewriteRule (.*) /main/_index.php?oldpath=$1 [NC,L,QSA]
I currently have this .htaccess rule the works fine:
RewriteRule ^instructor/([A-Za-z0-9-]+)$ instructor.php?username=$1 [NC,L]
However, when I attempt to add a period into the mix a lot of the rules on the site break so I am assuming the character isn't escaped correctly:
RewriteRule ^instructor/([A-Za-z0-9-\.]+)$ instructor.php?username=$1 [NC,L]
Anyone point me in the right direction please?
Update
It appears to be something to do with the directory structure.
Another selection of rules that apply to this site are the following:
## Registration
RewriteRule ^instructor/register/?$ instructor-form/index.php [L]
RewriteRule ^instructor/register/stage([1-5]+)$ instructor-form/stage$1.php [L]
These work fine (the directory here is instructor-form/
However, there is also a directory called instructor/ which these rules point to:
RewriteRule ^instructor/dashboard/?$ instructor/index.php [L]
RewriteRule ^instructor/account-details/?$ instructor/account-details.php [L]
RewriteRule ^instructor/change-password/?$ instructor/change-password.php [L]
These are the rules that are affected when adding the . into the first rule. The rules are all in order and work fine without the . in the [A-Za-z0-9-] char block. When added the physical instructor/ folder seems inaccessible.
You wrote so much text in your question but forgot to mention important details: what is actually broken? Because I do not have clear answer for that I will be speculating here based on the information you have provided so far.
RewriteRule ^instructor/([A-Za-z0-9-\.]+)$ instructor.php?username=$1 [NC,L]
The problem with this rule is that it will also rewrite already rewritten php files: instructor/index.php, instructor/account-details.php, instructor/change-password.php etc.
I think you are relaying on [L] flag too much .. or do not really know how mod_rewrite and [L] flag work. And that is why you are having this issue -- your rule with a dot in pattern rewrites already rewritten URLs.
Useful link: RewriteRule Last [L] flag not working?
You need to add some condition (global rule or condition specific to this rule only) to prevent rewriting already rewritten URLs or existing files.
1. Global rule -- place it somewhere on the top before other rules. Keep in mind that this may not work as intended depending on your website structure and rewrite logic (e.g. when you need to actually rewrite requests to already existing files or folders):
# do not do anything for already existing files
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule .+ - [L]
2. Condition specific to that rule only:
a) do not rewrite if requested URI is physical file
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^instructor/([A-Za-z0-9-\.]+)$ instructor.php?username=$1 [NC,L]
OR
b) do not rewrite .php files
RewriteCond %{REQUEST_URI} !.+\.php$
RewriteRule ^instructor/([A-Za-z0-9-\.]+)$ instructor.php?username=$1 [NC,L]
To newcomers: While trying to comprehensively describe my problem and phrase my questions I produced huge ammount of text. If you don't want to read the whole thing, my observations about (read "proof of") [L] flag not working the misconception, from which it all sprung, is located in Additional observations section. Why I misunderstood apparent behaviour is described in my Answer as well as solution to given problem.
Setup
I have following code in my .htaccess file:
# disallow directory indexing
Options -Indexes
# turn mod_rewrite on
Options +FollowSymlinks
RewriteEngine on
# allow access to robots file
RewriteRule ^robots.txt$ robots.txt [NC,L]
# mangle core request handler address
RewriteRule ^core/(\?.+)?$ core/handleCoreRequest.php$1 [NC,L]
# mangle web file adresses (move them to application root folder)
# application root folder serves as application GUI address
RewriteRule ^$ web/index.html [L]
# allow access to images
RewriteRule ^(images/.+\.(ico|png|bmp|jpg|gif))$ web/$1 [NC,L]
# allow access to stylesheets
RewriteRule ^(css/.+\.css)$ web/$1 [NC,L]
# allow access to javascript
RewriteRule ^(js/.+\.js)$ web/$1 [NC,L]
# allow access to library scripts, styles and images
RewriteRule ^(lib/js/.+\.js)$ web/$1 [NC,L]
RewriteRule ^(lib/css/.+\.css)$ web/$1 [NC,L]
RewriteRule ^(lib/(.+/)?images/.+\.(ico|png|bmp|jpg|gif))$ web/$1 [NC,L]
# redirect all other requests to application address
# RewriteRule ^(.*)$ /foo/ [R]
My web application (and its .htaccess file) is located in foo subfolder of DOCUMENT_ROOT (accessed from browser as http://localhost/foo/). It has PHP core part located in foo/core and JavaScript GUI part located in foo/web. As can be seen from the code above, I want to allow access only to single core script that handles all requests from GUI and to 'safe' web files and redirect all other requests to base application address (last commented directive).
Problem
Behaviour
It works until I try the last part by uncommenting the last redirecting directive. If I comment some more lines, the appropriate page parts stop working, etc.
However, when I uncomment last line, which should be performed only when matching of all previous rules fails (at least that's what I understand), page goes into redirection cycle (Firefox throws error page with something like "This page isn't redirecting properly"), because it's redirecting to http://localhost/foo/ again and again and again, forever.
Questions
What I don't understand is this processing of this rule:
RewriteRule ^$ web/index.html [L],
specifically the [L] flag. The flag apparently doesn't work for me. When the last line is commented, it correctly redirects, but when I uncomment it, it is always processed, even though rewriting should stop on [L] flag. Anyone got any ideas?
Also, on a sidenote, I'd be thrilled to know why my following attempt at fixing it doesn't work either:
RewriteEngine on
RewriteRule ^core/(\?.+)?$ core/handleCoreRequest.php$1 [NC,L]
RewriteRule ^(.*)$ web/$1 [L]
RewriteRule ^.*$ /foo/ [L]
This actually doesn't work at all. Even if I remove the last line, it still doesn't redirect anything correctly. How does the redirecting work in the first example, if it doesn't work in the second?
It would also be of great benefit to me, if anybody knew any way to actually debug these directives. I spend hours on this without even the slightest clue what could possibly be wrong.
Additional observations
After trying the advice given by bbadour (not that I haven't tried it before, but now that I had a second opinion, I gave it another shot) and it didn't work, I've come up with the following observation. By rewriting last line to this:
RewriteRule ^(.*)$ /foo/?uri=$1 [R,L]
or this
RewriteRule ^(.*)$ /foo/?uri=%{REQUEST_URI} [R,L]
and using Firebug's Net panel, I found out more evidence, that the [L] flag is clearly not working as expected in the previously mentioned RewriteRule ^$ web/index.html [L] rule (let's call it THE RULE from now on). In first case I get [...]uri=web/index.html, in second case [...]uri=/foo/web/index.html. That means that THE RULE gets executed (rewrites ^$ to web/index.html), but the rewriting doesn't stop there. Any more ideas, please?
After hours of searching and testing, I finally found the real problem and solution. Hopefully this will help somebody else too, when they come across the same problem.
Cause of observed behavior
.htaccess file is processed after every redirect (even without [R] flag),
which means that after the RewriteRule ^$ web/index.html [L] is processed, mod_rewrite correctly stops rewriting, goes to the end of the file, redirects correctly to /foo/web/index.html, and then the server starts processing .htaccess file for the new location, which is the same file. Now only the last rewrite rule matches and redirects back to /foo/ (this time with [R], so the redirect can be observed in browser) ... and the .htaccess file is processed again, and again, and again...
Once more for clarity: Because only the hard redirects can be observed, it seems like the [L] flag is ignored, but it is not so. Instead, the .htaccess is processed two times redirecting back and forth between /foo/ and /foo/web/index.html.
Solution
Disallow direct access to subfolder
To virtually move subdirectory to application root directory, additional complex conditional rewrites must be used. Variable THE_REQUEST is useful for distinguishing between hard and soft redirects:
RewriteCond %{THE_REQUEST} ^GET\ /foo/web/
RewriteRule ^web/(.*) /foo/$1 [L,R]
For this rewrite rule to be matched, two conditions must apply. First, on second line, the "local URI" must start with web/ (which corresponds with absolute web URI /foo/web/). Second, on first line, the real request URI must start with /foo/web/ too. Together this means, that the rule only matches when the file inside the web/ subfolder is requested directly from the browser, in which case we want to do a hard redirect.
Redirect to allowed content from root to subfolder (soft)
RewriteCond $1 !^web/
RewriteCond $1 ^(.+\.(html|css|js|ico|png|bmp|jpg|gif))?$
RewriteRule ^(.*)$ web/$1 [L,NC]
We want to redirect to allowed content only if we haven't done it already, hence the first condition. Second condition specifies mask for allowed content. Anything matching this mask will be softly redirected, possibly returning 404 error if the content doesn't exist.
Hide all content not in subfolder or not allowed
RewriteRule !^web/ /foo/ [L,R]
This will do a hard redirect to application root for all URIs not beginning with web/ (and remember, only requests that can begin with web/ at this point are internal redirects for allowed content.
Real example
My code shown in my "question" after using solution tips mentioned above gradually transformed into the following:
# disallow directory indexing
Options -Indexes
# turn mod_rewrite on
Options +FollowSymlinks
RewriteEngine on
# allow access to robots file
RewriteRule ^robots.txt$ - [NC,L]
# mangle core request handler address
# disallow direct access to core request handler
RewriteCond %{THE_REQUEST} !^(GET|POST)\ /asm/core/handleCoreRequest.php
RewriteRule ^core/handleCoreRequest.php$ - [L]
# allow access to request handler under alias
RewriteRule ^core/$ core/handleCoreRequest.php [NC,QSA,L]
# mangle GUI files adressing (move to application root folder)
# disallow direct access to GUI subfolder
RewriteCond %{THE_REQUEST} ^GET\ /foo/web/
RewriteRule ^web/(.*) /foo/$1 [L,R]
# allow access only to correct filetypes in appropriate locations
RewriteCond $1 ^$ [OR]
RewriteCond $1 ^(images/.+\.(ico|png|bmp|jpg|gif))$ [OR]
RewriteCond $1 ^(css/.+\.css)$ [OR]
RewriteCond $1 ^(js/.+\.js)$ [OR]
RewriteCond $1 ^(lib/js/.+\.js)$ [OR]
RewriteCond $1 ^(lib/css/.+\.css)$ [OR]
RewriteCond $1 ^(lib/(.+/)?images/.+\.(ico|png|bmp|jpg|gif))$
RewriteRule ^(.*)$ web/$1 [L,NC]
# hide all files not in GUI subfolder that are not whitelisted above
RewriteRule !^web/ /foo/ [L,R]
What I don't like about this approach is that the application root folder must be hardcoded in .htaccess file (as far as I know), so the file must be generated on application install, not simply copied.
To debug, try simplifying your regex, and the url you ask for (a part of the full url you wanna match), and see if it's working, now step by step, add more bits to the regex adn the testing url, till you find where things are stopping to work properly.
Try using:
RewriteRule ^(.*)$ /foo/ [R,L]
If it still loops, put a RewriteCond in front of it to skip the rule if it is already /foo/