This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 2bf299a366d206d0b0838ab99a79c206ef01f5eb
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jan 21 22:24:37 2020 +0000

    Improve logging of invalid HTTP header lines
---
 .../coyote/http11/InternalAprInputBuffer.java      | 11 +--
 .../apache/coyote/http11/InternalInputBuffer.java  | 11 +--
 .../coyote/http11/InternalNioInputBuffer.java      | 11 ++-
 java/org/apache/tomcat/util/http/HeaderUtil.java   | 53 ++++++++++++++
 .../util/http/TestHeaderUtiltoPrintableString.java | 83 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  8 +++
 6 files changed, 165 insertions(+), 12 deletions(-)

diff --git a/java/org/apache/coyote/http11/InternalAprInputBuffer.java 
b/java/org/apache/coyote/http11/InternalAprInputBuffer.java
index 9a14e35..da2846b 100644
--- a/java/org/apache/coyote/http11/InternalAprInputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalAprInputBuffer.java
@@ -20,7 +20,6 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.net.SocketTimeoutException;
 import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
 
 import org.apache.coyote.InputBuffer;
 import org.apache.coyote.Request;
@@ -30,6 +29,7 @@ import org.apache.tomcat.jni.Socket;
 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.HeaderUtil;
 import org.apache.tomcat.util.http.parser.HttpParser;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SocketWrapper;
@@ -376,6 +376,7 @@ public class InternalAprInputBuffer extends 
AbstractInputBuffer<Long> {
 
         // Mark the current buffer position
         int start = pos;
+        int lineStart = start;
 
         //
         // Reading the header name
@@ -400,7 +401,7 @@ public class InternalAprInputBuffer extends 
AbstractInputBuffer<Long> {
                 // Non-token characters are illegal in header names
                 // Parsing continues so the error can be reported in context
                 // skipLine() will handle the error
-                skipLine(start);
+                skipLine(lineStart, start);
                 return true;
             }
             chr = buf[pos];
@@ -504,7 +505,7 @@ public class InternalAprInputBuffer extends 
AbstractInputBuffer<Long> {
     }
 
 
-    private void skipLine(int start) throws IOException {
+    private void skipLine(int lineStart, int start) throws IOException {
         boolean eol = false;
         int lastRealByte = start;
         if (pos - 1 > start) {
@@ -530,8 +531,8 @@ public class InternalAprInputBuffer extends 
AbstractInputBuffer<Long> {
         }
 
         if (rejectIllegalHeaderName || log.isDebugEnabled()) {
-            String message = sm.getString("iib.invalidheader", new String(buf, 
start,
-                    lastRealByte - start + 1, Charset.forName("ISO-8859-1")));
+            String message = sm.getString("iib.invalidheader", 
HeaderUtil.toPrintableString(
+                    buf, lineStart, lastRealByte - lineStart + 1));
             if (rejectIllegalHeaderName) {
                 throw new IllegalArgumentException(message);
             }
diff --git a/java/org/apache/coyote/http11/InternalInputBuffer.java 
b/java/org/apache/coyote/http11/InternalInputBuffer.java
index 722fdbc..3d194e2 100644
--- a/java/org/apache/coyote/http11/InternalInputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalInputBuffer.java
@@ -20,7 +20,6 @@ import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
-import java.nio.charset.Charset;
 
 import org.apache.coyote.InputBuffer;
 import org.apache.coyote.Request;
@@ -28,6 +27,7 @@ import org.apache.juli.logging.Log;
 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.HeaderUtil;
 import org.apache.tomcat.util.http.parser.HttpParser;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SocketWrapper;
@@ -328,6 +328,7 @@ public class InternalInputBuffer extends 
AbstractInputBuffer<Socket> {
 
         // Mark the current buffer position
         int start = pos;
+        int lineStart = start;
 
         //
         // Reading the header name
@@ -352,7 +353,7 @@ public class InternalInputBuffer extends 
AbstractInputBuffer<Socket> {
                 // Non-token characters are illegal in header names
                 // Parsing continues so the error can be reported in context
                 // skipLine() will handle the error
-                skipLine(start);
+                skipLine(lineStart, start);
                 return true;
             }
 
@@ -475,7 +476,7 @@ public class InternalInputBuffer extends 
AbstractInputBuffer<Socket> {
 
 
 
-    private void skipLine(int start) throws IOException {
+    private void skipLine(int lineStart, int start) throws IOException {
         boolean eol = false;
         int lastRealByte = start;
         if (pos - 1 > start) {
@@ -501,8 +502,8 @@ public class InternalInputBuffer extends 
AbstractInputBuffer<Socket> {
         }
 
         if (rejectIllegalHeaderName || log.isDebugEnabled()) {
-            String message = sm.getString("iib.invalidheader", new String(buf, 
start,
-                    lastRealByte - start + 1, Charset.forName("ISO-8859-1")));
+            String message = sm.getString("iib.invalidheader", 
HeaderUtil.toPrintableString(
+                    buf, lineStart, lastRealByte - lineStart + 1));
             if (rejectIllegalHeaderName) {
                 throw new IllegalArgumentException(message);
             }
diff --git a/java/org/apache/coyote/http11/InternalNioInputBuffer.java 
b/java/org/apache/coyote/http11/InternalNioInputBuffer.java
index 2363fe3..ee241b1 100644
--- a/java/org/apache/coyote/http11/InternalNioInputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalNioInputBuffer.java
@@ -25,6 +25,7 @@ import org.apache.coyote.InputBuffer;
 import org.apache.coyote.Request;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HeaderUtil;
 import org.apache.tomcat.util.http.parser.HttpParser;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.NioChannel;
@@ -542,6 +543,7 @@ public class InternalNioInputBuffer extends 
AbstractInputBuffer<NioChannel> {
         if ( headerParsePos == HeaderParsePosition.HEADER_START ) {
             // Mark the current buffer position
             headerData.start = pos;
+            headerData.lineStart = headerData.start;
             headerParsePos = HeaderParsePosition.HEADER_NAME;
         }
 
@@ -714,8 +716,8 @@ public class InternalNioInputBuffer extends 
AbstractInputBuffer<NioChannel> {
             pos++;
         }
         if (rejectIllegalHeaderName || log.isDebugEnabled()) {
-            String message = sm.getString("iib.invalidheader", new String(buf, 
headerData.start,
-                    headerData.lastSignificantChar - headerData.start + 1, 
DEFAULT_CHARSET));
+            String message = sm.getString("iib.invalidheader", 
HeaderUtil.toPrintableString(
+                    buf, headerData.lineStart, headerData.lastSignificantChar 
- headerData.lineStart + 1));
             if (rejectIllegalHeaderName) {
                 throw new IllegalArgumentException(message);
             }
@@ -729,6 +731,10 @@ public class InternalNioInputBuffer extends 
AbstractInputBuffer<NioChannel> {
     private HeaderParseData headerData = new HeaderParseData();
     public static class HeaderParseData {
         /**
+         * The first character of the header line.
+         */
+        int lineStart = 0;
+        /**
          * When parsing header name: first character of the header.<br />
          * When skipping broken header line: first character of the header.<br 
/>
          * When parsing header value: first character after ':'.
@@ -756,6 +762,7 @@ public class InternalNioInputBuffer extends 
AbstractInputBuffer<NioChannel> {
          */
         MessageBytes headerValue = null;
         public void recycle() {
+            lineStart = 0;
             start = 0;
             realPos = 0;
             lastSignificantChar = 0;
diff --git a/java/org/apache/tomcat/util/http/HeaderUtil.java 
b/java/org/apache/tomcat/util/http/HeaderUtil.java
new file mode 100644
index 0000000..cb40cab
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/HeaderUtil.java
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.http;
+
+public class HeaderUtil {
+
+    /**
+     * Converts an HTTP header line in byte form to a printable String.
+     * Bytes corresponding to visible ASCII characters will converted to those
+     * characters. All other bytes (0x00 to 0x1F, 0x7F to OxFF) will be
+     * represented in 0xNN form.
+     *
+     * @param bytes  Contains an HTTP header line
+     * @param offset The start position of the header line in the array
+     * @param len    The length of the HTTP header line
+     *
+     * @return A String with non-printing characters replaced by the 0xNN
+     *         equivalent
+     */
+    public static String toPrintableString(byte[] bytes, int offset, int len) {
+        StringBuilder result = new StringBuilder();
+        for (int i = offset; i < offset + len; i++) {
+            char c = (char) (bytes[i] & 0xFF);
+            if (c < 0x20 || c > 0x7E) {
+                result.append("0x");
+                result.append(Character.forDigit((c >> 4) & 0xF, 16));
+                result.append(Character.forDigit((c) & 0xF, 16));
+            } else {
+                result.append(c);
+            }
+        }
+        return result.toString();
+    }
+
+
+    private HeaderUtil() {
+        // Utility class. Hide default constructor.
+    }
+}
diff --git 
a/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java 
b/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java
new file mode 100644
index 0000000..f4e7bb3
--- /dev/null
+++ b/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.util.http;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class TestHeaderUtiltoPrintableString {
+
+    @Parameterized.Parameters(name = "{index}: expected[{1}]")
+    public static Collection<Object[]> parameters() {
+        List<Object[]> parameterSets = new ArrayList<Object[]>();
+
+        parameterSets.add(new String[] { "", "" });
+
+        parameterSets.add(new String[] { "abcd", "abcd" });
+
+        parameterSets.add(new String[] { "\u0000abcd", "0x00abcd" });
+        parameterSets.add(new String[] { "ab\u0000cd", "ab0x00cd" });
+        parameterSets.add(new String[] { "abcd\u0000", "abcd0x00" });
+
+        parameterSets.add(new String[] { "\tabcd", "0x09abcd" });
+        parameterSets.add(new String[] { "ab\tcd", "ab0x09cd" });
+        parameterSets.add(new String[] { "abcd\t", "abcd0x09" });
+
+        parameterSets.add(new String[] { " abcd", " abcd" });
+        parameterSets.add(new String[] { "ab cd", "ab cd" });
+        parameterSets.add(new String[] { "abcd ", "abcd " });
+
+        parameterSets.add(new String[] { "~abcd", "~abcd" });
+        parameterSets.add(new String[] { "ab~cd", "ab~cd" });
+        parameterSets.add(new String[] { "abcd~", "abcd~" });
+
+        parameterSets.add(new String[] { "\u007fabcd", "0x7fabcd" });
+        parameterSets.add(new String[] { "ab\u007fcd", "ab0x7fcd" });
+        parameterSets.add(new String[] { "abcd\u007f", "abcd0x7f" });
+
+        parameterSets.add(new String[] { "\u00a3abcd", "0xa3abcd" });
+        parameterSets.add(new String[] { "ab\u00a3cd", "ab0xa3cd" });
+        parameterSets.add(new String[] { "abcd\u00a3", "abcd0xa3" });
+
+        return parameterSets;
+    }
+
+
+    @Parameter(0)
+    public String input;
+    @Parameter(1)
+    public String expected;
+
+
+    @Test
+    public void doTest() throws UnsupportedEncodingException {
+        byte[] bytes = input.getBytes("ISO_8859_1");
+
+        String result = HeaderUtil.toPrintableString(bytes, 0, bytes.length);
+
+        Assert.assertEquals(expected, result);
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 8c304d0..5f7e9bb 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -112,6 +112,14 @@
       </fix>
     </changelog>
   </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <add>
+        When reporting / logging invalid HTTP headers encode any non-printing
+        characters using the 0xNN form. (markt)
+      </add>
+    </changelog>
+  </subsection>
   <subsection name="Cluster">
     <changelog>
       <fix>


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

Reply via email to