Loading src/hpack.cpp +11 −1 Original line number Diff line number Diff line Loading @@ -212,9 +212,12 @@ uint32_t Decoder::decodeInt(const uint8_t *data, size_t len, uint8_t prefix_bits uint32_t m = 0; while (bytes_read < len) { uint8_t b = data[bytes_read++]; if (m < 32) { value += static_cast<uint32_t>(b & 0x7f) << m; } m += 7; if (!(b & 0x80)) return value; if (m >= 32) return value; // prevent overflow / runaway } return value; } Loading Loading @@ -302,6 +305,7 @@ std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) { size_t off = 0; while (off < len) { size_t prev_off = off; // progress guard uint8_t byte = data[off]; if (byte & 0x80) { Loading Loading @@ -360,6 +364,12 @@ std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) { } else { ++off; // skip unknown } // Safety: if no byte was consumed this iteration, advance by one // to avoid an infinite loop on malformed input. if (off == prev_off) { ++off; } } return headers; } Loading src/hpack.h +10 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,16 @@ private: class Decoder { public: Decoder() = default; ~Decoder() = default; // Non-copyable / non-movable — the dynamic table must not be // accidentally shared or left in a moved-from state. Decoder(const Decoder &) = delete; Decoder &operator=(const Decoder &) = delete; Decoder(Decoder &&) = delete; Decoder &operator=(Decoder &&) = delete; void reset() { _dynTable.clear(); _dynTableSize = 0; } // Instance method: decode using (and updating) the dynamic table std::vector<HeaderField> decode(const uint8_t *data, size_t len); Loading src/http.cpp +9 −8 Original line number Diff line number Diff line Loading @@ -1537,6 +1537,8 @@ libhttppp::HttpRequest::HttpRequest() : HttpHeader() { _MaxUploadSize=DEFAULT_UPLOADSIZE; }; libhttppp::HttpRequest::~HttpRequest() = default; libhttppp::HttpRequest::HttpRequest(netplus::eventapi *evapi) : HttpHeader(), con(evapi){ _RequestType=PARSEREQUEST; _MaxUploadSize=DEFAULT_UPLOADSIZE; Loading @@ -1549,11 +1551,9 @@ void libhttppp::HttpRequest::clear(){ _cachedRequest.clear(); _cachedRequestVersion.clear(); _httpProtocol = 0; _h2StreamId = 0; _h2HeadersSent = false; _h2ExpectedContentLength = 0; _h2BodyBytesSent = 0; _h2PendingResponses.clear(); // Reset H2 state: release the heap block entirely so that // any corrupted deque/map internals are freed as a unit. _h2.reset(); } size_t libhttppp::HttpRequest::parse() { Loading Loading @@ -1599,9 +1599,10 @@ size_t libhttppp::HttpRequest::parseH2(const std::vector<hpack::HeaderField> &he uint32_t stream_id) { clear(); _httpProtocol = 1; _h2StreamId = stream_id; _h2HeadersSent = false; _h2BodyBytesSent = 0; auto &h2 = h2state(); h2.streamId = stream_id; h2.headersSent = false; h2.bodyBytesSent = 0; SendData.pos = 0; // Store ALL headers (including pseudo-headers) in _firstHeaderData. Loading src/http.h +23 −12 Original line number Diff line number Diff line Loading @@ -223,7 +223,7 @@ namespace libhttppp { public: HttpRequest(); HttpRequest(netplus::eventapi *evapi); ~HttpRequest()=default; ~HttpRequest(); void clear(); Loading Loading @@ -275,30 +275,41 @@ namespace libhttppp { mutable std::string _cachedRequest; mutable std::string _cachedRequestVersion; // HTTP/2 and HTTP/3 protocol state (managed by HttpEvent) // HTTP/2 and HTTP/3 protocol state (managed by HttpEvent). // All H2-specific mutable state lives in a heap-allocated struct so // that inline-layout corruption of HttpRequest cannot trash the // deque / map / decoder internals. int _httpProtocol = 0; // 0=HTTP/1.x, 1=HTTP/2, 2=HTTP/3 uint32_t _h2StreamId = 0; bool _h2HeadersSent = false; size_t _h2ExpectedContentLength = 0; size_t _h2BodyBytesSent = 0; // Queue of pending H2 stream responses (body data waiting to be framed) struct H2PendingResponse { uint32_t streamId; std::string body; // remaining body data to send as DATA frames size_t offset = 0; // how far into body we've sent }; std::deque<H2PendingResponse> _h2PendingResponses; // Per-connection buffer for incomplete incoming H2 streams (awaiting DATA) struct H2PendingIncoming { std::vector<hpack::HeaderField> headers; std::string body; }; std::map<uint32_t, H2PendingIncoming> _h2PendingIncoming; // Per-connection HPACK decoder (maintains dynamic table across H2 frames) hpack::Decoder _h2HpackDecoder; struct H2State { uint32_t streamId = 0; bool headersSent = false; size_t expectedContentLength = 0; size_t bodyBytesSent = 0; std::deque<H2PendingResponse> pendingResponses; std::map<uint32_t, H2PendingIncoming> pendingIncoming; hpack::Decoder hpackDecoder; }; // Lazily allocated when the connection is upgraded to HTTP/2. std::unique_ptr<H2State> _h2; // Allocate H2 state if not yet present; return reference. H2State &h2state() { if (!_h2) _h2 = std::make_unique<H2State>(); return *_h2; } friend class HttpForm; friend class HttpResponse; Loading src/httpd.cpp +10 −10 Original line number Diff line number Diff line Loading @@ -346,7 +346,7 @@ void libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, if (is_streaming) { // Streaming response: collect all data via ResponseEvent tempreq._h2ExpectedContentLength = content_length; tempreq.h2state().expectedContentLength = content_length; tempreq.SendData.pos = 0; size_t max_iterations = content_length / 64 + 4096; Loading Loading @@ -418,7 +418,7 @@ void libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, pending.streamId = sid; pending.body = std::move(body); pending.offset = H2_MAX_FRAME_SIZE; cureq._h2PendingResponses.push_back(std::move(pending)); cureq.h2state().pendingResponses.push_back(std::move(pending)); } } } Loading Loading @@ -486,21 +486,21 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, // Decode HPACK headers using the connection's decoder // (maintains dynamic table state across frames) auto decoded = cureq._h2HpackDecoder.decode(hpack_data, hpack_len); auto decoded = cureq.h2state().hpackDecoder.decode(hpack_data, hpack_len); if (fflags & H2_FLAG_END_STREAM) { // No body expected (GET, HEAD, etc.) — dispatch immediately _dispatchH2Stream(cureq, out, sid, decoded, "", tid, args); } else { // Body expected via DATA frames (POST, PUT, etc.) cureq._h2PendingIncoming[sid] = {std::move(decoded), ""}; cureq.h2state().pendingIncoming[sid] = {std::move(decoded), ""}; } break; } case H2_FRAME_DATA: { auto it = cureq._h2PendingIncoming.find(sid); if (it != cureq._h2PendingIncoming.end()) { auto it = cureq.h2state().pendingIncoming.find(sid); if (it != cureq.h2state().pendingIncoming.end()) { it->second.body.append(data + off, flen); if (fflags & H2_FLAG_END_STREAM) { // All body data received — dispatch the request Loading @@ -508,7 +508,7 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, it->second.headers, it->second.body, tid, args); cureq._h2PendingIncoming.erase(it); cureq.h2state().pendingIncoming.erase(it); } } break; Loading Loading @@ -754,8 +754,8 @@ void libhttppp::HttpEvent::ResponseEvent(netplus::con &curcon,const int tid,ULON // HTTP/2: connection is persistent and multiplexed. // Incrementally send pending H2 stream DATA frames. if (cureq._httpProtocol == 1) { if (!cureq._h2PendingResponses.empty()) { auto &pending = cureq._h2PendingResponses.front(); if (cureq._h2 && !cureq._h2->pendingResponses.empty()) { auto &pending = cureq._h2->pendingResponses.front(); size_t remaining = pending.body.size() - pending.offset; if (remaining > 0) { size_t chunk = std::min(remaining, H2_MAX_FRAME_SIZE); Loading @@ -768,7 +768,7 @@ void libhttppp::HttpEvent::ResponseEvent(netplus::con &curcon,const int tid,ULON pending.offset += chunk; } if (pending.offset >= pending.body.size()) { cureq._h2PendingResponses.pop_front(); cureq._h2->pendingResponses.pop_front(); } } return; Loading Loading
src/hpack.cpp +11 −1 Original line number Diff line number Diff line Loading @@ -212,9 +212,12 @@ uint32_t Decoder::decodeInt(const uint8_t *data, size_t len, uint8_t prefix_bits uint32_t m = 0; while (bytes_read < len) { uint8_t b = data[bytes_read++]; if (m < 32) { value += static_cast<uint32_t>(b & 0x7f) << m; } m += 7; if (!(b & 0x80)) return value; if (m >= 32) return value; // prevent overflow / runaway } return value; } Loading Loading @@ -302,6 +305,7 @@ std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) { size_t off = 0; while (off < len) { size_t prev_off = off; // progress guard uint8_t byte = data[off]; if (byte & 0x80) { Loading Loading @@ -360,6 +364,12 @@ std::vector<HeaderField> Decoder::decode(const uint8_t *data, size_t len) { } else { ++off; // skip unknown } // Safety: if no byte was consumed this iteration, advance by one // to avoid an infinite loop on malformed input. if (off == prev_off) { ++off; } } return headers; } Loading
src/hpack.h +10 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,16 @@ private: class Decoder { public: Decoder() = default; ~Decoder() = default; // Non-copyable / non-movable — the dynamic table must not be // accidentally shared or left in a moved-from state. Decoder(const Decoder &) = delete; Decoder &operator=(const Decoder &) = delete; Decoder(Decoder &&) = delete; Decoder &operator=(Decoder &&) = delete; void reset() { _dynTable.clear(); _dynTableSize = 0; } // Instance method: decode using (and updating) the dynamic table std::vector<HeaderField> decode(const uint8_t *data, size_t len); Loading
src/http.cpp +9 −8 Original line number Diff line number Diff line Loading @@ -1537,6 +1537,8 @@ libhttppp::HttpRequest::HttpRequest() : HttpHeader() { _MaxUploadSize=DEFAULT_UPLOADSIZE; }; libhttppp::HttpRequest::~HttpRequest() = default; libhttppp::HttpRequest::HttpRequest(netplus::eventapi *evapi) : HttpHeader(), con(evapi){ _RequestType=PARSEREQUEST; _MaxUploadSize=DEFAULT_UPLOADSIZE; Loading @@ -1549,11 +1551,9 @@ void libhttppp::HttpRequest::clear(){ _cachedRequest.clear(); _cachedRequestVersion.clear(); _httpProtocol = 0; _h2StreamId = 0; _h2HeadersSent = false; _h2ExpectedContentLength = 0; _h2BodyBytesSent = 0; _h2PendingResponses.clear(); // Reset H2 state: release the heap block entirely so that // any corrupted deque/map internals are freed as a unit. _h2.reset(); } size_t libhttppp::HttpRequest::parse() { Loading Loading @@ -1599,9 +1599,10 @@ size_t libhttppp::HttpRequest::parseH2(const std::vector<hpack::HeaderField> &he uint32_t stream_id) { clear(); _httpProtocol = 1; _h2StreamId = stream_id; _h2HeadersSent = false; _h2BodyBytesSent = 0; auto &h2 = h2state(); h2.streamId = stream_id; h2.headersSent = false; h2.bodyBytesSent = 0; SendData.pos = 0; // Store ALL headers (including pseudo-headers) in _firstHeaderData. Loading
src/http.h +23 −12 Original line number Diff line number Diff line Loading @@ -223,7 +223,7 @@ namespace libhttppp { public: HttpRequest(); HttpRequest(netplus::eventapi *evapi); ~HttpRequest()=default; ~HttpRequest(); void clear(); Loading Loading @@ -275,30 +275,41 @@ namespace libhttppp { mutable std::string _cachedRequest; mutable std::string _cachedRequestVersion; // HTTP/2 and HTTP/3 protocol state (managed by HttpEvent) // HTTP/2 and HTTP/3 protocol state (managed by HttpEvent). // All H2-specific mutable state lives in a heap-allocated struct so // that inline-layout corruption of HttpRequest cannot trash the // deque / map / decoder internals. int _httpProtocol = 0; // 0=HTTP/1.x, 1=HTTP/2, 2=HTTP/3 uint32_t _h2StreamId = 0; bool _h2HeadersSent = false; size_t _h2ExpectedContentLength = 0; size_t _h2BodyBytesSent = 0; // Queue of pending H2 stream responses (body data waiting to be framed) struct H2PendingResponse { uint32_t streamId; std::string body; // remaining body data to send as DATA frames size_t offset = 0; // how far into body we've sent }; std::deque<H2PendingResponse> _h2PendingResponses; // Per-connection buffer for incomplete incoming H2 streams (awaiting DATA) struct H2PendingIncoming { std::vector<hpack::HeaderField> headers; std::string body; }; std::map<uint32_t, H2PendingIncoming> _h2PendingIncoming; // Per-connection HPACK decoder (maintains dynamic table across H2 frames) hpack::Decoder _h2HpackDecoder; struct H2State { uint32_t streamId = 0; bool headersSent = false; size_t expectedContentLength = 0; size_t bodyBytesSent = 0; std::deque<H2PendingResponse> pendingResponses; std::map<uint32_t, H2PendingIncoming> pendingIncoming; hpack::Decoder hpackDecoder; }; // Lazily allocated when the connection is upgraded to HTTP/2. std::unique_ptr<H2State> _h2; // Allocate H2 state if not yet present; return reference. H2State &h2state() { if (!_h2) _h2 = std::make_unique<H2State>(); return *_h2; } friend class HttpForm; friend class HttpResponse; Loading
src/httpd.cpp +10 −10 Original line number Diff line number Diff line Loading @@ -346,7 +346,7 @@ void libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, if (is_streaming) { // Streaming response: collect all data via ResponseEvent tempreq._h2ExpectedContentLength = content_length; tempreq.h2state().expectedContentLength = content_length; tempreq.SendData.pos = 0; size_t max_iterations = content_length / 64 + 4096; Loading Loading @@ -418,7 +418,7 @@ void libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, pending.streamId = sid; pending.body = std::move(body); pending.offset = H2_MAX_FRAME_SIZE; cureq._h2PendingResponses.push_back(std::move(pending)); cureq.h2state().pendingResponses.push_back(std::move(pending)); } } } Loading Loading @@ -486,21 +486,21 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, // Decode HPACK headers using the connection's decoder // (maintains dynamic table state across frames) auto decoded = cureq._h2HpackDecoder.decode(hpack_data, hpack_len); auto decoded = cureq.h2state().hpackDecoder.decode(hpack_data, hpack_len); if (fflags & H2_FLAG_END_STREAM) { // No body expected (GET, HEAD, etc.) — dispatch immediately _dispatchH2Stream(cureq, out, sid, decoded, "", tid, args); } else { // Body expected via DATA frames (POST, PUT, etc.) cureq._h2PendingIncoming[sid] = {std::move(decoded), ""}; cureq.h2state().pendingIncoming[sid] = {std::move(decoded), ""}; } break; } case H2_FRAME_DATA: { auto it = cureq._h2PendingIncoming.find(sid); if (it != cureq._h2PendingIncoming.end()) { auto it = cureq.h2state().pendingIncoming.find(sid); if (it != cureq.h2state().pendingIncoming.end()) { it->second.body.append(data + off, flen); if (fflags & H2_FLAG_END_STREAM) { // All body data received — dispatch the request Loading @@ -508,7 +508,7 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, it->second.headers, it->second.body, tid, args); cureq._h2PendingIncoming.erase(it); cureq.h2state().pendingIncoming.erase(it); } } break; Loading Loading @@ -754,8 +754,8 @@ void libhttppp::HttpEvent::ResponseEvent(netplus::con &curcon,const int tid,ULON // HTTP/2: connection is persistent and multiplexed. // Incrementally send pending H2 stream DATA frames. if (cureq._httpProtocol == 1) { if (!cureq._h2PendingResponses.empty()) { auto &pending = cureq._h2PendingResponses.front(); if (cureq._h2 && !cureq._h2->pendingResponses.empty()) { auto &pending = cureq._h2->pendingResponses.front(); size_t remaining = pending.body.size() - pending.offset; if (remaining > 0) { size_t chunk = std::min(remaining, H2_MAX_FRAME_SIZE); Loading @@ -768,7 +768,7 @@ void libhttppp::HttpEvent::ResponseEvent(netplus::con &curcon,const int tid,ULON pending.offset += chunk; } if (pending.offset >= pending.body.size()) { cureq._h2PendingResponses.pop_front(); cureq._h2->pendingResponses.pop_front(); } } return; Loading