Index: java/org/apache/coyote/http11/LocalStrings.properties
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/LocalStrings.properties	(date 1454441552000)
+++ java/org/apache/coyote/http11/LocalStrings.properties	(revision )
@@ -33,8 +33,10 @@
 iib.eof.error=Unexpected EOF read on the socket
 iib.failedread.apr=Read failed with APR/native error code [{0}]
 iib.filter.npe=You may not add a null filter
-iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 2616 and has been ignored.
-iib.invalidmethod=Invalid character (CR or LF) found in method name
+iib.invalidheader=The HTTP header line [{0}] does not conform to RFC 7230 and has been ignored.
+iib.invalidmethod=Invalid character found in method name. HTTP method names must be tokens
+iib.invalidRequestTarget=Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
+iib.invalidHttpProtocol=Invalid character found in the HTTP protocol
 iib.parseheaders.ise.error=Unexpected state: headers already parsed. Buffer not recycled?
 iib.readtimeout=Timeout attempting to read data from the socket
 iib.requestheadertoolarge.error=Request header is too large
Index: test/org/apache/catalina/valves/rewrite/TestRewriteValve.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- test/org/apache/catalina/valves/rewrite/TestRewriteValve.java	(date 1454441552000)
+++ test/org/apache/catalina/valves/rewrite/TestRewriteValve.java	(revision )
@@ -16,6 +16,8 @@
  */
 package org.apache.catalina.valves.rewrite;
 
+import java.nio.charset.StandardCharsets;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -60,7 +62,62 @@
                 "RewriteRule /b/(.*).html$ /c/${mapa:$1|dd}", "/b/x.html", "/c/dd");
     }
 
+    @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", null);
+    }
+
+    @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", null);
+    }
+
+    @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", null);
+    }
+
+    @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", null);
+    }
+
+    @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", null);
+    }
+
     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 {
+        doTestRewrite(config, request, expectedURI, expectedQueryString, null);
+    }
+
+    private void doTestRewrite(String config, String request, String expectedURI,
+            String expectedQueryString, String expectedAttributeValue) throws Exception {
         Tomcat tomcat = getTomcatInstance();
 
         // No file system docBase required
@@ -81,11 +138,29 @@
 
         tomcat.start();
 
-        ByteChunk res = getUrl("http://localhost:" + getPort() + request);
+        ByteChunk res = new ByteChunk();
+        int rc = getUrl("http://localhost:" + getPort() + request, res, null);
+        res.setCharset(StandardCharsets.UTF_8);
 
+        if (expectedURI == null) {
+            // Rewrite is expected to fail. Probably because invalid characters
+            // were written into the request target
+            Assert.assertEquals(400, rc);
+        } else {
-        String body = res.toString();
-        RequestDescriptor requestDesc = SnoopResult.parse(body);
-        String requestURI = requestDesc.getRequestInfo("REQUEST-URI");
-        Assert.assertEquals(expectedURI, requestURI);
+            String body = res.toString();
+            RequestDescriptor requestDesc = SnoopResult.parse(body);
+            String requestURI = requestDesc.getRequestInfo("REQUEST-URI");
+            Assert.assertEquals(expectedURI, requestURI);
+
+            if (expectedQueryString != null) {
+                String queryString = requestDesc.getRequestInfo("REQUEST-QUERY-STRING");
+                Assert.assertEquals(expectedQueryString, queryString);
+            }
+
+            if (expectedAttributeValue != null) {
+                String attributeValue = requestDesc.getAttribute("X-Test");
+                Assert.assertEquals(expectedAttributeValue, attributeValue);
+            }
+        }
     }
 }
Index: java/org/apache/coyote/http11/InternalAprInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/InternalAprInputBuffer.java	(date 1454441552000)
+++ java/org/apache/coyote/http11/InternalAprInputBuffer.java	(revision )
@@ -32,6 +32,7 @@
 import org.apache.tomcat.jni.Status;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.parser.HttpParser;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SocketWrapper;
 
@@ -237,6 +238,8 @@
             } else if ((buf[pos] == Constants.QUESTION)
                        && (questionPos == -1)) {
                 questionPos = pos;
+            } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
             }
 
             pos++;
@@ -289,6 +292,8 @@
                 if (end == 0)
                     end = pos;
                 eol = true;
+            } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
             }
 
             pos++;
@@ -387,7 +392,7 @@
             if (buf[pos] == Constants.COLON) {
                 colon = true;
                 headerValue = headers.addValue(buf, start, pos - start);
-            } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+            } else if (!HttpParser.isToken(buf[pos])) {
                 // If a non-token header is detected, skip the line and
                 // ignore the header
                 skipLine(start);
Index: java/org/apache/tomcat/util/http/parser/HttpParser.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/tomcat/util/http/parser/HttpParser.java	(date 1454441552000)
+++ java/org/apache/tomcat/util/http/parser/HttpParser.java	(revision )
@@ -34,33 +34,57 @@
  */
 public class HttpParser {
 
-    // Arrays used by isToken(), isHex()
-    private static final boolean isToken[] = new boolean[128];
-    private static final boolean isHex[] = new boolean[128];
+    private static final int ARRAY_SIZE = 128;
 
+    private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_HEX = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_NOT_REQUEST_TARGET = new boolean[ARRAY_SIZE];
+    private static final boolean[] IS_HTTP_PROTOCOL = new boolean[ARRAY_SIZE];
+
     static {
-        // Setup the flag arrays
-        for (int i = 0; i < 128; i++) {
-            if (i <= 32) { // includes '\t' and ' '
-                isToken[i] = false;
-            } else if (i == '(' || i == ')' || i == '<' || i == '>'  || i == '@'  ||
+        for (int i = 0; i < ARRAY_SIZE; i++) {
+            // Control> 0-31, 127
+            if (i < 32 || i == 127) {
+                IS_CONTROL[i] = true;
+            }
+
+            // Separator
+            if (    i == '(' || i == ')' || i == '<' || i == '>'  || i == '@'  ||
-                       i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' ||
-                       i == '/' || i == '[' || i == ']' || i == '?'  || i == '='  ||
+                    i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' ||
+                    i == '/' || i == '[' || i == ']' || i == '?'  || i == '='  ||
-                       i == '{' || i == '}') {
-                isToken[i] = false;
-            } else {
-                isToken[i] = true;
+                    i == '{' || i == '}' || i == ' ' || i == '\t') {
+                IS_SEPARATOR[i] = true;
             }
 
-            if (i >= '0' && i <= '9' || i >= 'A' && i <= 'F' ||
-                    i >= 'a' && i <= 'f') {
-                isHex[i] = true;
-            } else {
-                isHex[i] = false;
+            // Token: Anything 0-127 that is not a control and not a separator
+            if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) {
+                IS_TOKEN[i] = true;
             }
+
+            // Hex: 0-9, a-f, A-F
+            if ((i >= '0' && i <='9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) {
+                IS_HEX[i] = true;
-        }
+            }
+
+            // Not valid for request target.
+            // Combination of multiple rules from RFC7230 and RFC 3986. Must be
+            // ASCII, no controls plus a few additional characters excluded
+            if (IS_CONTROL[i] || i > 127 ||
+                    i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' ||
+                    i == '^' || i == '`'  || i == '{' || i == '|' || i == '}') {
+                IS_NOT_REQUEST_TARGET[i] = true;
-    }
+            }
 
+            // Not valid for HTTP protocol
+            // "HTTP/" DIGIT "." DIGIT
+            if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) {
+                IS_HTTP_PROTOCOL[i] = true;
+            }
+        }
+    }
+
     public static String unquote(String input) {
         if (input == null || input.length() < 2 || input.charAt(0) != '"') {
             return input;
@@ -79,19 +103,19 @@
         return result.toString();
     }
 
-    static boolean isToken(int c) {
+    public static boolean isToken(int c) {
         // Fast for correct values, slower for incorrect ones
         try {
-            return isToken[c];
+            return IS_TOKEN[c];
         } catch (ArrayIndexOutOfBoundsException ex) {
             return false;
         }
     }
 
-    static boolean isHex(int c) {
+    public static boolean isHex(int c) {
         // Fast for correct values, slower for incorrect ones
         try {
-            return isHex[c];
+            return IS_HEX[c];
         } catch (ArrayIndexOutOfBoundsException ex) {
             return false;
         }
@@ -383,6 +407,27 @@
         return result;
     }
 
+
+    public static boolean isNotRequestTarget(int c) {
+        // Fast for valid request target characters, slower for some incorrect
+        // ones
+        try {
+            return IS_NOT_REQUEST_TARGET[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return true;
+        }
+    }
+
+
+    public static boolean isHttpProtocol(int c) {
+        // Fast for valid HTTP protocol characters, slower for some incorrect
+        // ones
+        try {
+            return IS_HTTP_PROTOCOL[c];
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            return false;
+        }
+    }
 
     /**
      * Skips all characters until EOF or the specified target is found. Normally
Index: java/org/apache/coyote/http11/AbstractNioInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/AbstractNioInputBuffer.java	(date 1454441552000)
+++ java/org/apache/coyote/http11/AbstractNioInputBuffer.java	(revision )
@@ -21,6 +21,7 @@
 
 import org.apache.coyote.Request;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.parser.HttpParser;
 
 public abstract class AbstractNioInputBuffer<S> extends AbstractInputBuffer<S> {
 
@@ -231,6 +232,8 @@
                 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
                     space = true;
                     request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart);
+                } else if (!HttpParser.isToken(buf[pos])) {
+                    throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
                 }
                 pos++;
             }
@@ -280,6 +283,8 @@
                 } else if ((buf[pos] == Constants.QUESTION)
                            && (parsingRequestLineQPos == -1)) {
                     parsingRequestLineQPos = pos;
+                } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+                    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
                 }
                 pos++;
             }
@@ -331,6 +336,8 @@
                     if (end == 0)
                         end = pos;
                     parsingRequestLineEol = true;
+                } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+                    throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
                 }
                 pos++;
             }
@@ -471,7 +478,7 @@
                 headerData.realPos = pos;
                 headerData.lastSignificantChar = pos;
                 break;
-            } else if (!HTTP_TOKEN_CHAR[chr]) {
+            } else if (!HttpParser.isToken(chr)) {
                 // If a non-token header is detected, skip the line and
                 // ignore the header
                 headerData.lastSignificantChar = pos;
Index: java/org/apache/coyote/http11/InternalInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/InternalInputBuffer.java	(date 1454441552000)
+++ java/org/apache/coyote/http11/InternalInputBuffer.java	(revision )
@@ -28,6 +28,7 @@
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.parser.HttpParser;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SocketWrapper;
 
@@ -199,6 +200,8 @@
             } else if ((buf[pos] == Constants.QUESTION)
                        && (questionPos == -1)) {
                 questionPos = pos;
+            } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
             }
 
             pos++;
@@ -250,6 +253,8 @@
                 if (end == 0)
                     end = pos;
                 eol = true;
+            } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
             }
 
             pos++;
@@ -348,7 +353,7 @@
             if (buf[pos] == Constants.COLON) {
                 colon = true;
                 headerValue = headers.addValue(buf, start, pos - start);
-            } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+            } else if (!HttpParser.isToken(buf[pos])) {
                 // If a non-token header is detected, skip the line and
                 // ignore the header
                 skipLine(start);
Index: java/org/apache/coyote/http11/AbstractInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/AbstractInputBuffer.java	(date 1454441552000)
+++ java/org/apache/coyote/http11/AbstractInputBuffer.java	(revision )
@@ -30,62 +30,10 @@
 
 public abstract class AbstractInputBuffer<S> implements InputBuffer{
 
-    protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128];
-
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm =
-        StringManager.getManager(Constants.Package);
-
-
-    static {
-        for (int i = 0; i < 128; i++) {
-            if (i < 32) {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == 127) {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '(') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == ')') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '<') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '>') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '@') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == ',') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == ';') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == ':') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '\\') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '\"') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '/') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '[') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == ']') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '?') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '=') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '{') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == '}') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else if (i == ' ') {
-                HTTP_TOKEN_CHAR[i] = false;
-            } else {
-                HTTP_TOKEN_CHAR[i] = true;
-            }
-        }
-    }
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
 
 
     /**
