Author: markt
Date: Fri Sep  9 13:44:46 2016
New Revision: 1760022

URL: http://svn.apache.org/viewvc?rev=1760022&view=rev
Log:
Additional review of the fix for bug 60013
After review of the httpd behaviour, update the Rewrite valve to better
align with httpd and modify the existing tests where they do not reflect
current httpd behaviour.
Add additional tests to cover various combinations of R, B, NE and QSA
flags along with UTF-8 values in original URIs, re-written URIs,
original query strings and re-written query strings.

Modified:
    tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteRule.java
    tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteValve.java
    tomcat/trunk/java/org/apache/catalina/valves/rewrite/Substitution.java
    tomcat/trunk/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java

Modified: tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteRule.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteRule.java?rev=1760022&r1=1760021&r2=1760022&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteRule.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteRule.java Fri 
Sep  9 13:44:46 2016
@@ -38,7 +38,6 @@ public class RewriteRule {
             substitution.setSub(substitutionString);
             substitution.parse(maps);
             substitution.setEscapeBackReferences(isEscapeBackReferences());
-            substitution.setNoEscape(isNoescape());
         }
         // Parse the pattern
         int flags = 0;

Modified: tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteValve.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteValve.java?rev=1760022&r1=1760021&r2=1760022&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteValve.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/valves/rewrite/RewriteValve.java Fri 
Sep  9 13:44:46 2016
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.StringReader;
+import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Hashtable;
@@ -44,15 +45,54 @@ import org.apache.catalina.Pipeline;
 import org.apache.catalina.connector.Connector;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
+import org.apache.catalina.util.URLEncoder;
 import org.apache.catalina.valves.ValveBase;
 import org.apache.tomcat.util.buf.CharChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
-import org.apache.tomcat.util.buf.UDecoder;
 import org.apache.tomcat.util.buf.UriUtil;
 import org.apache.tomcat.util.http.RequestUtil;
 
 public class RewriteValve extends ValveBase {
 
+    static URLEncoder ENCODER = new URLEncoder();
+    static {
+        /*
+         * Replicates httpd's encoding
+         * Primarily aimed at encoding URI paths, so from the spec:
+         *
+         * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+         *
+         * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
+         *
+         * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+         *              / "*" / "+" / "," / ";" / "="
+         */
+        // ALPHA and DIGIT are always treated as safe characters
+        // Add the remaining unreserved characters
+        ENCODER.addSafeCharacter('-');
+        ENCODER.addSafeCharacter('.');
+        ENCODER.addSafeCharacter('_');
+        ENCODER.addSafeCharacter('~');
+        // Add the sub-delims
+        ENCODER.addSafeCharacter('!');
+        ENCODER.addSafeCharacter('$');
+        ENCODER.addSafeCharacter('&');
+        ENCODER.addSafeCharacter('\'');
+        ENCODER.addSafeCharacter('(');
+        ENCODER.addSafeCharacter(')');
+        ENCODER.addSafeCharacter('*');
+        ENCODER.addSafeCharacter('+');
+        ENCODER.addSafeCharacter(',');
+        ENCODER.addSafeCharacter(';');
+        ENCODER.addSafeCharacter('=');
+        // Add the remaining literals
+        ENCODER.addSafeCharacter(':');
+        ENCODER.addSafeCharacter('@');
+        // Add '/' so it isn't encoded when we encode a path
+        ENCODER.addSafeCharacter('/');
+    }
+
+
     /**
      * The rewrite rules that the valve will use.
      */
@@ -285,13 +325,13 @@ public class RewriteValve extends ValveB
             // converted to a string
             MessageBytes urlMB = context ? request.getRequestPathMB() : 
request.getDecodedRequestURIMB();
             urlMB.toChars();
-            CharSequence url = urlMB.getCharChunk();
+            CharSequence urlDecoded = urlMB.getCharChunk();
             CharSequence host = request.getServerName();
             boolean rewritten = false;
             boolean done = false;
             for (int i = 0; i < rules.length; i++) {
                 RewriteRule rule = rules[i];
-                CharSequence test = (rule.isHost()) ? host : url;
+                CharSequence test = (rule.isHost()) ? host : urlDecoded;
                 CharSequence newtest = rule.evaluate(test, resolver);
                 if (newtest != null && !test.equals(newtest.toString())) {
                     if (container.getLogger().isDebugEnabled()) {
@@ -301,7 +341,7 @@ public class RewriteValve extends ValveB
                     if (rule.isHost()) {
                         host = newtest;
                     } else {
-                        url = newtest;
+                        urlDecoded = newtest;
                     }
                     rewritten = true;
                 }
@@ -323,35 +363,55 @@ public class RewriteValve extends ValveB
                 // - redirect (code)
                 if (rule.isRedirect() && newtest != null) {
                     // append the query string to the url if there is one and 
it hasn't been rewritten
-                    String queryString = request.getQueryString();
-                    StringBuffer urlString = new StringBuffer(url);
-                    if (queryString != null && queryString.length() > 0) {
-                        int index = urlString.indexOf("?");
-                        if (index != -1) {
-                            // if qsa is specified append the query
+                    String originalQueryStringEncoded = 
request.getQueryString();
+                    String urlStringDecoded = urlDecoded.toString();
+                    int index = urlStringDecoded.indexOf("?");
+                    String rewrittenQueryStringDecoded;
+                    if (index == -1) {
+                        rewrittenQueryStringDecoded = null;
+                    } else {
+                        rewrittenQueryStringDecoded = 
urlStringDecoded.substring(index + 1);
+                        urlStringDecoded = urlStringDecoded.substring(0, 
index);
+                    }
+
+                    StringBuffer urlStringEncoded = new 
StringBuffer(ENCODER.encode(urlStringDecoded, 
request.getConnector().getURIEncoding()));
+                    if (originalQueryStringEncoded != null && 
originalQueryStringEncoded.length() > 0) {
+                        if (rewrittenQueryStringDecoded == null) {
+                            urlStringEncoded.append('?');
+                            
urlStringEncoded.append(originalQueryStringEncoded);
+                        } else {
                             if (rule.isQsappend()) {
-                                urlString.append('&');
-                                urlString.append(queryString);
-                            }
-                            // if the ? is the last character delete it, its 
only purpose was to
-                            // prevent the rewrite module from appending the 
query string
-                            else if (index == urlString.length() - 1) {
-                                urlString.deleteCharAt(index);
+                                // if qsa is specified append the query
+                                urlStringEncoded.append('?');
+                                
urlStringEncoded.append(ENCODER.encode(rewrittenQueryStringDecoded, 
request.getConnector().getURIEncoding()));
+                                urlStringEncoded.append('&');
+                                
urlStringEncoded.append(originalQueryStringEncoded);
+                            } else if (index == urlStringEncoded.length() - 1) 
{
+                                // if the ? is the last character delete it, 
its only purpose was to
+                                // prevent the rewrite module from appending 
the query string
+                                urlStringEncoded.deleteCharAt(index);
+                            } else {
+                                urlStringEncoded.append('?');
+                                
urlStringEncoded.append(ENCODER.encode(rewrittenQueryStringDecoded, 
request.getConnector().getURIEncoding()));
                             }
-                        } else {
-                            urlString.append('?');
-                            urlString.append(queryString);
                         }
+                    } else if (rewrittenQueryStringDecoded != null) {
+                        urlStringEncoded.append('?');
+                        
urlStringEncoded.append(ENCODER.encode(rewrittenQueryStringDecoded, 
request.getConnector().getURIEncoding()));
                     }
 
                     // Insert the context if
                     // 1. this valve is associated with a context
                     // 2. the url starts with a leading slash
                     // 3. the url isn't absolute
-                    if (context && urlString.charAt(0) == '/' && 
!UriUtil.hasScheme(urlString)) {
-                        urlString.insert(0, 
request.getContext().getEncodedPath());
+                    if (context && urlStringEncoded.charAt(0) == '/' && 
!UriUtil.hasScheme(urlStringEncoded)) {
+                        urlStringEncoded.insert(0, 
request.getContext().getEncodedPath());
+                    }
+                    if (rule.isNoescape()) {
+                        
response.sendRedirect(URLDecoder.decode(urlStringEncoded.toString(), 
request.getConnector().getURIEncoding()));
+                    } else {
+                        response.sendRedirect(urlStringEncoded.toString());
                     }
-                    response.sendRedirect(urlString.toString());
                     response.setStatus(rule.getRedirectCode());
                     done = true;
                     break;
@@ -384,9 +444,9 @@ public class RewriteValve extends ValveB
                 // - qsappend
                 if (rule.isQsappend() && newtest != null) {
                     String queryString = request.getQueryString();
-                    String urlString = url.toString();
+                    String urlString = urlDecoded.toString();
                     if (urlString.indexOf('?') != -1 && queryString != null) {
-                        url = urlString + "&" + queryString;
+                        urlDecoded = urlString + "&" + queryString;
                     }
                 }
 
@@ -421,42 +481,46 @@ public class RewriteValve extends ValveB
             if (rewritten) {
                 if (!done) {
                     // See if we need to replace the query string
-                    String urlString = url.toString();
-                    String queryString = null;
-                    int queryIndex = urlString.indexOf('?');
+                    String urlStringDecoded = urlDecoded.toString();
+                    String queryStringDecoded = null;
+                    int queryIndex = urlStringDecoded.indexOf('?');
                     if (queryIndex != -1) {
-                        queryString = urlString.substring(queryIndex+1);
-                        urlString = urlString.substring(0, queryIndex);
+                        queryStringDecoded = 
urlStringDecoded.substring(queryIndex+1);
+                        urlStringDecoded = urlStringDecoded.substring(0, 
queryIndex);
                     }
-                    // Set the new 'original' URI
+                    // Save the current context path before re-writing starts
                     String contextPath = null;
                     if (context) {
                         contextPath = request.getContextPath();
                     }
+                    // Populated the encoded (i.e. undecoded) requestURI
                     request.getCoyoteRequest().requestURI().setString(null);
                     CharChunk chunk = 
request.getCoyoteRequest().requestURI().getCharChunk();
                     chunk.recycle();
                     if (context) {
+                        // This is neither decoded nor normalized
                         chunk.append(contextPath);
                     }
-                    chunk.append(urlString);
+                    chunk.append(ENCODER.encode(urlStringDecoded, 
request.getConnector().getURIEncoding()));
                     request.getCoyoteRequest().requestURI().toChars();
                     // Decoded and normalized URI
+                    // Rewriting may have denormalized the URL
+                    urlStringDecoded = RequestUtil.normalize(urlStringDecoded);
                     request.getCoyoteRequest().decodedURI().setString(null);
                     chunk = 
request.getCoyoteRequest().decodedURI().getCharChunk();
                     chunk.recycle();
                     if (context) {
-                        chunk.append(contextPath);
+                        // This is decoded and normalized
+                        
chunk.append(request.getServletContext().getContextPath());
                     }
-                    chunk.append(RequestUtil.normalize(UDecoder.URLDecode(
-                            urlString, 
request.getConnector().getURIEncoding())));
+                    chunk.append(urlStringDecoded);
                     request.getCoyoteRequest().decodedURI().toChars();
                     // Set the new Query if there is one
-                    if (queryString != null) {
+                    if (queryStringDecoded != null) {
                         
request.getCoyoteRequest().queryString().setString(null);
                         chunk = 
request.getCoyoteRequest().queryString().getCharChunk();
                         chunk.recycle();
-                        chunk.append(queryString);
+                        chunk.append(ENCODER.encode(queryStringDecoded, 
request.getConnector().getURIEncoding()));
                         request.getCoyoteRequest().queryString().toChars();
                     }
                     // Set the new host if it changed

Modified: tomcat/trunk/java/org/apache/catalina/valves/rewrite/Substitution.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/valves/rewrite/Substitution.java?rev=1760022&r1=1760021&r2=1760022&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/valves/rewrite/Substitution.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/valves/rewrite/Substitution.java Fri 
Sep  9 13:44:46 2016
@@ -20,25 +20,8 @@ import java.util.ArrayList;
 import java.util.Map;
 import java.util.regex.Matcher;
 
-import org.apache.catalina.util.URLEncoder;
-
 public class Substitution {
 
-    private static URLEncoder STATIC_ENCODER = new URLEncoder();
-    static {
-        // Defaults
-        STATIC_ENCODER.addSafeCharacter('~');
-        STATIC_ENCODER.addSafeCharacter('-');
-        STATIC_ENCODER.addSafeCharacter('_');
-        STATIC_ENCODER.addSafeCharacter('.');
-        STATIC_ENCODER.addSafeCharacter('*');
-        STATIC_ENCODER.addSafeCharacter('/');
-        // httpd doesn't encode these either
-        STATIC_ENCODER.addSafeCharacter('?');
-        STATIC_ENCODER.addSafeCharacter('=');
-    }
-
-
     public abstract class SubstitutionElement {
         public abstract String evaluate(Matcher rule, Matcher cond, Resolver 
resolver);
     }
@@ -48,11 +31,7 @@ public class Substitution {
 
         @Override
         public String evaluate(Matcher rule, Matcher cond, Resolver resolver) {
-            if (noEscape) {
-                return value;
-            } else {
-                return STATIC_ENCODER.encode(value, resolver.getUriEncoding());
-            }
+            return value;
         }
 
     }
@@ -66,7 +45,7 @@ public class Substitution {
                 //       We might want to consider providing a dedicated 
decoder
                 //       with an option to add additional safe characters to
                 //       provide users with more flexibility
-                return URLEncoder.DEFAULT.encode(rule.group(n), 
resolver.getUriEncoding());
+                return RewriteValve.ENCODER.encode(rule.group(n), 
resolver.getUriEncoding());
             } else {
                 return rule.group(n);
             }
@@ -139,11 +118,6 @@ public class Substitution {
         this.escapeBackReferences = escapeBackReferences;
     }
 
-    private boolean noEscape;
-    void setNoEscape(boolean noEscape) {
-        this.noEscape = noEscape;
-    }
-
     public void parse(Map<String, RewriteMap> maps) {
 
         ArrayList<SubstitutionElement> elements = new ArrayList<>();

Modified: 
tomcat/trunk/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java?rev=1760022&r1=1760021&r2=1760022&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java 
(original)
+++ tomcat/trunk/test/org/apache/catalina/valves/rewrite/TestRewriteValve.java 
Fri Sep  9 13:44:46 2016
@@ -41,17 +41,17 @@ public class TestRewriteValve extends To
 
     @Test
     public void testNoopRewrite() throws Exception {
-        doTestRewrite("RewriteRule ^(.*) $1 [B]", "/a/%255A", "/a/%255A");
+        doTestRewrite("RewriteRule ^(.*) $1", "/a/%255A", "/a/%255A");
     }
 
     @Test
     public void testPathRewrite() throws Exception {
-        doTestRewrite("RewriteRule ^/b(.*) /a$1 [B]", "/b/%255A", "/a/%255A");
+        doTestRewrite("RewriteRule ^/b(.*) /a$1", "/b/%255A", "/a/%255A");
     }
 
     @Test
     public void testNonNormalizedPathRewrite() throws Exception {
-        doTestRewrite("RewriteRule ^/b/(.*) /b/../a/$1 [B]", "/b/%255A", 
"/b/../a/%255A");
+        doTestRewrite("RewriteRule ^/b/(.*) /b/../a/$1", "/b/%255A", 
"/b/../a/%255A");
     }
 
     // BZ 57863
@@ -75,13 +75,15 @@ public class TestRewriteValve extends To
     @Test
     public void testRewriteEnvVarAndServerVar() throws Exception {
         System.setProperty("some_variable", "something");
-        doTestRewrite("RewriteRule /b/(.*).html$ 
/c/%{ENV:some_variable}%{SERVLET_PATH}", "/b/x.html", "/c/something/b/x.html");
+        doTestRewrite("RewriteRule /b/(.*).html$ 
/c/%{ENV:some_variable}%{SERVLET_PATH}",
+                "/b/x.html", "/c/something/b/x.html");
     }
 
     @Test
     public void testRewriteServerVarAndEnvVar() throws Exception {
         System.setProperty("some_variable", "something");
-        doTestRewrite("RewriteRule /b/(.*).html$ 
/c%{SERVLET_PATH}/%{ENV:some_variable}", "/b/x.html", "/c/b/x.html/something");
+        doTestRewrite("RewriteRule /b/(.*).html$ 
/c%{SERVLET_PATH}/%{ENV:some_variable}",
+                "/b/x.html", "/c/b/x.html/something");
     }
 
     @Test
@@ -90,7 +92,7 @@ public class TestRewriteValve extends To
             doTestRewrite("RewriteRule /b/(.*).html$ /c%_{SERVLET_PATH}", 
"/b/x.html", "/c");
             Assert.fail("IAE expected.");
         } catch (java.lang.IllegalArgumentException e) {
-            // excpected as %_{ is invalid
+            // expected as %_{ is invalid
         }
     }
 
@@ -100,7 +102,7 @@ public class TestRewriteValve extends To
             doTestRewrite("RewriteRule /b/(.*).html$ /c$_{SERVLET_PATH}", 
"/b/x.html", "/c");
             Assert.fail("IAE expected.");
         } catch (java.lang.IllegalArgumentException e) {
-            // excpected as $_{ is invalid
+            // expected as $_{ is invalid
         }
     }
 
@@ -110,19 +112,15 @@ public class TestRewriteValve extends To
                 "/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95", "/c/", 
"param=\u5728\u7EBF\u6D4B\u8BD5");
     }
 
-    private void doTestRewrite(String config, String request, String 
expectedURI) throws Exception {
-        doTestRewrite(config, request, expectedURI, null);
-    }
-
     @Test
     public void testNonAsciiPath() throws Exception {
-        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [B]", 
"/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1", 
"/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
                 "/c/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
     }
 
     @Test
     public void testNonAsciiPathRedirect() throws Exception {
-        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [R,B]",
+        doTestRewrite("RewriteRule ^/b/(.*) /c/$1 [R]",
                 "/b/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
                 "/c/%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
     }
@@ -134,7 +132,7 @@ public class TestRewriteValve extends To
 
     @Test
     public void testNonAsciiQueryString() throws Exception {
-        doTestRewrite("RewriteRule ^/b/id=(.*) /c?id=$1 [B]",
+        doTestRewrite("RewriteRule ^/b/(.*) /c?$1",
                 "/b/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
                 "/c", "id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
     }
@@ -142,28 +140,304 @@ public class TestRewriteValve extends To
 
     @Test
     public void testNonAsciiQueryStringAndPath() throws Exception {
-        doTestRewrite("RewriteRule ^/b/(.*)/id=(.*) /c/$1?id=$2 [B]",
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/$1?$2",
                 "/b/%E5%9C%A8%E7%BA%BF/id=%E6%B5%8B%E8%AF%95",
                 "/c/%E5%9C%A8%E7%BA%BF", "id=%E6%B5%8B%E8%AF%95");
     }
 
 
     @Test
-    public void testNonAsciiQueryStringRedirect() throws Exception {
-        doTestRewrite("RewriteRule ^/b/id=(.*) /c?id=$1 [R,B]",
+    public void testNonAsciiQueryStringAndRedirect() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*) /c?$1 [R]",
                 "/b/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95",
                 "/c", "id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95");
     }
 
 
     @Test
-    public void testNonAsciiQueryStringAndRedirectPath() throws Exception {
-        doTestRewrite("RewriteRule ^/b/(.*)/id=(.*) /c/$1?id=$2 [R,B]",
+    public void testNonAsciiQueryStringAndPathAndRedirect() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/$1?$2 [R]",
                 "/b/%E5%9C%A8%E7%BA%BF/id=%E6%B5%8B%E8%AF%95",
                 "/c/%E5%9C%A8%E7%BA%BF", "id=%E6%B5%8B%E8%AF%95");
     }
 
 
+    @Test
+    public void testNonAsciiQueryStringWithB() throws Exception {
+        doTestRewrite("RewriteRule ^/b/(.*)/id=(.*) /c?filename=$1&id=$2 [B]",
+                "/b/file01/id=%E5%9C%A8%E7%BA%BF%E6%B5%8B%E8%AF%95", "/c",
+                
"filename=file01&id=%25E5%259C%25A8%25E7%25BA%25BF%25E6%25B5%258B%25E8%25AF%2595");
+    }
+
+
+    @Test
+    public void testNonAsciiQueryStringAndPathAndRedirectWithB() throws 
Exception {
+        // Note the double encoding of the result (httpd produces the same 
result)
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*)/id=(.*) 
/c/$1?filename=$2&id=$3 [B,R]",
+                "/b/%E5%9C%A8%E7%BA%BF/file01/id=%E6%B5%8B%E8%AF%95",
+                "/c/%25E5%259C%25A8%25E7%25BA%25BF",
+                "filename=file01&id=%25E6%25B5%258B%25E8%25AF%2595");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsNone() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1", 
"id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1", 
"id=%25C2%25A1");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsR() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1", 
"id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1", 
"id=%25C2%25A1");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", 
"/c/\u00C2\u00A1\u00C2\u00A1", "id=\u00C2\u00A1");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRBNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1%C2%A1", 
"id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsBQSA() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B,QSA]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1",
+                "id=%25C2%25A1&di=%25C2%25AE");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRQSA() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,QSA]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%C2%A1",
+                "id=%C2%A1&di=%C2%AE");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRBQSA() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,QSA]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/%C2%A1%25C2%25A1",
+                "id=%25C2%25A1&di=%C2%AE");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRNEQSA() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE,QSA]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1\u00C2\u00A1",
+                "id=\u00C2\u00A1&di=\u00C2\u00AE");
+    }
+
+
+    @Test
+    public void testUtf8WithBothQsFlagsRBNEQSA() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE,QSA]",
+                "/b/%C2%A1/id=%C2%A1?di=%C2%AE", "/c/\u00C2\u00A1%C2%A1",
+                "id=%C2%A1&di=\u00C2\u00AE");
+    }
+
+
+    @Test
+    public void testUtf8WithOriginalQsFlagsNone() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1",
+                "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithOriginalQsFlagsB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]",
+                "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithOriginalQsFlagsR() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R]",
+                "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithOriginalQsFlagsRB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]",
+                "/b/%C2%A1?id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithOriginalQsFlagsRNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]",
+                "/b/%C2%A1?id=%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1", 
"id=\u00C2\u00A1");
+    }
+
+
+    @Test
+    public void testUtf8WithOriginalQsFlagsRBNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]",
+                "/b/%C2%A1?id=%C2%A1", "/c/\u00C2\u00A1%C2%A1", 
"id=\u00C2\u00A1");
+    }
+
+
+    @Test
+    public void testUtf8WithRewriteQsFlagsNone() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2",
+                "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithRewriteQsFlagsB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [B]",
+                "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
+    }
+
+
+    @Test
+    public void testUtf8WithRewriteQsFlagsR() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R]",
+                "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%C2%A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8WithRewriteQsFlagsRB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B]",
+                "/b/%C2%A1/id=%C2%A1", "/c/%C2%A1%25C2%25A1", "id=%25C2%25A1");
+    }
+
+
+    @Test
+    public void testUtf8WithRewriteQsFlagsRNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,NE]",
+                "/b/%C2%A1/id=%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1", 
"id=\u00C2\u00A1");
+    }
+
+
+    @Test
+    public void testUtf8WithRewriteQsFlagsRBNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*)/(.*) /c/\u00A1$1?$2 [R,B,NE]",
+                "/b/%C2%A1/id=%C2%A1", "/c/\u00C2\u00A1%C2%A1", "id=%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8FlagsNone() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1", "/b/%C2%A1", 
"/c/%C2%A1%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8FlagsB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [B]", "/b/%C2%A1", 
"/c/%C2%A1%25C2%25A1");
+    }
+
+
+    @Test
+    public void testUtf8FlagsR() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R]", "/b/%C2%A1", 
"/c/%C2%A1%C2%A1");
+    }
+
+
+    @Test
+    public void testUtf8FlagsRB() throws Exception {
+        // Note %C2%A1 == \u00A1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B]", "/b/%C2%A1", 
"/c/%C2%A1%25C2%25A1");
+    }
+
+
+    @Test
+    public void testUtf8FlagsRNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,NE]",
+                "/b/%C2%A1", "/c/\u00C2\u00A1\u00C2\u00A1");
+    }
+
+
+    @Test
+    public void testUtf8FlagsRBNE() throws Exception {
+        // Note %C2%A1 == \u00A1
+        // Failing to escape the redirect means UTF-8 bytes in the Location
+        // header which will be treated as if they are ISO-8859-1
+        doTestRewrite("RewriteRule ^/b/(.*) /c/\u00A1$1 [R,B,NE]",
+                "/b/%C2%A1", "/c/\u00C2\u00A1%C2%A1");
+    }
+
+
+    private void doTestRewrite(String config, String request, String 
expectedURI) throws Exception {
+        doTestRewrite(config, request, expectedURI, null);
+    }
+
+
     private void doTestRewrite(String config, String request, String 
expectedURI,
             String expectedQueryString) throws Exception {
 



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to