Do not preallocate memory in HttpDecoder for frame payload.
DATA, HEADERS, and unknown frame payload fragments are passed immediately to
Visitor without buffering. CANCEL_PUSH and PUSH_PROMISE frames are not
processed. GOAWAY and MAX_PUSH_ID frames have small payloads. Only SETTINGS,
PRIORITY_UPDATE, and ACCEPT_CH frames may be buffered by HttpDecoder and are
allowed to have large payloads. In most cases these frames are small and the
payload is sent in a single fragment, in which case HttpDecoder parses it
directly from the input without buffering. Buffering only happens in the rest
of the cases, which clearly is not a hot path. This CL changes HttpDecoder not
to preallocate memory for the payload buffer in these cases, and let std::string
deal with growing internally.
Also introduce kPayloadLengthLimit constant, and do not call MaxFrameLength()
for frames that are not buffered.
PiperOrigin-RevId: 399421301
diff --git a/quic/core/http/http_decoder.cc b/quic/core/http/http_decoder.cc
index de86955..2265b77 100644
--- a/quic/core/http/http_decoder.cc
+++ b/quic/core/http/http_decoder.cc
@@ -5,7 +5,6 @@
#include "quic/core/http/http_decoder.h"
#include <cstdint>
-#include <limits>
#include "absl/base/attributes.h"
#include "absl/strings/string_view.h"
@@ -21,6 +20,17 @@
namespace quic {
+namespace {
+
+// Limit on the payload length for frames that are buffered by HttpDecoder.
+// If a frame header indicating a payload length exceeding this limit is
+// received, HttpDecoder closes the connection. Does not apply to frames that
+// are not buffered here but each payload fragment is immediately passed to
+// Visitor, like HEADERS, DATA, and unknown frames.
+constexpr QuicByteCount kPayloadLengthLimit = 1024 * 1024;
+
+} // anonymous namespace
+
HttpDecoder::HttpDecoder(Visitor* visitor) : HttpDecoder(visitor, Options()) {}
HttpDecoder::HttpDecoder(Visitor* visitor, Options options)
: visitor_(visitor),
@@ -227,11 +237,8 @@
return false;
}
- if (current_frame_length_ > MaxFrameLength(current_frame_type_)) {
- // MaxFrameLength() returns numeric_limits::max()
- // if IsFrameBuffered() is false.
- QUICHE_DCHECK(IsFrameBuffered());
-
+ if (IsFrameBuffered() &&
+ current_frame_length_ > MaxFrameLength(current_frame_type_)) {
RaiseError(QUIC_HTTP_FRAME_TOO_LARGE, "Frame is too large.");
return false;
}
@@ -470,10 +477,6 @@
continue_processing = ParseEntirePayload(¤t_payload_reader);
reader->Seek(current_frame_length_);
} else {
- if (buffer_.empty()) {
- buffer_.reserve(current_frame_length_);
- }
-
// Buffer as much of the payload as |*reader| contains.
QuicByteCount bytes_to_read = std::min<QuicByteCount>(
remaining_frame_length_, reader->BytesRemaining());
@@ -654,23 +657,22 @@
}
QuicByteCount HttpDecoder::MaxFrameLength(uint64_t frame_type) {
+ QUICHE_DCHECK(IsFrameBuffered());
+
switch (frame_type) {
case static_cast<uint64_t>(HttpFrameType::SETTINGS):
- // This limit is arbitrary.
- return 1024 * 1024;
+ return kPayloadLengthLimit;
case static_cast<uint64_t>(HttpFrameType::GOAWAY):
return VARIABLE_LENGTH_INTEGER_LENGTH_8;
case static_cast<uint64_t>(HttpFrameType::MAX_PUSH_ID):
return VARIABLE_LENGTH_INTEGER_LENGTH_8;
case static_cast<uint64_t>(HttpFrameType::PRIORITY_UPDATE_REQUEST_STREAM):
- // This limit is arbitrary.
- return 1024 * 1024;
+ return kPayloadLengthLimit;
case static_cast<uint64_t>(HttpFrameType::ACCEPT_CH):
- // This limit is arbitrary.
- return 1024 * 1024;
+ return kPayloadLengthLimit;
default:
- // Other frames require no data buffering, so it's safe to have no limit.
- return std::numeric_limits<QuicByteCount>::max();
+ QUICHE_NOTREACHED();
+ return 0;
}
}