You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2015/12/16 20:24:17 UTC
svn commit: r1720418 - in /tomcat/tc6.0.x/trunk:
java/org/apache/coyote/http11/ webapps/docs/
Author: markt
Date: Wed Dec 16 19:24:16 2015
New Revision: 1720418
URL: http://svn.apache.org/viewvc?rev=1720418&view=rev
Log:
Improve HTTP header validation.
This is a partial back-port of various refactorings and fixes that have been made in 7.0.x onwards.
Added:
tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/AbstractInputBuffer.java (with props)
Modified:
tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalAprInputBuffer.java
tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalInputBuffer.java
tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalNioInputBuffer.java
tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml
Added: tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/AbstractInputBuffer.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/AbstractInputBuffer.java?rev=1720418&view=auto
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/AbstractInputBuffer.java (added)
+++ tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/AbstractInputBuffer.java Wed Dec 16 19:24:16 2015
@@ -0,0 +1,72 @@
+/*
+ * 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.coyote.http11;
+
+import org.apache.coyote.InputBuffer;
+
+public abstract class AbstractInputBuffer implements InputBuffer {
+
+ protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128];
+
+ 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;
+ }
+ }
+ }
+}
Propchange: tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/AbstractInputBuffer.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalAprInputBuffer.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalAprInputBuffer.java?rev=1720418&r1=1720417&r2=1720418&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalAprInputBuffer.java (original)
+++ tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalAprInputBuffer.java Wed Dec 16 19:24:16 2015
@@ -14,8 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.coyote.http11;
import java.io.IOException;
@@ -31,6 +29,8 @@ import org.apache.tomcat.util.http.MimeH
import org.apache.tomcat.util.res.StringManager;
import org.apache.coyote.InputBuffer;
import org.apache.coyote.Request;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
/**
* Implementation of InputBuffer which provides HTTP request header parsing as
@@ -38,11 +38,9 @@ import org.apache.coyote.Request;
*
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
*/
-public class InternalAprInputBuffer implements InputBuffer {
-
-
- // -------------------------------------------------------------- Constants
+public class InternalAprInputBuffer extends AbstractInputBuffer {
+ private static final Log log = LogFactory.getLog(InternalAprInputBuffer.class);
// ----------------------------------------------------------- Constructors
@@ -552,6 +550,7 @@ public class InternalAprInputBuffer impl
* @return false after reading a blank line (which indicates that the
* HTTP header parsing is done
*/
+ @SuppressWarnings("null") // headerValue cannot be null
public boolean parseHeader()
throws IOException {
@@ -570,11 +569,11 @@ public class InternalAprInputBuffer impl
chr = buf[pos];
- if ((chr == Constants.CR) || (chr == Constants.LF)) {
- if (chr == Constants.LF) {
- pos++;
- return false;
- }
+ if (chr == Constants.CR) {
+ // Skip
+ } else if (chr == Constants.LF) {
+ pos++;
+ return false;
} else {
break;
}
@@ -605,6 +604,11 @@ public class InternalAprInputBuffer impl
if (buf[pos] == Constants.COLON) {
colon = true;
headerValue = headers.addValue(buf, start, pos - start);
+ } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+ // If a non-token header is detected, skip the line and
+ // ignore the header
+ skipLine(start);
+ return true;
}
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
@@ -659,6 +663,7 @@ public class InternalAprInputBuffer impl
}
if (buf[pos] == Constants.CR) {
+ // Skip
} else if (buf[pos] == Constants.LF) {
eol = true;
} else if (buf[pos] == Constants.SP) {
@@ -706,6 +711,38 @@ public class InternalAprInputBuffer impl
}
+ private void skipLine(int start) throws IOException {
+ boolean eol = false;
+ int lastRealByte = start;
+ if (pos - 1 > start) {
+ lastRealByte = pos - 1;
+ }
+
+ while (!eol) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(sm.getString("iib.eof.error"));
+ }
+
+ if (buf[pos] == Constants.CR) {
+ // Skip
+ } else if (buf[pos] == Constants.LF) {
+ eol = true;
+ } else {
+ lastRealByte = pos;
+ }
+ pos++;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("iib.invalidheader", new String(buf, start,
+ lastRealByte - start + 1, "ISO-8859-1")));
+ }
+ }
+
+
/**
* Available bytes (note that due to encoding, this may not correspond )
*/
Modified: tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalInputBuffer.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalInputBuffer.java?rev=1720418&r1=1720417&r2=1720418&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalInputBuffer.java (original)
+++ tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalInputBuffer.java Wed Dec 16 19:24:16 2015
@@ -14,8 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.coyote.http11;
import java.io.IOException;
@@ -29,6 +27,8 @@ import org.apache.tomcat.util.res.String
import org.apache.coyote.InputBuffer;
import org.apache.coyote.Request;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
/**
* Implementation of InputBuffer which provides HTTP request header parsing as
@@ -36,9 +36,10 @@ import org.apache.coyote.Request;
*
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
*/
-public class InternalInputBuffer implements InputBuffer {
-
+public class InternalInputBuffer extends AbstractInputBuffer {
+ private static final Log log = LogFactory.getLog(InternalInputBuffer.class);
+
// -------------------------------------------------------------- Constants
@@ -539,6 +540,7 @@ public class InternalInputBuffer impleme
* @return false after reading a blank line (which indicates that the
* HTTP header parsing is done
*/
+ @SuppressWarnings("null") // headerValue cannot be null
public boolean parseHeader()
throws IOException {
@@ -557,11 +559,11 @@ public class InternalInputBuffer impleme
chr = buf[pos];
- if ((chr == Constants.CR) || (chr == Constants.LF)) {
- if (chr == Constants.LF) {
- pos++;
- return false;
- }
+ if (chr == Constants.CR) {
+ // Skip
+ } else if (chr == Constants.LF) {
+ pos++;
+ return false;
} else {
break;
}
@@ -592,7 +594,13 @@ public class InternalInputBuffer impleme
if (buf[pos] == Constants.COLON) {
colon = true;
headerValue = headers.addValue(buf, start, pos - start);
+ } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+ // If a non-token header is detected, skip the line and
+ // ignore the header
+ skipLine(start);
+ return true;
}
+
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
buf[pos] = (byte) (chr - Constants.LC_OFFSET);
@@ -646,6 +654,7 @@ public class InternalInputBuffer impleme
}
if (buf[pos] == Constants.CR) {
+ // Skip
} else if (buf[pos] == Constants.LF) {
eol = true;
} else if (buf[pos] == Constants.SP) {
@@ -712,7 +721,38 @@ public class InternalInputBuffer impleme
// ------------------------------------------------------ Protected Methods
+ private void skipLine(int start) throws IOException {
+ boolean eol = false;
+ int lastRealByte = start;
+ if (pos - 1 > start) {
+ lastRealByte = pos - 1;
+ }
+
+ while (!eol) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill())
+ throw new EOFException(sm.getString("iib.eof.error"));
+ }
+
+ if (buf[pos] == Constants.CR) {
+ // Skip
+ } else if (buf[pos] == Constants.LF) {
+ eol = true;
+ } else {
+ lastRealByte = pos;
+ }
+ pos++;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("iib.invalidheader", new String(buf, start,
+ lastRealByte - start + 1, "ISO-8859-1")));
+ }
+ }
+
/**
* Fill the internal buffer using data from the undelying input stream.
*
Modified: tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalNioInputBuffer.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalNioInputBuffer.java?rev=1720418&r1=1720417&r2=1720418&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalNioInputBuffer.java (original)
+++ tomcat/tc6.0.x/trunk/java/org/apache/coyote/http11/InternalNioInputBuffer.java Wed Dec 16 19:24:16 2015
@@ -14,8 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.coyote.http11;
import java.io.EOFException;
@@ -39,18 +37,56 @@ import org.apache.tomcat.util.net.NioEnd
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
* @author Filip Hanik
*/
-public class InternalNioInputBuffer implements InputBuffer {
+public class InternalNioInputBuffer extends AbstractInputBuffer {
- /**
- * Logger.
- */
private static final org.apache.juli.logging.Log log =
org.apache.juli.logging.LogFactory.getLog(InternalNioInputBuffer.class);
// -------------------------------------------------------------- Constants
- enum HeaderParseStatus {DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA}
- enum HeaderParsePosition {HEADER_START, HEADER_NAME, HEADER_VALUE, HEADER_MULTI_LINE}
+ enum HeaderParseStatus {
+ DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA
+ }
+
+ enum HeaderParsePosition {
+ /**
+ * Start of a new header. A CRLF here means that there are no more
+ * headers. Any other character starts a header name.
+ */
+ HEADER_START,
+ /**
+ * Reading a header name. All characters of header are HTTP_TOKEN_CHAR.
+ * Header name is followed by ':'. No whitespace is allowed.<br />
+ * Any non-HTTP_TOKEN_CHAR (this includes any whitespace) encountered
+ * before ':' will result in the whole line being ignored.
+ */
+ HEADER_NAME,
+ /**
+ * Skipping whitespace before text of header value starts, either on the
+ * first line of header value (just after ':') or on subsequent lines
+ * when it is known that subsequent line starts with SP or HT.
+ */
+ HEADER_VALUE_START,
+ /**
+ * Reading the header value. We are inside the value. Either on the
+ * first line or on any subsequent line. We come into this state from
+ * HEADER_VALUE_START after the first non-SP/non-HT byte is encountered
+ * on the line.
+ */
+ HEADER_VALUE,
+ /**
+ * Before reading a new line of a header. Once the next byte is peeked,
+ * the state changes without advancing our position. The state becomes
+ * either HEADER_VALUE_START (if that first byte is SP or HT), or
+ * HEADER_START (otherwise).
+ */
+ HEADER_MULTI_LINE,
+ /**
+ * Reading all bytes until the next CRLF. The line is being ignored.
+ */
+ HEADER_SKIPLINE
+ }
+
// ----------------------------------------------------------- Constructors
@@ -190,7 +226,8 @@ public class InternalNioInputBuffer impl
/**
- * Maximum allowed size of the HTTP request line plus headers.
+ * Maximum allowed size of the HTTP request line plus headers plus any
+ * leading blank lines.
*/
private final int headerBufferSize;
@@ -420,7 +457,7 @@ public class InternalNioInputBuffer impl
* using it.
*
* @throws IOException If an exception occurs during the underlying socket
- * read operations, or if the given buffer is not big enough to accomodate
+ * read operations, or if the given buffer is not big enough to accommodate
* the whole line.
* @return true if data is properly fed; false if no data is available
* immediately and thread should be freed
@@ -716,11 +753,11 @@ public class InternalNioInputBuffer impl
chr = buf[pos];
- if ((chr == Constants.CR) || (chr == Constants.LF)) {
- if (chr == Constants.LF) {
- pos++;
- return HeaderParseStatus.DONE;
- }
+ if (chr == Constants.CR) {
+ // Skip
+ } else if (chr == Constants.LF) {
+ pos++;
+ return HeaderParseStatus.DONE;
} else {
break;
}
@@ -740,8 +777,6 @@ public class InternalNioInputBuffer impl
// Header name is always US-ASCII
//
-
-
while (headerParsePos == HeaderParsePosition.HEADER_NAME) {
// Read new bytes if needed
@@ -751,58 +786,67 @@ public class InternalNioInputBuffer impl
}
}
- if (buf[pos] == Constants.COLON) {
- headerParsePos = HeaderParsePosition.HEADER_VALUE;
+ chr = buf[pos];
+ if (chr == Constants.COLON) {
+ headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
headerData.headerValue = headers.addValue(buf, headerData.start, pos - headerData.start);
+ pos++;
+ // Mark the current buffer position
+ headerData.start = pos;
+ headerData.realPos = pos;
+ headerData.lastSignificantChar = pos;
+ break;
+ } else if (!HTTP_TOKEN_CHAR[chr]) {
+ // If a non-token header is detected, skip the line and
+ // ignore the header
+ headerData.lastSignificantChar = pos;
+ return skipLine();
}
- chr = buf[pos];
+
+ // chr is next byte of header name. Convert to lowercase.
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
buf[pos] = (byte) (chr - Constants.LC_OFFSET);
}
-
pos++;
- if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) {
- // Mark the current buffer position
- headerData.start = pos;
- headerData.realPos = pos;
- }
}
-
+ // Skip the line and ignore the header
+ if (headerParsePos == HeaderParsePosition.HEADER_SKIPLINE) {
+ return skipLine();
+ }
+
//
// Reading the header value (which can be spanned over multiple lines)
//
- boolean eol = false;
-
- while (headerParsePos == HeaderParsePosition.HEADER_VALUE ||
+ while (headerParsePos == HeaderParsePosition.HEADER_VALUE_START ||
+ headerParsePos == HeaderParsePosition.HEADER_VALUE ||
headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
- if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) {
-
- boolean space = true;
+ if ( headerParsePos == HeaderParsePosition.HEADER_VALUE_START ) {
// Skipping spaces
- while (space) {
-
+ while (true) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill(true,false)) {//parse header
- //HEADER_VALUE, should already be set
+ //HEADER_VALUE_START
return HeaderParseStatus.NEED_MORE_DATA;
}
}
- if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+ chr = buf[pos];
+ if (chr == Constants.SP || chr == Constants.HT) {
pos++;
} else {
- space = false;
+ headerParsePos = HeaderParsePosition.HEADER_VALUE;
+ break;
}
-
}
-
- headerData.lastSignificantChar = headerData.realPos;
+ }
+ if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) {
// Reading bytes until the end of the line
+ boolean eol = false;
while (!eol) {
// Read new bytes if needed
@@ -811,25 +855,26 @@ public class InternalNioInputBuffer impl
//HEADER_VALUE
return HeaderParseStatus.NEED_MORE_DATA;
}
-
}
- if (buf[pos] == Constants.CR) {
- } else if (buf[pos] == Constants.LF) {
+ chr = buf[pos];
+ if (chr == Constants.CR) {
+ // Skip
+ } else if (chr == Constants.LF) {
eol = true;
- } else if (buf[pos] == Constants.SP) {
- buf[headerData.realPos] = buf[pos];
+ } else if (chr == Constants.SP || chr == Constants.HT) {
+ buf[headerData.realPos] = chr;
headerData.realPos++;
} else {
- buf[headerData.realPos] = buf[pos];
+ buf[headerData.realPos] = chr;
headerData.realPos++;
headerData.lastSignificantChar = headerData.realPos;
}
pos++;
-
}
+ // Ignore whitespaces at the end of the line
headerData.realPos = headerData.lastSignificantChar;
// Checking the first character of the new line. If the character
@@ -849,27 +894,86 @@ public class InternalNioInputBuffer impl
if ( headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE ) {
if ( (chr != Constants.SP) && (chr != Constants.HT)) {
headerParsePos = HeaderParsePosition.HEADER_START;
+ break;
} else {
- eol = false;
// Copying one extra space in the buffer (since there must
// be at least one space inserted between the lines)
buf[headerData.realPos] = chr;
headerData.realPos++;
- headerParsePos = HeaderParsePosition.HEADER_VALUE;
+ headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
}
}
}
// Set the header value
- headerData.headerValue.setBytes(buf, headerData.start, headerData.realPos - headerData.start);
+ headerData.headerValue.setBytes(buf, headerData.start,
+ headerData.lastSignificantChar - headerData.start);
headerData.recycle();
return HeaderParseStatus.HAVE_MORE_HEADERS;
}
- protected HeaderParseData headerData = new HeaderParseData();
+ private HeaderParseStatus skipLine() throws IOException {
+ headerParsePos = HeaderParsePosition.HEADER_SKIPLINE;
+ boolean eol = false;
+
+ // Reading bytes until the end of the line
+ while (!eol) {
+
+ // Read new bytes if needed
+ if (pos >= lastValid) {
+ if (!fill(true,false)) {
+ return HeaderParseStatus.NEED_MORE_DATA;
+ }
+ }
+
+ if (buf[pos] == Constants.CR) {
+ // Skip
+ } else if (buf[pos] == Constants.LF) {
+ eol = true;
+ } else {
+ headerData.lastSignificantChar = pos;
+ }
+
+ pos++;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("iib.invalidheader", new String(buf,
+ headerData.start,
+ headerData.lastSignificantChar - headerData.start + 1,
+ "ISO-8859-1")));
+ }
+
+ headerParsePos = HeaderParsePosition.HEADER_START;
+ return HeaderParseStatus.HAVE_MORE_HEADERS;
+ }
+
+ private HeaderParseData headerData = new HeaderParseData();
public static class HeaderParseData {
+ /**
+ * 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 ':'.
+ */
int start = 0;
+ /**
+ * When parsing header name: not used (stays as 0).<br />
+ * When skipping broken header line: not used (stays as 0).<br />
+ * When parsing header value: starts as the first character after ':'.
+ * Then is increased as far as more bytes of the header are harvested.
+ * Bytes from buf[pos] are copied to buf[realPos]. Thus the string from
+ * [start] to [realPos-1] is the prepared value of the header, with
+ * whitespaces removed as needed.<br />
+ */
int realPos = 0;
+ /**
+ * When parsing header name: not used (stays as 0).<br />
+ * When skipping broken header line: last non-CR/non-LF character.<br />
+ * When parsing header value: position after the last not-LWS character.<br />
+ */
int lastSignificantChar = 0;
+ /**
+ * MB that will store the value of the header. It is null while parsing
+ * header name and is created after the name has been parsed.
+ */
MessageBytes headerValue = null;
public void recycle() {
start = 0;
@@ -968,11 +1072,6 @@ public class InternalNioInputBuffer impl
pos = lastValid;
return (length);
-
}
-
-
}
-
-
}
Modified: tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml?rev=1720418&r1=1720417&r2=1720418&view=diff
==============================================================================
--- tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/tc6.0.x/trunk/webapps/docs/changelog.xml Wed Dec 16 19:24:16 2015
@@ -116,6 +116,9 @@
<bug>57943</bug>: Prevent the same socket being added to the cache
twice. Patch based on analysis by Ian Luo / Sun Qi. (markt/kkolinko)
</fix>
+ <fix>
+ Improve HTTP header validation. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Web applications">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org