Redirect to new URL having looked up new "ID" in RewriteMap which appears in two places in the target URL - apache

This one may have been answered but I can't seem to find one that fits my specifics. So apologies if it sounds old.
My rewrite consists of using a rewrite map and a redirect to another domain.
For example:
An old site has moved and new id's have been generated along with file name changes
So this URL:
https://www.example.com/gallery/951/med_U951I1424251158.SEQ.0.jpg
would need to be redirected to
https://www.example2.com/gallery/5710/med_U5710I1424251158.SEQ.0.jpg
The new value 5710 comes from a rewrite map lookup where I pass 951
there are two changes needed and the 2nd change has two possible variations:
The first change is gallery/951 to gallery/5710
The 2nd change is the filename where the id can be delimited in either of 2 ways:
U951I - delimited between a U and an I
or
U951. - delimited between a U and a .
I started with something like this:
RewriteRule "^gallery/(\d+)/(\s+)$"
But that is as far as I can get.

You could do something like the following:
RewriteCond ${MapName:$1|XXX} (.+)
RewriteRule ^/?gallery/(\d+)/(.*U)\1([I.][^/]+)$ https://www.example2.com/gallery/%1/$2%1$3 [R=302,L]
I'm assuming the domain being redirected to resides on a different server, otherwise (if this is in .htaccess) you would need to check the requested hostname in an additional condition (RewriteCond directive).
Explanation:
The regex ^/?gallery/(\d+)/(.*U)\1([I.][^/]+)$ matches and captures the relevant parts of the source URL...
^/?gallery/ - Literal text (optional slash prefix if this is being used in a server context, as opposed to .htaccess)
(\d+) - Captures the "old" member ID (later available using the $1 backreference and used to lookup the "new" member ID from the RewriteMap)
/ - literal slash
(.*U) - Captures the first part of the filename before the "old" member ID. Later available in the $2 backreference.
\1 - An internal backreference matches the "old" member ID that occurred in the 2nd path segment - Not captured.
([I.][^/]+)$ - Captures the last part of the filename after the "old" member ID - Later available in the $3 backreference. The character classs [I.] matches either an I or a literal . (the dot does not need to be escaped when used inside a character class). And [^/]+ matches everything thereafter to the end of the filename (URL-path).
The RewriteCond directive serves to lookup the new member ID (using the old member ID saved in the $1 backreference) in the rewrite map defined earlier in the server config. The result is then captured, which is later available in the %1 backreference. I set a default XXX so it would be easy to spot any lookups that fail.
Using the RewriteCond directive means we only lookup the rewrite map once. (Results are probably cached anyway, but it saves repetition at the very least.)
/%1/$2%1$3 - The substitution string is then constructed from the backreferences captured earlier:
%1 is the new member ID (captured in the preceding CondPattern)
$2 is the part of the filename before the member ID (captured by the RewriteRule pattern against the URL-path).
$3 is the part of the filename after the member ID.
Note that backreferences of the form $n refer to captured groups in the RewriteRule pattern and backreferences of the form %n refer to captured groups in the last matched CondPattern (RewriteCond directive).

Related

Replacing one of the parameters in the URL having a query string and retaining the other parameters

I'm trying to replace a URL parameter value with the Apache rewrite.
RewriteCond ℅{QUERY_STRING} market=value [NC]
RewriteRule ^/page.html /page.html?market=new-value [R=301,L,NC]
The URL pattern is https://example.com/page.html?market=value&store=value
I need to replace only the market parameter value and retain the store parameter as is.
RewriteRule ^/page.html /page.html?market=new-value [R=301,L,NC]
Note that when used in .htaccess, the URL-path matched by the RewriteRule pattern does not start with a slash, so the regex ^/page.html used above will never match in this context.
Try the following instead:
RewriteCond %{QUERY_STRING} ^(.*&)?(market=)value(&.*)?$
RewriteRule ^/?page\.html$ %{REQUEST_URI}?%1%2new-value%3 [NE,R=301,L]
This looks for the market=value URL parameter name/value pair anywhere in the query string. We capture the parts of the query string that surround the market URL parameter value (including the parameter name itself). These are saved in the following backreferences:
%1 / pattern (.*&)? - the part of the query string before the market URL parameter (if any). This includes the & delimiter before the market URL parameter name.
%2 / pattern (market=) - captures the literal text market=. This must match to be successful. This simply saves repetition later.
%3 / pattern (&.*)? - the part of the query string after the market URL parameter (if any). This includes the & delimiter after the market URL parameter value.
The REQUEST_URI server variable in the substitution string contains the full root-relative URL-path. We follow this with the query string that we rebuild using the backreferences as explained above, injecting new-value as the value of the market URL param (replacing the value this URL parameter had originally).
The NE (noescape) flag is required to prevent the & (URL param delimiter) being URL-encoded in the response.
Note that I removed the NC (nocase) flag from both the RewriteCond and RewriteRule directives. URL parameter names are not normally case-insensitive and this rule (as written) will preserve the case used in the request.
Test first with a 302 (temporary) redirect to avoid potential caching issues, even if you change this to a permanent redirect later. And you will need to clear your browser cache before testing, since the earlier 301 (permanent) redirect will have been cached by the browser.

How to mod_rewrite query string which includes path and parameters?

My website uses a rather complicated query string parameter: Its value is a path including parameters.
For SEO (Search Engine Optimization) etc. I'm now attempting to mod_rewrite shortened versions...
example.com/path/c1/d1/e1.html?x=x1&y=y1
example.com/path/c2/d2/e2.html?x=x2&y=y2
example.com/path/c2/d3/e4.html?x=x5&y=y6
...to the currently required...
example.com/path/?param=a/b/c1/d1/e1?x=x1&y=y1
example.com/path/?param=a/b/c2/d2/e2?x=x2&y=y2
example.com/path/?param=a/b/c2/d3/e4?x=x5&y=y6
So the goal is to...
get rid of the fixed part (?param=a/b/) to shorten the address and
don't have two ? in the visible address
preserve the query string value's necessary variable path components (like c1/d1/e1 or c2/d2/e2 or c2/d3/e4)
add .html to the final part before the query string value's ? to make the folder structure appear 1 level less deep
preserve the query string value's necessary variable parameters (like ?x=x1&y=y1 or ?x=x2&y=y2 or ?x=x5&y=y6)
After hours of research and attempting lots of things that did not work, I signed up here to request your advice on how to solve this mess. Would you please be so kind to assist?
Edit / additional infos:
After the fixed string /path/?param=a/b/ it is always 3 variable path segments like c1/d1/e1.
These variable segments can contain alphanumerical characters a-z A-Z 0-9, dash symbol - and bracket symbols ( and ).
Same applies to the parameter values (x1, y1). Additionally, y1 can contain percent symbol % due to URL-encoding.
Using two question marks (one to start the query string and the other as part of the parameter value) looks invalid but works.
The actual file that handles the request is /path/index.php.
Try the following at the top of your .htaccess file, using mod_rewrite:
RewriteEngine on
# REDIRECT: /path/?param=a/b/c1/d1/e1?x=1&y=y1
RewriteCond %{THE_REQUEST} ^[A-Z]{3,7}\s/path/(?:index\.php)?\?param=a/b/([^/]+/[^/]+/[^/]+)\?(x=[^&]+&y=[^&]+)\s
RewriteRule ^(path)/(?:index\.php)?$ /$1/%1.html?%2 [R=302,L]
# REWRITE: /path/c1/d1/e1.html?x=x1&y=y1
RewriteCond %{QUERY_STRING} ^(x=[^&]+&y=[^&]+)$
RewriteRule ^(path)/([^/]+/[^/]+/[^/]+)\.html$ $1/index.php?param=a/b/$2?%1 [L]
The first rule redirects any direct requests for the "old" URL of the form /path/?param=a/b/c1/d1/e1?x=1&y=y1 (index.php is optional) to the "new" canonical URL of the form /path/c1/d1/e1.html?x=x1&y=y1. This is for the benefit of search engines and any third party inbound links that cannot be updated. You must, however, have already changed all your internal links to the "new" canonical URL.
By matching against THE_REQUEST (as opposed to the QUERY_STRING) we avoid a redirect loop by preventing the rewritten URL from being redirected. THE_REQUEST contains the first line of the request headers and is not changed by other rewrites. For example, THE_REQUEST would contain a string of the form:
GET /path/?param=a/b/c1/d1/e1?x=1&y=y1 HTTP/1.1
This is currently a 302 (temporary) redirect. Only change this to a 301 (permanent) redirect once you have tested that this works OK, in order to avoid potential caching issues.
The second rule internally rewrites requests for the "new" canonical URL, eg. /path/c1/d1/e1.html?x=x1&y=y1, back to the original/underlying URL-path, eg. /path/index.php?param=a/b/c1/d1/e1?x=1&y=y1. The & before the last URL parameter is intentional un-escaped (ie. URL decoded) as discussed in comments.
The $1 and $2 backreferences refer back to the captured groups in the RewriteRule pattern. Whereas the %1 and %2 backreferencs refer to the captured groups in the preceding CondPattern.
These variable segments can contain alphanumerical characters a-z A-Z 0-9, dash symbol - and bracket symbols ( and ).
I've used a more general (and shorter) subpattern in the regex above which will match more characters, but is arguably easier to read. ie. [^/]+ - matches anything except a slash and [^&]+ - matches anything except a &.
If you specifically wanted to match only the allowed characters then you could change the above subpatterns to [a-zA-Z0-9()%-]+ or [\w()%-]+ which also matches underscores (_).
UPDATE: x and y are just examples for parameter names, but in reality there can be lots of different parameter names.
the parameters have more than a single character. They consist of letters a-z, A-Z and in the future maybe digits 0-9. There can be more than the two parameters x and y.
Maybe just match any query string (providing there is a query string).
Try the following instead:
# REDIRECT: /path/?param=a/b/c1/d1/e1?x=1&y=y1
RewriteCond %{THE_REQUEST} ^[A-Z]{3,7}\s/path/(?:index\.php)?\?param=a/b/([^/]+/[^/]+/[^/]+)\?([^\s]+)
RewriteRule ^(path)/(?:index\.php)?$ /$1/%1.html?%2 [R=302,L]
# REWRITE: /path/c1/d1/e1.html?x=x1&y=y1
RewriteCond %{QUERY_STRING} ^(.+)$
RewriteRule ^(path)/([^/]+/[^/]+/[^/]+)\.html$ $1/index.php?param=a/b/$2?%1

.htaccess: Add optional language parameter to all pages

I have a .htaccess file rewriting all URLs. For example:
# Special urls
RewriteRule ^(article)/([^/]*)(?:/[^/]*)?$ /index.php?page=$1&keyword=$2 [L,QSA,NC]
RewriteRule ^(privacy)$ /index.php?page=legal&type=$1 [L,QSA,NC]
RewriteRule ^(imprint)$ /index.php?page=legal&type=$1 [L,QSA,NC]
# All urls
RewriteRule ^([0-9a-zA-Z\s]+)$ /index.php?page=$1 [QSA]
Now I want to integrate a language parameter in my URL. For example
example.com/test ➡ /index.php?page=test
example.com/en/test ➡ /index.php?lang=en&page=test
How would I accomplish this without having to edit all RewriteRules? Is there a way to check if the part after example.com matches a regex, append a query parameter and handle all future rules normally?
Yes, this is possible. Add the following rule before your existing rules:
# Add optional language URL param if present in the first path segment
RewriteRule ^(\w\w)/(.*) $2?lang=$1 [QSA,DPI]
UPDATE: The DPI flag discards the original path-info that would otherwise be appended to the URL-path after the rewrite. This would result in the directives that immediately follow from failing to match, until the next round of processing. (See update below that goes into more detail.)
This assumes that the language code is always 2 characters.
The URL is rewritten to remove the language path segment from the URL-path and appends this as a lang URL parameter. The L flag is specifically omitted so the following rules are left to match the URL-path without the language code.
Since the following rules already have the QSA flag then the lang= URL parameter is appended.
Note, however, that the lang=en parameter is appended at the end of the query string, not prefixed to the beginning, as in your example.
UPDATE: It seems like not only the language gets appended, but also the page name. For example: example.com/de/index results in index?lang=de/index
Solved by adding DPI to [QSA] ➡ [QSA,DPI]
Hhhmmm, yes and no... given only the directives as stated in the question this should still "work" without the DPI flag (although not as efficiently). In fact, index?lang=de/index does not seem to be possible as a resulting URL (there's no page URL parameter)? It's possible that other directives are perhaps resulting in the path-info being appended to the query string? One thing of note is that the last rule stated in the question is missing the L flag, so any directives that follow are perhaps being unnecessarily processed (and even conflicting). However, the DPI flag is certainly an improvement here and should be added.
In detail...
Given a request of the form /de/index, and /de does not exist as a physical directory, then the /index part on the end of the requested URL-path is additional pathname information (path-info) and this is appended to the URL-path after the rewrite above (which is indeed undesirable). So, a request of the form /de/index is rewritten to index?lang=de, which becomes index/index?lang=de after the path-info is re-appended (note that it's not appended to the query string).
The resulting URL-path index/index (with appended path-info) fails to match the RewriteRule directives that follow. However, the rewrite engine then starts over, at which point the path-info that was (unnecessarily) added is then naturally discarded before the next round of processing. This results in the "correct" index?lang=de URL being used as the input for the second round of processing. This matches the last rule stated in the question and the request is finally rewritten to /index.php?page=index&lang=de.
So, the DPI flag shouldn't strictly be necessary here for it to "work". However, it is certainly recommended as it avoids the unnecessary 2nd pass through the rewrite engine. With the DPI flag, the path-info is not appended after the first rewrite, so the URL-path would match the appropriate rule on the first pass.

How to rewrite URLs in htaccess that end with recurring characters

I have changed web platforms and have old URLs that I cannot and do not want to match on the new platform where the old content is now living.
I have an array of old product URLs that all have '-p-' in the URL, followed by a string of numbers and ending in .html (osCommerce platform URLs).
I would like to know how to rewrite:
/x/[rest-of-url]-p-[random numbers].html
to
/x/[rest-of-url]
I would like the end result to look something like this:
http://www.shop.com/shop/versace-black-snakeskin-pony-hair-hobo-p-2214.html
redirects to:
http://www.shop.com/shop/versace-black-snakeskin-pony-hair-hobo
Does anyone know if this is doable in the htaccess file as a rewrite rule?
My managed hosting service providers BeepWeb answered my question.
RewriteRule ^/shop/(.*)-p-(.*).html$ http://www.shop.com/product/$1/ [R=302]
The first argument is the URI that you are matching. The (.) matches any characters. The second argument is the destination URL. The $1 corresponds to the first (.). $2 would be the second (.*), and so on... The [R=302] tells the rewrite to be a 302 redirect (use [R=301] for a 301 redirect).
Using the (.) is essentially like using a wildard. You can instead narrow this down by specifying which characters you want to match as opposed to all characters (instead of using (.) you could use ([abc]*) which would match only against a, b and c characters).
Also, be careful that you do not match other URLs unintentionally (i.e. you need to make sure that the pattern matches are unique to the URLs being rewritten).
If you need the source reference, see the following:
https://httpd.apache.org/docs/current/rewrite/intro.html
Thanks again to http://www.beepweb.com for their detailed response.
Hope it helps others.

How to change URL argument from arg-1 to arg_1 before apache processes it? use .htaccess?

I have a CMS that takes url arguments to return a list of results with this structure:
website.com/argument_1/argument_2.
In order for the site to return the results the args have to have underscores.
However, the code that is generated for the url structure is
website.com/argument-1/argument-2. I need to keep this url structure, but, when someone clicks the link, I need it passed to PHP via apache with the underscores.
I hope that makes sense. Is this done with .htaccess and rewrite rules? I have never written any thing like that before, so any help is appreciated. Thanks
You should definitely do this in PHP. Here is a solution that will work, but requires one pass through RewriteRule for each dash in the URL, so I don't recommend it. But regardless:
RewriteEngine On
RewriteRule ^(.*)-(.*)$ $1_$2 [QSA]
To clarify what will happen here, if the request is for http://www.example.com/argument-1/argument-2/argument-3, the RewriteRule will be run multiple times because it can only replace a single dash per pass. So the URL will be transformed something like this:
Pass 1: http://www.example.com/argument-1/argument-2/argument_3
Pass 2: http://www.example.com/argument-1/argument_2/argument_3
Pass 3: http://www.example.com/argument_1/argument_2/argument_3
As for the $1 and $2, these refer back to the parenthesized components from the regular expression. The regular expression, ^(.*)-(.*)$, breaks down like this:
^ - Match the beginning of the URI
(.*) - Match (and capture) any number of characters, this will be $1 in the replacement
- - Match a dash
(.*) - Match (and capture) any number of characters, this will be $2 in the replacement
$ - Match the end of the URI
So the first pass through, $1 will be /argument-1/argument-2/argument and $2 will be 3. The replacement then puts an underscore between the 2 of them and creates a new URI:
/argument-1/argument-2/argument_3
Then it runs again because the regular expression still matches the new URI (because it has a - in it) and $1 is /argument-1/argument and $2 is 2/argument_3. The replacement then puts an underscore between the 2 of them and creates a new URI:
/argument-1/argument_2/argument_3
Then it runs again because the regular expression still matches the new URI (because it has a - in it) and $1 is /argument and $2 is 1/argument_2/argument_3. The replacement then puts an underscore between the 2 of them and creates a new URI:
/argument_1/argument_2/argument_3
Then Apache continues with this URI since the regular expression no longer matches (because there are no more dashes).