This patch fixes a problem in mod_rewrite. The following config with the
RewriteRule in per-directory context works fine for query strings:
<Directory "/">
RewriteEngine On
RewriteRule ^foo http://hostname/bar [R=301,L]
</Directory>
But the same RewriteRule in per-server context doesn't work:
<VirtualHost *:80>
RewriteEngine On
RewriteRule ^/foo http://hostname/bar [R=301,L]
</VirtualHost>
The second example leads to double URL-escaping of the query string:
GET /foo?q=%25
301 Moved Permanently
Location: http://hostname/bar?q=%25%25
This is PR 50447 [1], and the issue was fixed for per-directory contexts in
r1044673 [2]. However, RewriteRules in server context are handled differently
in mod_rewrite, and their handling wasn't updated. The attached patch applies
the same fix for such rules. The second patch adds a regression test for the
issue in t/modules/rewrite.t. Both of the patches are against trunk.
[1] https://bz.apache.org/bugzilla/show_bug.cgi?id=50447
[2] https://svn.apache.org/r1044673
Regards,
Evgeny Kotkov
mod_rewrite: Fix double escaping of the query string for RewriteRules in
server context.
See PR 50447 and r1044673 that fixed this for directory contexts.
Index: modules/mappers/mod_rewrite.c
===================================================================
--- modules/mappers/mod_rewrite.c (revision 1735001)
+++ modules/mappers/mod_rewrite.c (working copy)
@@ -4548,6 +4548,7 @@ static int hook_uri2file(request_rec *r)
unsigned int port;
int rulestatus;
void *skipdata;
+ const char *oargs;
/*
* retrieve the config structures
@@ -4598,6 +4599,12 @@ static int hook_uri2file(request_rec *r)
}
/*
+ * remember the original query string for later check, since we don't
+ * want to apply URL-escaping when no substitution has changed it.
+ */
+ oargs = r->args;
+
+ /*
* add the SCRIPT_URL variable to the env. this is a bit complicated
* due to the fact that apache uses subrequests and internal redirects
*/
@@ -4731,11 +4738,21 @@ static int hook_uri2file(request_rec *r)
/* append the QUERY_STRING part */
if (r->args) {
+ char *escaped_args = NULL;
+ int noescape = (rulestatus == ACTION_NOESCAPE ||
+ (oargs && !strcmp(r->args, oargs)));
+
r->filename = apr_pstrcat(r->pool, r->filename, "?",
- (rulestatus == ACTION_NOESCAPE)
+ noescape
? r->args
- : ap_escape_uri(r->pool, r->args),
+ : (escaped_args =
+ ap_escape_uri(r->pool,
r->args)),
NULL);
+
+ rewritelog((r, 1, NULL, "%s %s to query string for redirect
%s",
+ noescape ? "copying" : "escaping",
+ r->args ,
+ noescape ? "" : escaped_args));
}
/* determine HTTP redirect response code */
mod_rewrite: Add regression tests for PR 50447 (double URL-escaping of
the query string).
Index: t/conf/extra.conf.in
===================================================================
--- t/conf/extra.conf.in (revision 1735001)
+++ t/conf/extra.conf.in (working copy)
@@ -234,6 +234,9 @@
## Proxy and QSA
RewriteRule ^proxy-qsa.html$
http://@SERVERNAME@:@PORT@/modules/cgi/env.pl?foo=bar [QSA,L,P]
+ ## Redirect, directory context
+ RewriteRule ^redirect-dir.html$ http://@SERVERNAME@:@PORT@/foobar.html
[L,R=301]
+
</Directory>
### Proxy pass-through to env.pl
@@ -243,6 +246,9 @@
RewriteCond %{QUERY_STRING} horse=trigger
RewriteRule ^/modules/rewrite/proxy3/(.*)$
http://@SERVERNAME@:@PORT@/modules/cgi/$1 [L,P]
+ ### Redirect, server context
+ RewriteRule ^/modules/rewrite/redirect.html$
http://@SERVERNAME@:@PORT@/foobar.html [L,R=301]
+
<VirtualHost cve_2011_3368_rewrite>
DocumentRoot @SERVERROOT@/htdocs/modules/proxy
RewriteEngine On
Index: t/modules/rewrite.t
===================================================================
--- t/modules/rewrite.t (revision 1735001)
+++ t/modules/rewrite.t (working copy)
@@ -12,11 +12,20 @@ use Apache::TestUtil;
my @map = qw(txt rnd prg); #dbm XXX: howto determine dbm support is available?
my @num = qw(1 2 3 4 5 6);
my @url = qw(forbidden gone perm temp);
-my @todo = ();
+my @todo;
my $r;
-plan tests => @map * @num + 11, todo => \@todo, need_module 'rewrite';
+if (!have_min_apache_version('2.5')) {
+ # PR 50447, server context
+ push @todo, 26
+}
+if (!have_min_apache_version('2.4')) {
+ # PR 50447, directory context (r1044673)
+ push @todo, 24
+}
+plan tests => @map * @num + 15, todo => \@todo, need_module 'rewrite';
+
foreach (@map) {
foreach my $n (@num) {
## throw $_ into upper case just so we can test out internal
@@ -58,6 +67,19 @@ $r = GET_BODY("/modules/rewrite/qsa.html?baz=bee")
chomp $r;
ok t_cmp($r, qr/\nQUERY_STRING = foo=bar\&baz=bee\n/s, "query-string append
test");
+# PR 50447 (double URL-escaping of the query string)
+my $hostport = Apache::TestRequest::hostport();
+
+$r = GET("/modules/rewrite/redirect-dir.html?q=%25", redirect_ok => 0);
+ok t_cmp($r->code, 301, "per-dir redirect response code is OK");
+ok t_cmp($r->header("Location"), "http://$hostport/foobar.html?q=%25",
+ "per-dir query-string escaping is OK");
+
+$r = GET("/modules/rewrite/redirect.html?q=%25", redirect_ok => 0);
+ok t_cmp($r->code, 301, "redirect response code is OK");
+ok t_cmp($r->header("Location"), "http://$hostport/foobar.html?q=%25",
+ "query-string escaping is OK");
+
if (have_module('mod_proxy')) {
$r = GET_BODY("/modules/rewrite/proxy.html");
chomp $r;