Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ChunkedEncoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ namespace uWS {
if (chunkSize(state) == 0) {
// Start of line: \r means final empty line, else it's a trailer header
if (c == '\r') state = (state & ~STATE_SIZE_MASK) | 1;
else if (c == '\n') { state = STATE_IS_ERROR; return std::nullopt; }
else state = (state & ~STATE_SIZE_MASK) | 2;
} else if (chunkSize(state) == 1) {
// Expecting \n of final empty line
Expand All @@ -215,6 +216,7 @@ namespace uWS {
} else if (chunkSize(state) == 2) {
// Inside trailer header, wait for \r
if (c == '\r') state = (state & ~STATE_SIZE_MASK) | 3;
else if (c == '\n') { state = STATE_IS_ERROR; return std::nullopt; }
} else if (chunkSize(state) == 3) {
// Expecting \n to terminate the header line
if (c == '\n') state = (state & ~STATE_SIZE_MASK) | 0;
Expand Down
6 changes: 3 additions & 3 deletions src/HttpParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ struct HttpParser {
if (!CONSUME_MINIMALLY) {
/* Go ahead and parse it (todo: better heuristics for emitting FIN to the app level) */
std::string_view dataToConsume(data, length);
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes)) {
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes, true)) {
dataHandler(user, chunk, chunk.length() ? UINT64_MAX : 0);
}
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
Expand Down Expand Up @@ -672,7 +672,7 @@ struct HttpParser {
/* It's either chunked or with a content-length */
if (isParsingChunkedEncoding(remainingStreamingBytes)) {
std::string_view dataToConsume(data, length);
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes)) {
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes, true)) {
/* If we got the zero size chunk, maxRemainingBodyLength is 0, else it is practically infinity */
dataHandler(user, chunk, chunk.length() ? UINT64_MAX : 0);
}
Expand Down Expand Up @@ -730,7 +730,7 @@ struct HttpParser {
/* It's either chunked or with a content-length */
if (isParsingChunkedEncoding(remainingStreamingBytes)) {
std::string_view dataToConsume(data, length);
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes)) {
for (auto chunk : uWS::ChunkIterator(&dataToConsume, &remainingStreamingBytes, true)) {
dataHandler(user, chunk, chunk.length() ? UINT64_MAX : 0);
}
if (isParsingInvalidChunkedEncoding(remainingStreamingBytes)) {
Expand Down
35 changes: 35 additions & 0 deletions tests/HttpParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,41 @@ int main() {

std::cout << "HTTP DONE" << std::endl;

/* RFC 9112 7.1.2: trailer fields after the last chunk are skipped and the next request still parses.
* A bare LF there is a 400, else the next request would be read as a trailer field */
struct {
const char *trailer;
bool accept;
} trailerCases[] = {
{"X-Checksum: abc\r\n\r\n", true},
{"\n", false},
{"X-Checksum: abc\n\r\n", false},
};

for (auto &c : trailerCases) {
std::string req = std::string("POST / HTTP/1.1\r\nHost: 127.0.0.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n") + c.trailer
+ "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n";
unsigned int length = (unsigned int) req.size();
req.append(32, 'E');

int requests = 0;
void *trailerUser = (void *) 13;
uWS::HttpParser trailerParser;
auto [trailerErr, trailerReturned] = trailerParser.consumePostPadded(req.data(), length, trailerUser, nullptr, [&requests](void *s, uWS::HttpRequest *) -> void * {
requests++;
return s;
}, [](void *s, std::string_view, bool) -> void * {
return s;
});

bool ok = c.accept ? (trailerReturned == trailerUser && requests == 2) : trailerReturned != trailerUser;
if (!ok) {
std::cerr << "Trailer \"" << c.trailer << "\" expected " << (c.accept ? "accept" : "400")
<< ", got err=" << trailerErr << " requests=" << requests << std::endl;
return 1;
}
}

/* Issue 1941: accept Transfer-Encoding when the final coding is chunked (case-insensitive). */
struct {
const char *te;
Expand Down