This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 8.5.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 57fffc7b75beba6e68a72dc58fd9227f630a47d6 Author: Mark Thomas <ma...@apache.org> AuthorDate: Tue Jan 21 22:24:37 2020 +0000 Improve logging of invalid HTTP header lines --- .../apache/coyote/http11/Http11InputBuffer.java | 12 +++- java/org/apache/tomcat/util/http/HeaderUtil.java | 53 ++++++++++++++ .../util/http/TestHeaderUtiltoPrintableString.java | 83 ++++++++++++++++++++++ webapps/docs/changelog.xml | 4 ++ 4 files changed, 149 insertions(+), 3 deletions(-) diff --git a/java/org/apache/coyote/http11/Http11InputBuffer.java b/java/org/apache/coyote/http11/Http11InputBuffer.java index a0dba8e..ef0b498 100644 --- a/java/org/apache/coyote/http11/Http11InputBuffer.java +++ b/java/org/apache/coyote/http11/Http11InputBuffer.java @@ -28,6 +28,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.MimeHeaders; import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.ApplicationBufferHandler; @@ -785,6 +786,7 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler if (headerParsePos == HeaderParsePosition.HEADER_START) { // Mark the current buffer position headerData.start = byteBuffer.position(); + headerData.lineStart = headerData.start; headerParsePos = HeaderParsePosition.HEADER_NAME; } @@ -953,9 +955,8 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler } if (rejectIllegalHeaderName || log.isDebugEnabled()) { String message = sm.getString("iib.invalidheader", - new String(byteBuffer.array(), headerData.start, - headerData.lastSignificantChar - headerData.start + 1, - StandardCharsets.ISO_8859_1)); + HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart, + headerData.lastSignificantChar - headerData.lineStart + 1)); if (rejectIllegalHeaderName) { throw new IllegalArgumentException(message); } @@ -1016,6 +1017,10 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler private 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 ':'. @@ -1043,6 +1048,7 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler */ 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..71da5e4 --- /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.nio.charset.StandardCharsets; +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<>(); + + 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() { + byte[] bytes = input.getBytes(StandardCharsets.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 08b0425..cf5b336 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -149,6 +149,10 @@ Add support for RFC 5915 formatted, unencrypted EC key files when using a JSSE based TLS connector. (markt) </add> + <add> + When reporting / logging invalid HTTP headers encode any non-printing + characters using the 0xNN form. (markt) + </add> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org