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