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 1465480394000)
+++ 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> {
 
@@ -228,7 +229,7 @@
                 if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
                     space = true;
                     request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart);
-                } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+                } else if (!HttpParser.isToken(buf[pos])) {
                     throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
                 }
                 pos++;
@@ -276,9 +277,10 @@
                     parsingRequestLineEol = true;
                     space = true;
                     end = pos;
-                } else if ((buf[pos] == Constants.QUESTION)
-                           && (parsingRequestLineQPos == -1)) {
+                } else if ((buf[pos] == Constants.QUESTION) && (parsingRequestLineQPos == -1)) {
                     parsingRequestLineQPos = pos;
+                } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+                    throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
                 }
                 pos++;
             }
@@ -315,7 +317,7 @@
         if (parsingRequestLinePhase == 6) {
             //
             // Reading the protocol
-            // Protocol is always US-ASCII
+            // Protocol is always "HTTP/" DIGIT "." DIGIT
             //
             while (!parsingRequestLineEol) {
                 // Read new bytes if needed
@@ -330,6 +332,8 @@
                     if (end == 0)
                         end = pos;
                     parsingRequestLineEol = true;
+                } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+                    throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
                 }
                 pos++;
             }
@@ -470,7 +474,7 @@
                 headerData.realPos = pos;
                 headerData.lastSignificantChar = pos;
                 break;
-            } else if (chr < 0 || !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 1465480394000)
+++ 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;
 
@@ -142,7 +143,7 @@
             if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
                 space = true;
                 request.method().setBytes(buf, start, pos - start);
-            } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+            } else if (!HttpParser.isToken(buf[pos])) {
                 throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
             }
 
@@ -193,9 +194,10 @@
                 eol = true;
                 space = true;
                 end = pos;
-            } else if ((buf[pos] == Constants.QUESTION)
-                       && (questionPos == -1)) {
+            } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {
                 questionPos = pos;
+            } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
             }
 
             pos++;
@@ -230,9 +232,8 @@
 
         //
         // Reading the protocol
-        // Protocol is always US-ASCII
+        // Protocol is always "HTTP/" DIGIT "." DIGIT
         //
-
         while (!eol) {
 
             // Read new bytes if needed
@@ -247,6 +248,8 @@
                 if (end == 0)
                     end = pos;
                 eol = true;
+            } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
             }
 
             pos++;
@@ -345,7 +348,7 @@
             if (buf[pos] == Constants.COLON) {
                 colon = true;
                 headerValue = headers.addValue(buf, start, pos - start);
-            } else if (buf[pos] < 0 || !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/InternalAprInputBuffer.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/InternalAprInputBuffer.java	(date 1465480394000)
+++ 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;
 
@@ -181,7 +182,7 @@
             if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
                 space = true;
                 request.method().setBytes(buf, start, pos - start);
-            } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+            } else if (!HttpParser.isToken(buf[pos])) {
                 throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
             }
 
@@ -232,9 +233,10 @@
                 eol = true;
                 space = true;
                 end = pos;
-            } else if ((buf[pos] == Constants.QUESTION)
-                       && (questionPos == -1)) {
+            } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {
                 questionPos = pos;
+            } else if (HttpParser.isNotRequestTarget(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
             }
 
             pos++;
@@ -270,7 +272,7 @@
 
         //
         // Reading the protocol
-        // Protocol is always US-ASCII
+        // Protocol is always "HTTP/" DIGIT "." DIGIT
         //
 
         while (!eol) {
@@ -287,6 +289,8 @@
                 if (end == 0)
                     end = pos;
                 eol = true;
+            } else if (!HttpParser.isHttpProtocol(buf[pos])) {
+                throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
             }
 
             pos++;
@@ -385,7 +389,7 @@
             if (buf[pos] == Constants.COLON) {
                 colon = true;
                 headerValue = headers.addValue(buf, start, pos - start);
-            } else if (buf[pos] < 0 || !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/LocalStrings.properties
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- java/org/apache/coyote/http11/LocalStrings.properties	(date 1465480394000)
+++ 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.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
@@ -45,4 +47,4 @@
 iob.illegalreset=The response may not be reset once it has been committed
 iob.responseheadertoolarge.error=An attempt was made to write more data to the response headers than there was room available in the buffer. Increase maxHttpHeaderSize on the connector or write less data into the response headers.
 
-iob.nio2.nullSocket=Socket was null while trying to process exception. See Bug 57749
\ No newline at end of file
+iob.nio2.nullSocket=Socket was null while trying to process exception. See Bug 57749
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 1465480394000)
+++ 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);
 
 
     /**
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 1465480394000)
+++ java/org/apache/tomcat/util/http/parser/HttpParser.java	(revision )
@@ -34,33 +34,58 @@
  */
 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) {
             return input;
@@ -91,23 +116,46 @@
         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) {
-        // Fast for correct values, slower for incorrect ones
+        // Fast for correct values, slower for some incorrect ones
         try {
-            return isHex[c];
+            return IS_HEX[c];
         } catch (ArrayIndexOutOfBoundsException ex) {
             return false;
         }
     }
+
+
+    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;
+        }
+    }
+
 
     // Skip any LWS and return the next char
     static int skipLws(StringReader input, boolean withReset) throws IOException {
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 1465480394000)
+++ 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;
 
@@ -103,6 +105,17 @@
     }
 
     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
@@ -123,11 +136,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);
+            }
+        }
     }
 }
