Loading src/hpack.cpp +9 −2 Changes for src/hpack.cpp: 9 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -355,11 +355,18 @@ std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) { off += str_bytes; headers.push_back({name, value}); } else if ((byte & 0xE0) == 0x20) { // Dynamic table size update (section 6.3) // Dynamic table size update (section 6.3). Clamp to the size we // actually advertise (we never send our own // SETTINGS_HEADER_TABLE_SIZE, so the RFC 7541 §6.5.2 default of // 4096 applies) -- otherwise a peer could grow this decoder's // dynamic table to gigabytes via this 5-bit-prefixed integer // (which itself can encode values up to ~4 billion) and then // keep it pinned there with incremental-indexing literals for // the lifetime of the connection. size_t int_bytes = 0; uint32_t newMax = decodeInt(data + off, len - off, 5, int_bytes); off += int_bytes; _dynTableMaxSize = newMax; _dynTableMaxSize = std::min<uint32_t>(newMax, kMaxDynTableSize); evict(0); // evict entries that exceed new max } else { ++off; // skip unknown Loading src/hpack.h +6 −0 Changes for src/hpack.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -98,6 +98,12 @@ private: size_t _dynTableSize = 0; // current size in octets size_t _dynTableMaxSize = 4096; // default per RFC 7541 §6.5.2 // Ceiling a peer's Dynamic Table Size Update (section 6.3) may raise // _dynTableMaxSize to. We never send our own SETTINGS_HEADER_TABLE_SIZE, // so the RFC 7541 §6.5.2 default of 4096 is what we've implicitly agreed // to; without this clamp a peer's update is taken as-is (up to ~4 billion). static constexpr uint32_t kMaxDynTableSize = 4096; // Look up an index across static + dynamic tables bool lookupIndex(uint32_t idx, std::string &name, std::string &value) const; bool lookupIndexName(uint32_t idx, std::string &name) const; Loading src/http.cpp +88 −40 Changes for src/http.cpp: 88 added lines, 40 removed lines. Original line number Diff line number Diff line Loading @@ -1127,10 +1127,30 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest } break; } case H2C_FRAME_GOAWAY: _streamH2EndStream = true; got_headers = true; case H2C_FRAME_GOAWAY: { // Reaching this means got_headers is still false (the // while-loop condition) -- our stream's HEADERS never // arrived. Previously this set got_headers=true anyway, // returning an empty/default HttpResponse as if the // request had succeeded. Only treat it as a hard // failure when the peer explicitly never processed // this stream; otherwise keep reading. uint32_t last_stream_id = 0; if (frame_len >= 4) { last_stream_id = ((static_cast<uint32_t>(payload[0]) & 0x7f) << 24) | (static_cast<uint32_t>(payload[1]) << 16) | (static_cast<uint32_t>(payload[2]) << 8) | static_cast<uint32_t>(payload[3]); } if (stream_id > last_stream_id) { HTTPException ee; ee[HTTPException::Error] << "HTTP/2 GetStream: GOAWAY before stream " << stream_id << " was processed (last_stream_id=" << last_stream_id << ")"; throw ee; } break; } default: break; } Loading Loading @@ -2621,6 +2641,7 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( // 4) Read response frames bool got_end_stream = false; bool got_headers = false; // Use connection-level decoder to maintain HPACK dynamic table state if (!_h2Decoder) _h2Decoder = std::make_unique<hpack::Decoder>(); Loading Loading @@ -2684,17 +2705,9 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( case H2C_FRAME_HEADERS: { if (frame_stream == stream_id) { auto headers = decoder.decode(payload, frame_len); // Build synthetic HTTP/1.1 response for HttpResponse::parse() std::string status_val = "200"; for (const auto &h : headers) if (h.name == ":status") status_val = h.value; std::string synth = "HTTP/1.1 " + status_val + "\r\n"; for (const auto &h : headers) if (!h.name.empty() && h.name[0] != ':') synth += h.name + ": " + h.value + "\r\n"; synth += "\r\n"; got_headers = true; _lastResponse = std::make_unique<HttpResponse>(); _lastResponse->parse(synth.c_str(), synth.size()); _lastResponse->parseH2(headers); _lastStatusCode = _lastResponse->getStatusCode(); _lastContentType = _lastResponse->getContentType(); _lastLocation.clear(); Loading Loading @@ -2737,7 +2750,30 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( } case H2C_FRAME_GOAWAY: { got_end_stream = true; // RFC 7540 §6.8: 4-byte Last-Stream-ID (top bit reserved) // followed by a 4-byte error code. Reaching this case means // got_end_stream is still false (the while-loop condition), // i.e. our stream's response is not yet complete. Previously // this unconditionally set got_end_stream=true, silently // returning an empty/partial response as if it had // succeeded. If the peer explicitly never processed our // stream, fail loudly instead; otherwise keep reading -- // it may still deliver the remaining frames before closing // (bounded by the deadline/EOF checks around this loop). uint32_t last_stream_id = 0; if (frame_len >= 4) { last_stream_id = ((static_cast<uint32_t>(payload[0]) & 0x7f) << 24) | (static_cast<uint32_t>(payload[1]) << 16) | (static_cast<uint32_t>(payload[2]) << 8) | static_cast<uint32_t>(payload[3]); } if (stream_id > last_stream_id) { HTTPException ee; ee[HTTPException::Error] << "HTTP/2: GOAWAY before stream " << stream_id << " was processed (last_stream_id=" << last_stream_id << ")"; throw ee; } break; } Loading Loading @@ -2929,17 +2965,8 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( pos = frame_start; break; } else if (frame_type == 0x01) { auto decoded = qpack::Decoder::decode(raw.data() + pos, frame_len); // Build synthetic HTTP/1.1 response for HttpResponse::parse() std::string status_val = "200"; for (const auto &h : decoded) if (h.name == ":status") status_val = h.value; std::string synth = "HTTP/1.1 " + status_val + "\r\n"; for (const auto &h : decoded) if (!h.name.empty() && h.name[0] != ':') synth += h.name + ": " + h.value + "\r\n"; synth += "\r\n"; _lastResponse = std::make_unique<HttpResponse>(); _lastResponse->parse(synth.c_str(), synth.size()); _lastResponse->parseH3(decoded); _lastStatusCode = _lastResponse->getStatusCode(); status_code = _lastStatusCode; _lastContentType = _lastResponse->getContentType(); Loading Loading @@ -3915,15 +3942,12 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) { return bytes_consumed; } // Shared by parseH2/parseH3 below: HPACK/QPACK already split each field into // a clean name/value pair, so ingestion here is a straight copy into the // header list -- no CRLF/colon scanning, and (unlike the old synthetic-text // approach) no way for a value to be misinterpreted as introducing new // header lines. template <typename HeaderFieldT> static size_t ingestDecodedHeaders(libhttppp::HttpResponse &resp, const std::vector<HeaderFieldT> &headers, const std::string &httpVersion) { // parseH2/parseH3: HPACK/QPACK already split each field into a clean // name/value pair, so ingestion here is a straight copy into the header // list -- no CRLF/colon scanning, and (unlike the old synthetic-text // approach this replaces) no way for a value to be misinterpreted as // introducing new header lines. size_t libhttppp::HttpResponse::parseH2(const std::vector<hpack::HeaderField> &headers) { int statusCode = 0; for (const auto &h : headers) { if (h.name == ":status") { Loading @@ -3931,26 +3955,50 @@ static size_t ingestDecodedHeaders(libhttppp::HttpResponse &resp, break; } } resp.setResponseLine(httpVersion, statusCode); _Version = "HTTP/2"; _StatusCode = statusCode; _State.clear(); for (const auto &h : headers) { if (h.name.empty() || h.name[0] == ':') continue; std::string key = h.name; for (char &c : key) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); resp.setHeaderData(key)->push_back(h.value); setHeaderData(key)->push_back(h.value); } resp.refreshCachedHeaders(); _ContentLength = getHeaderData("content-length"); _Connection = getHeaderData("connection"); _ContentType = getHeaderData("content-type"); _TransferEncoding = getHeaderData("transfer-encoding"); return headers.size(); } size_t libhttppp::HttpResponse::parseH2(const std::vector<hpack::HeaderField> &headers) { return ingestDecodedHeaders(*this, headers, "HTTP/2"); size_t libhttppp::HttpResponse::parseH3(const std::vector<qpack::HeaderField> &headers) { int statusCode = 0; for (const auto &h : headers) { if (h.name == ":status") { try { statusCode = std::stoi(h.value); } catch (...) { statusCode = 0; } break; } } _Version = "HTTP/3"; _StatusCode = statusCode; _State.clear(); size_t libhttppp::HttpResponse::parseH3(const std::vector<qpack::HeaderField> &headers) { return ingestDecodedHeaders(*this, headers, "HTTP/3"); for (const auto &h : headers) { if (h.name.empty() || h.name[0] == ':') continue; std::string key = h.name; for (char &c : key) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); setHeaderData(key)->push_back(h.value); } _ContentLength = getHeaderData("content-length"); _Connection = getHeaderData("connection"); _ContentType = getHeaderData("content-type"); _TransferEncoding = getHeaderData("transfer-encoding"); return headers.size(); } void libhttppp::HttpResponse::setTransferEncoding(const std ::string &enc){ Loading Loading
src/hpack.cpp +9 −2 Changes for src/hpack.cpp: 9 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -355,11 +355,18 @@ std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) { off += str_bytes; headers.push_back({name, value}); } else if ((byte & 0xE0) == 0x20) { // Dynamic table size update (section 6.3) // Dynamic table size update (section 6.3). Clamp to the size we // actually advertise (we never send our own // SETTINGS_HEADER_TABLE_SIZE, so the RFC 7541 §6.5.2 default of // 4096 applies) -- otherwise a peer could grow this decoder's // dynamic table to gigabytes via this 5-bit-prefixed integer // (which itself can encode values up to ~4 billion) and then // keep it pinned there with incremental-indexing literals for // the lifetime of the connection. size_t int_bytes = 0; uint32_t newMax = decodeInt(data + off, len - off, 5, int_bytes); off += int_bytes; _dynTableMaxSize = newMax; _dynTableMaxSize = std::min<uint32_t>(newMax, kMaxDynTableSize); evict(0); // evict entries that exceed new max } else { ++off; // skip unknown Loading
src/hpack.h +6 −0 Changes for src/hpack.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -98,6 +98,12 @@ private: size_t _dynTableSize = 0; // current size in octets size_t _dynTableMaxSize = 4096; // default per RFC 7541 §6.5.2 // Ceiling a peer's Dynamic Table Size Update (section 6.3) may raise // _dynTableMaxSize to. We never send our own SETTINGS_HEADER_TABLE_SIZE, // so the RFC 7541 §6.5.2 default of 4096 is what we've implicitly agreed // to; without this clamp a peer's update is taken as-is (up to ~4 billion). static constexpr uint32_t kMaxDynTableSize = 4096; // Look up an index across static + dynamic tables bool lookupIndex(uint32_t idx, std::string &name, std::string &value) const; bool lookupIndexName(uint32_t idx, std::string &name) const; Loading
src/http.cpp +88 −40 Changes for src/http.cpp: 88 added lines, 40 removed lines. Original line number Diff line number Diff line Loading @@ -1127,10 +1127,30 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest } break; } case H2C_FRAME_GOAWAY: _streamH2EndStream = true; got_headers = true; case H2C_FRAME_GOAWAY: { // Reaching this means got_headers is still false (the // while-loop condition) -- our stream's HEADERS never // arrived. Previously this set got_headers=true anyway, // returning an empty/default HttpResponse as if the // request had succeeded. Only treat it as a hard // failure when the peer explicitly never processed // this stream; otherwise keep reading. uint32_t last_stream_id = 0; if (frame_len >= 4) { last_stream_id = ((static_cast<uint32_t>(payload[0]) & 0x7f) << 24) | (static_cast<uint32_t>(payload[1]) << 16) | (static_cast<uint32_t>(payload[2]) << 8) | static_cast<uint32_t>(payload[3]); } if (stream_id > last_stream_id) { HTTPException ee; ee[HTTPException::Error] << "HTTP/2 GetStream: GOAWAY before stream " << stream_id << " was processed (last_stream_id=" << last_stream_id << ")"; throw ee; } break; } default: break; } Loading Loading @@ -2621,6 +2641,7 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( // 4) Read response frames bool got_end_stream = false; bool got_headers = false; // Use connection-level decoder to maintain HPACK dynamic table state if (!_h2Decoder) _h2Decoder = std::make_unique<hpack::Decoder>(); Loading Loading @@ -2684,17 +2705,9 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( case H2C_FRAME_HEADERS: { if (frame_stream == stream_id) { auto headers = decoder.decode(payload, frame_len); // Build synthetic HTTP/1.1 response for HttpResponse::parse() std::string status_val = "200"; for (const auto &h : headers) if (h.name == ":status") status_val = h.value; std::string synth = "HTTP/1.1 " + status_val + "\r\n"; for (const auto &h : headers) if (!h.name.empty() && h.name[0] != ':') synth += h.name + ": " + h.value + "\r\n"; synth += "\r\n"; got_headers = true; _lastResponse = std::make_unique<HttpResponse>(); _lastResponse->parse(synth.c_str(), synth.size()); _lastResponse->parseH2(headers); _lastStatusCode = _lastResponse->getStatusCode(); _lastContentType = _lastResponse->getContentType(); _lastLocation.clear(); Loading Loading @@ -2737,7 +2750,30 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( } case H2C_FRAME_GOAWAY: { got_end_stream = true; // RFC 7540 §6.8: 4-byte Last-Stream-ID (top bit reserved) // followed by a 4-byte error code. Reaching this case means // got_end_stream is still false (the while-loop condition), // i.e. our stream's response is not yet complete. Previously // this unconditionally set got_end_stream=true, silently // returning an empty/partial response as if it had // succeeded. If the peer explicitly never processed our // stream, fail loudly instead; otherwise keep reading -- // it may still deliver the remaining frames before closing // (bounded by the deadline/EOF checks around this loop). uint32_t last_stream_id = 0; if (frame_len >= 4) { last_stream_id = ((static_cast<uint32_t>(payload[0]) & 0x7f) << 24) | (static_cast<uint32_t>(payload[1]) << 16) | (static_cast<uint32_t>(payload[2]) << 8) | static_cast<uint32_t>(payload[3]); } if (stream_id > last_stream_id) { HTTPException ee; ee[HTTPException::Error] << "HTTP/2: GOAWAY before stream " << stream_id << " was processed (last_stream_id=" << last_stream_id << ")"; throw ee; } break; } Loading Loading @@ -2929,17 +2965,8 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( pos = frame_start; break; } else if (frame_type == 0x01) { auto decoded = qpack::Decoder::decode(raw.data() + pos, frame_len); // Build synthetic HTTP/1.1 response for HttpResponse::parse() std::string status_val = "200"; for (const auto &h : decoded) if (h.name == ":status") status_val = h.value; std::string synth = "HTTP/1.1 " + status_val + "\r\n"; for (const auto &h : decoded) if (!h.name.empty() && h.name[0] != ':') synth += h.name + ": " + h.value + "\r\n"; synth += "\r\n"; _lastResponse = std::make_unique<HttpResponse>(); _lastResponse->parse(synth.c_str(), synth.size()); _lastResponse->parseH3(decoded); _lastStatusCode = _lastResponse->getStatusCode(); status_code = _lastStatusCode; _lastContentType = _lastResponse->getContentType(); Loading Loading @@ -3915,15 +3942,12 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) { return bytes_consumed; } // Shared by parseH2/parseH3 below: HPACK/QPACK already split each field into // a clean name/value pair, so ingestion here is a straight copy into the // header list -- no CRLF/colon scanning, and (unlike the old synthetic-text // approach) no way for a value to be misinterpreted as introducing new // header lines. template <typename HeaderFieldT> static size_t ingestDecodedHeaders(libhttppp::HttpResponse &resp, const std::vector<HeaderFieldT> &headers, const std::string &httpVersion) { // parseH2/parseH3: HPACK/QPACK already split each field into a clean // name/value pair, so ingestion here is a straight copy into the header // list -- no CRLF/colon scanning, and (unlike the old synthetic-text // approach this replaces) no way for a value to be misinterpreted as // introducing new header lines. size_t libhttppp::HttpResponse::parseH2(const std::vector<hpack::HeaderField> &headers) { int statusCode = 0; for (const auto &h : headers) { if (h.name == ":status") { Loading @@ -3931,26 +3955,50 @@ static size_t ingestDecodedHeaders(libhttppp::HttpResponse &resp, break; } } resp.setResponseLine(httpVersion, statusCode); _Version = "HTTP/2"; _StatusCode = statusCode; _State.clear(); for (const auto &h : headers) { if (h.name.empty() || h.name[0] == ':') continue; std::string key = h.name; for (char &c : key) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); resp.setHeaderData(key)->push_back(h.value); setHeaderData(key)->push_back(h.value); } resp.refreshCachedHeaders(); _ContentLength = getHeaderData("content-length"); _Connection = getHeaderData("connection"); _ContentType = getHeaderData("content-type"); _TransferEncoding = getHeaderData("transfer-encoding"); return headers.size(); } size_t libhttppp::HttpResponse::parseH2(const std::vector<hpack::HeaderField> &headers) { return ingestDecodedHeaders(*this, headers, "HTTP/2"); size_t libhttppp::HttpResponse::parseH3(const std::vector<qpack::HeaderField> &headers) { int statusCode = 0; for (const auto &h : headers) { if (h.name == ":status") { try { statusCode = std::stoi(h.value); } catch (...) { statusCode = 0; } break; } } _Version = "HTTP/3"; _StatusCode = statusCode; _State.clear(); size_t libhttppp::HttpResponse::parseH3(const std::vector<qpack::HeaderField> &headers) { return ingestDecodedHeaders(*this, headers, "HTTP/3"); for (const auto &h : headers) { if (h.name.empty() || h.name[0] == ':') continue; std::string key = h.name; for (char &c : key) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); setHeaderData(key)->push_back(h.value); } _ContentLength = getHeaderData("content-length"); _Connection = getHeaderData("connection"); _ContentType = getHeaderData("content-type"); _TransferEncoding = getHeaderData("transfer-encoding"); return headers.size(); } void libhttppp::HttpResponse::setTransferEncoding(const std ::string &enc){ Loading