Using variable in RewriteCond condition patern - apache

I want to associate user Ids to a specific application Id like:
<user_id> <app_id>
615 1
616 7
617 3
618 3
My URIs looks like:
/<app_id>/<user_id>/...
Now, I want to be able to easily change the application without impacting the user bookmarks. In my example, I want both
/1/615/index.html or /3/615/index.html
to be served as
/1/615/index.html
With the following rule, I get infinite loop:
RewriteMap map dbm:user-application.map
RewriteRule ^/([0-9]+)/([0-9]+)/(.*)$ /${map:$2}/$2/$3 [R,L]
...
#other proxy code to forward request to applications
I understand that after the redirection, Apache will always execute the same rule.
I then tried to add a rewrite condition to block the loop, like
RewriteMap map dbm:user-application.map
RewriteCond %{REQUEST_URI} !^/${map:$2}
RewriteRule ^/([0-9]+)/([0-9]+)/(.*)$ /${map:$2}/$2/$3 [R,L]
If I read correctly my rewrite logs, I can see that the variable !^/${map:$2} is not replaced in the condition pattern, but checked "as it". And then the condition is always true, and I still get my infinite loop.
Any idea to block the loop as soon as the application id match my map?

/3/615/index.html is correctly redirecting to /1/615/index.html
The problem is that you are redirecting /1/615/index.html to /1/615/index.html as well - you want to detect the case in which the map transform is a no-op and not redirect at all in that case.
If you don't care about the user-facing URL, just change the [R,L] to [L] (removing the R) and you should be fine since it won't trigger a new round-trip from the client.
You're right that the $2 backreference won't work in a RewriteCond expression; this is because the RewriteRule hasn't yet been evaluated. You might be able to use %n - style backreferences to a regex in a previous RewriteCond...
RewriteCond {%REQUEST_URI} ^/([0-9]+)/
RewriteCond {%REQUEST_URI} !^/${map:%1}
But I have not tested this, so YMMV.

Related

htaccess url redirect with get parameters ID and reduce value

I want to do an url redirect to a new domain by retrieving the ID parameter but only taking the first 4 characters. Anyone know how to do this?
For example, an original url:
http://www.original.example/see/news/actualite.php?newsId=be9e836&newsTitle="blablabla"
To :
https://www.new.example/actualites/be9e
I have tested :
RewriteCond %{QUERY_STRING} ^newsId=(.*)$ [NC]
RewriteRule ^$ https://www.new.example/actualites/%1? [NC,L,R]
RewriteCond %{QUERY_STRING} ^newsId=(.*)$ [NC]
RewriteRule ^$ https://www.new.example/actualites/%1? [NC,L,R]
There are a couple of problems with this:
The regex ^$ in the RewriteRule pattern only matches the document root. The URL in your example is /see/news/actualite.php - so this rule will never match (and the conditions are never processed).
The regex ^newsId=(.*)$ is capturing everything after newsId=, including any additional URL parameters. You only need the first 4 characters of this particular URL param.
As an aside, your existing condition is dependent on newsId being the first URL parameter. Maybe this is always the case, maybe not. But it is relatively trivial to check for this URL parameter, regardless of order.
Also, do you need a case-insensitive match? Or is it always newsId as stated in your example. Only use the NC flag if this is necessary, not as a default.
Try the following instead:
RewriteCond %{QUERY_STRING} (?:^|&)newsId=([^&]{4})
RewriteRule ^see/news/actualite\.php$ https://www.new.example/actualites/%1 [QSD,R,L]
The %1 backreference now contains just the first 4 characters of the newsId URL parameter value (ie. non & characters), as denoted by the regex ([^&]{4}).
The QSD flag (Apache 2.4) discards the original query string from teh redirect response. No need to append the substitution string with ? (an empty query string), as would have been required in earlier versions of Apache.
UPDATE:
I have an anchor link (#) which is added at the end of the link, is there a possibility of deleting it to make a clean link? Example, currently I have: https://www.new.example/news/4565/#title Ideally : https://www.new.example/news/4565
The "problem" here is that the browser manages the "fragment identifier" (fragid) (ie. the "anchor link (#)") and preserves this through the redirect. In other words, the browser re-appends the fragid to the redirect response from the server. The fragid is never sent to the server, so we cannot detect this server side prior to issuing the HTTP redirect.
The only thing we can do is to append an empty fragid (ie. a trailing #) in the hope that the browser discards the original fragment. Unfortunately, you will likely end up with a trailing # on your redirected URLs (browser dependent).
For example (simplified):
:
RewriteRule .... https://example.com/# [R=301,NE,L]
Note that you will need the NE flag here to prevent Apache from URL-encoding the # in the redirect response.
Like I say above, browsers might handle this differently.
Further reading:
URL Fragment and 302 redirects
redirect is keeping hash
How to clear fragment identifier on 302 redirect?

Apache Mod_Rewrite Question Mark

I need to redirect an incoming request with the following URL:
http://mywebsite.com/abc/mapserv.exe?map=123
to
http://mywebsite.com/abc/mapserv.exe?map=C:\Mapserver\ms4w\Apache\htdocs\Mapfiles\123.map
I already managed to do simple mod_rewrites but the question mark is killing this one all the time. I am not able to adapt common Query String examples to my case so I need help with this exact case.
As though you did not show your try, you could test this:
RewriteEngine On
RewriteCond %{QUERY_STRING} map=([0-9]+)$
RewriteRule . %{REQUEST_URI}?map=C:\\Mapserver\\ms4w\\Apache\\htdocs\\Mapfiles\\%1.map [NE,L]
Rewrite flags used:
NE: Not Escape,
L: Last instruction to run.
I was still having trouble with the .exe url since it is not accessible if you dont deliver the parameters right when you send the request. And then the redirect wont fire. So I made a dummy mapserver.php file which allows setting a parameter like so:
http://mywebsite.com/abc/mapserver.php?map=123
After hours of trying I ended up with the following RewriteRule:
RewriteCond %{QUERY_STRING} ^map=(.*)$
RewriteRule ^mapserver.php?$ /cgi-bin/mapserv.exe?map=C://Mapserver//ms4w//Apache//htdocs//Mapfiles//%1.map

modrewrite alter 1 element of query string

Flash movies are called based on dynamic links on mypage.php. mypage.php has the flash player embedded. The links look like mypage.php?moviefolder=folder1/folder2&swfTitle=sometitle.swf. mypage.php is parsed on each link click (per the href). Folder2 is always the same but movieTitle.swf is dynamic. Sometimes subfolders will be called (folder2/subfolder2/sometitle.swf).
Can mod_rewrite allow the query string to reflect folder2 but instead silently serve folder3 as well as occasional subfolders? I would place all files in folder3. The goal is to have the user not know where the swfs are. Thanks in advance again!
Using a RewriteCond to match the contents of the query string (since they are not read in a RewriteRule directive, you can extract swfTitle=sometitle.swf and substitute folder1/folder3 for folder1/folder2 in the moviefolder.
This will use a regex pattern like ([^&]+) to match everything up to the next & (which denotes another query param).
# Capture everything after folder2 into %1
RewriteCond %{QUERY_STRING} moviefolder=folder1/folder2([^&]+) [NC]
# Capture everything in the swfTitle param into %2
# Both conditions must be matched...
RewriteCond %{QUERY_STRING} swfTitle=([^&]+) [NC]
# Then silently rewrite mypage.php to substitute folder3,
# and pass in the original swfTitle captured above
RewriteRule ^mypage\.php$ mypage.php?moviefolder=folder1/folder3%1&swfTitle=%2 [L]
Hopefully, you won't get a rewrite loop, since the rewritten folder1/folder3 won't match the second time. [NC] allows for a case-insensitive match.
I did manage to successfully test this over at http://htaccess.madewithlove.be/, using the sample input:
http://example.com/mypage.php?swfTitle=thetitle.swf&moviefolder=folder1/folder2/thing
---> http://example.com/mypage.php?moviefolder=folder1/folder3/thing&swfTitle=thetitle.swf
http://example.com/mypage.php?moviefolder=folder1/folder2/thing999zzz&swfTitle=thetitle.swf
---> http://example.com/mypage.php?moviefolder=folder1/folder3/thing999zzz&swfTitle=thetitle.swf

How can I get mod_rewrite to match a rule just once

I have the following URL...
http://localhost/http.mygarble.com/foundationsofwebprogramming/86
...that I want to convert into the following:
http://localhost/http.mygarble.com/php/blog.php?subdomain=foundationsofwebprogramming&page=posts&label=86
I thought I could achieve this with the following rule:
RewriteRule ([^/]+)/([^/]+)$ php/blog.php?subdomain=$1&page=post&label=$2 [NC,L]
However what I find is that this rule is applied repeatedly, resulting in an internal server error. I understand that when the URI is transformed using this rule, the resulting URI will also match the rule, and therefore it is applied again ad-infinitum.
My previous (admittedly rather hazy) understanding was that the [L] flag would stop further processing, although I now understand that this simply means that only the remainder of the rules are skipped, and does not stop the rewrite engine running through the rules again.
I can fix this problem by adding the following condition...
RewriteCond $0 !php/blog.php
RewriteRule ([^/]+)/([^/]+)$ php/blog.php?subdomain=$1&page=post&label=$2 [NC,L]
...or by writing a more specific regular expression. But what I really want to do is find a way of stopping the rewrite engine from attempting ANY further matches once this rule is matched once. Is this possible?
Many thanks.
Usually 2 methods are used.
The first one is a Rewrite Condition testing that the requested file is not a real file. When internal recursion arise your php/blog.php is a real file and rewriterule is not executed the 2nd time. Side-effect is that any request for a file which exists won't be rewritten (which can be good side effect)
RewriteCond %{REQUEST_FILENAME} !-f
The second solution is to check you're not in an internal redirection with:
RewriteCond %{ENV:REDIRECT_STATUS} ^$
Side effect of this 2nd solution is that the rewriteRule cannot be applied if some other rules are applied before (if you want some internal redirection to run after a first pass of rewriting in fact).
Edit
For completion I will add a third method: the [NS] or [nosubreq] tag seems to be doing the same thing. Preventing the rule usage after an internal redirection.
And the third method is to upgrade apache to 2.3.9 or higher and use [END] flag instead of [L].
No side effects

Apache RewriteRule with RewriteMap

I've got a RewriteMap that looks like this:
Guide 1
Mini-Guide 2
White Paper 3
and I'm including it into Apache via
RewriteMap legacy txt:/var/www/site/var/rewrite_map.txt
I want to create a RewriteRule that will allow only values from the left side of said RewriteMap to be in this position;
RewriteRule ^/section/downloads/(${legacy})/(.*)$ /blah.php?subsection=${legacy:%1}&title=$2
I know I can use ${legacy} on the right side, but can I use it on the left, and if so, how?
In your map file, the left side is the key and the right side is the value. When you create a rule for matching against a map, you input the key and it outputs the value.
Change your RewriteRule to this:
# Put these on one line
RewriteRule ^/section/downloads/([a-zA-Z-]+)/(.*)$
/blah.php?subsection=${legacy:$1}&title=$2
The first grouping captures the string in the incoming URL. The $1 in the replacement applies it to the named map. To make a default value, change ${legacy:$1} to ${legacy:$1|Unknown}.
Finally, if you only want the rule to work on values that are in the map file, add a RewriteCond:
RewriteCond ${legacy:$1|Unknown} !Unknown
# Put these on one line
RewriteRule ^/section/downloads/([a-zA-Z-]+)/(.*)$
/blah.php?subsection=${legacy:$1}&title=$2
The condition says if the map does not return the default value (Unknown), then run the next rule. Otherwise, skip the rule and move on.
Apache RewriteMap
another variant:
# %1 will be the subpattern number1 afterwards
RewriteCond %{REQUEST_URI} ^/section/downloads/(.*)
# check if there is no mapping for %1
RewriteCond ${legacy:%1} !^$
# if there is rewrite it
RewriteRule ^/(.*) /blah.php?subsection=${legacy:%1}&title=$2 [R]
You said, you want to only allow values found in the map. This isn't possible unless you specify an additional restriction in regex for the capture group. There's no way to do it with the map itself. There's no "map.keys" syntax, as far as I know, that you can apply in the left hand side, the pattern.
BUT,
You can specify a default value if the captured value is not found. This way:
## all on one line
RewriteRule ^/section/downloads/([a-zA-Z-]+)/(.*)$
/blah.php?subsection=${legacy:$1|defaultValue}&title=$2
Replace "defaultValue" with whatever you like. For example 0 (zero), or "notfound", if the given arg is not found in the map.
You can then either rewrite the result of that, with another rule, or just allow it to flow through and provide a "404" message at the URL with the default value.
If you choose to use another rule, then it would look like this:
## all on one line
RewriteRule ^/section/downloads/([a-zA-Z-]+)/(.*)$
/blah.php?subsection=${legacy:$1|notFoundMarker}&title=$2
## This rule fires if the lookupKey was not found in the map in the prior rule.
RewriteRule ^/blah.php?subsection=notFoundMarker /404.php [L]