This is an automated email from the ASF dual-hosted git repository. remm pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push: new 7943c54211 Improve CRLF skipping for the available method 7943c54211 is described below commit 7943c54211e96b31b93fbbd8ebb94f31961a3b10 Author: remm <r...@apache.org> AuthorDate: Thu Jan 30 11:51:40 2025 +0100 Improve CRLF skipping for the available method The bytes will now be consumed instead. Also add processing for the chunk header since those are not actual data. Likely related to BZ69545 since the supposedly offending commit was causing a testsuite failure following the TestChunkedInputFilter update. --- .../coyote/http11/filters/ChunkedInputFilter.java | 121 ++++++++++++++++++++- webapps/docs/changelog.xml | 4 + 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java b/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java index debdf57ed8..4621ab82e4 100644 --- a/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java +++ b/java/org/apache/coyote/http11/filters/ChunkedInputFilter.java @@ -199,13 +199,32 @@ public class ChunkedInputFilter implements InputFilter, ApplicationBufferHandler available = readChunk.remaining(); } - // Handle some edge cases + if (available > 2 && (parseState == ParseState.CHUNK_BODY_CRLF || parseState == ParseState.CHUNK_HEADER)) { + if (parseState == ParseState.CHUNK_BODY_CRLF) { + skipCRLF(); + } + if (parseState == ParseState.CHUNK_HEADER) { + skipChunkHeader(); + } + available = readChunk.remaining(); + // If ending as TRAILER_FIELDS, then the next read will be EOF and available can be > 0 + // If ending as CHUNK_HEADER then there's nothing left to read for now + // If ending as CHUNK_BODY_CRLF, then if there's more than two left, there will be data to read or leading to EOF + } if (available == 1 && parseState == ParseState.CHUNK_BODY_CRLF) { // Either just the CR or just the LF are left in the buffer. There is no data to read. - available = 0; + if (!skipCRLF()) { + available = readChunk.remaining(); + } else { + available = 0; + } } else if (available == 2 && !crFound && parseState == ParseState.CHUNK_BODY_CRLF) { // Just CRLF is left in the buffer. There is no data to read. - available = 0; + if (!skipCRLF()) { + available = readChunk.remaining(); + } else { + available = 0; + } } if (available == 0) { @@ -391,6 +410,69 @@ public class ChunkedInputFilter implements InputFilter, ApplicationBufferHandler } + private boolean skipChunkHeader() { + + boolean eol = false; + + while (!eol) { + if (readChunk == null || readChunk.position() >= readChunk.limit()) { + return false; + } + + byte chr = readChunk.get(readChunk.position()); + if (chr == Constants.CR || chr == Constants.LF) { + parsingExtension = false; + if (!skipCRLF()) { + return false; + } + eol = true; + } else if (chr == Constants.SEMI_COLON && !parsingExtension) { + // First semi-colon marks the start of the extension. Further + // semi-colons may appear to separate multiple chunk-extensions. + // These need to be processed as part of parsing the extensions. + parsingExtension = true; + extensionSize++; + } else if (!parsingExtension) { + int charValue = HexUtils.getDec(chr); + if (charValue != -1 && chunkSizeDigitsRead < 8) { + chunkSizeDigitsRead++; + remaining = (remaining << 4) | charValue; + } else { + // Isn't valid hex so this is an error condition + return false; + } + } else { + // Extension 'parsing' + // Note that the chunk-extension is neither parsed nor + // validated. Currently it is simply ignored. + extensionSize++; + if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) { + return false; + } + } + + // Parsing the CRLF increments pos + if (!eol) { + readChunk.position(readChunk.position() + 1); + } + } + + if (chunkSizeDigitsRead == 0 || remaining < 0) { + return false; + } else { + chunkSizeDigitsRead = 0; + } + + if (remaining == 0) { + parseState = ParseState.TRAILER_FIELDS; + } else { + parseState = ParseState.CHUNK_BODY; + } + + return true; + } + + private int parseChunkBody(ApplicationBufferHandler handler) throws IOException { int result = 0; @@ -461,6 +543,39 @@ public class ChunkedInputFilter implements InputFilter, ApplicationBufferHandler } + private boolean skipCRLF() { + + boolean eol = false; + + while (!eol) { + if (readChunk == null || readChunk.position() >= readChunk.limit()) { + return false; + } + + byte chr = readChunk.get(readChunk.position()); + if (chr == Constants.CR) { + if (crFound) { + return false; + } + crFound = true; + } else if (chr == Constants.LF) { + if (!crFound) { + return false; + } + eol = true; + } else { + return false; + } + + readChunk.position(readChunk.position() + 1); + } + + crFound = false; + parseState = ParseState.CHUNK_HEADER; + return true; + } + + /** * Parse end chunk data. * diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 6cefa4444f..1b2a09b0bc 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -262,6 +262,10 @@ <strong>allowBackslash</strong> attributes determines how the decoded URI is processed. (markt) </add> + <fix> + <bug>69545</bug>: Improve CRLF skipping for the <code>available</code> + method of the <code>ChunkedInputFilter</code>. (remm) + </fix> </changelog> </subsection> <subsection name="Jasper"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org