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