Loading src/http.h +8 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <memory> #include <deque> #include <map> #include <netplus/socket.h> #include <netplus/connection.h> Loading Loading @@ -289,6 +290,13 @@ namespace libhttppp { }; 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; Loading src/httpd.cpp +258 −204 Original line number Diff line number Diff line Loading @@ -292,76 +292,22 @@ void libhttppp::HttpEvent::ConnectEvent(HttpRequest &curreq,const int tid,ULONG_ void libhttppp::HttpEvent::DisconnectEvent(HttpRequest &curreq,const int tid,ULONG_PTR args){ } bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args, const std::string &alpn, const netplus::ssl::FramingCallback &frame_cb) { (void)alpn; (void)frame_cb; HttpRequest &cureq = dynamic_cast<HttpRequest&>(curcon); cureq._httpProtocol = 1; // Mark connection as HTTP/2 std::string out; size_t off = 0; // Consume HTTP/2 client connection preface if present if (cureq.RecvData.size() >= H2_CLIENT_PREFACE_LEN && std::memcmp(cureq.RecvData.data(), H2_CLIENT_PREFACE, H2_CLIENT_PREFACE_LEN) == 0) { off += H2_CLIENT_PREFACE_LEN; } // Send server SETTINGS if not already sent (e.g. ConnectEvent already sent it) if (cureq.SendData.empty()) { out += h2BuildSettings(); } // Process all complete frames in RecvData while (off + H2_FRAME_HEADER_LEN <= cureq.RecvData.size()) { const char *data = cureq.RecvData.data(); uint32_t flen = (static_cast<uint8_t>(data[off]) << 16) | (static_cast<uint8_t>(data[off + 1]) << 8) | static_cast<uint8_t>(data[off + 2]); uint8_t ftype = static_cast<uint8_t>(data[off + 3]); uint8_t fflags = static_cast<uint8_t>(data[off + 4]); uint32_t sid = ((static_cast<uint8_t>(data[off + 5]) & 0x7f) << 24) | (static_cast<uint8_t>(data[off + 6]) << 16) | (static_cast<uint8_t>(data[off + 7]) << 8) | static_cast<uint8_t>(data[off + 8]); if (off + H2_FRAME_HEADER_LEN + flen > cureq.RecvData.size()) { break; // Incomplete frame, wait for more data } off += H2_FRAME_HEADER_LEN; switch (ftype) { case H2_FRAME_SETTINGS: if (!(fflags & H2_FLAG_ACK)) { out += h2BuildSettingsAck(); } break; case H2_FRAME_HEADERS: { // RFC 7540 §6.2: If PRIORITY flag is set, the first 5 bytes // are priority data (stream dependency + weight), not HPACK. const uint8_t *hpack_data = reinterpret_cast<const uint8_t*>(data + off); size_t hpack_len = flen; if ((fflags & H2_FLAG_PRIORITY) && hpack_len >= 5) { hpack_data += 5; hpack_len -= 5; } // Decode HPACK headers using the connection's decoder // (maintains dynamic table state across frames) auto decoded = cureq._h2HpackDecoder.decode(hpack_data, hpack_len); void libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, std::string &out, uint32_t sid, const std::vector<hpack::HeaderField> &decoded, const std::string &reqBody, const int tid, ULONG_PTR args) { // Create a per-stream temporary HttpRequest (HTTP/2 multiplexes // multiple streams on one connection — each needs its own state) HttpRequest tempreq; tempreq.parseH2(decoded, sid); // Store POST body in RecvData so HttpForm::parse() can access it if (!reqBody.empty()) { tempreq.RecvData.append(reqBody.data(), reqBody.size()); } // Dispatch to user's virtual RequestEvent RequestEvent(tempreq, tid, args); Loading Loading @@ -475,6 +421,96 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, cureq._h2PendingResponses.push_back(std::move(pending)); } } } bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args, const std::string &alpn, const netplus::ssl::FramingCallback &frame_cb) { (void)alpn; (void)frame_cb; HttpRequest &cureq = dynamic_cast<HttpRequest&>(curcon); cureq._httpProtocol = 1; // Mark connection as HTTP/2 std::string out; size_t off = 0; // Consume HTTP/2 client connection preface if present if (cureq.RecvData.size() >= H2_CLIENT_PREFACE_LEN && std::memcmp(cureq.RecvData.data(), H2_CLIENT_PREFACE, H2_CLIENT_PREFACE_LEN) == 0) { off += H2_CLIENT_PREFACE_LEN; } // Send server SETTINGS if not already sent (e.g. ConnectEvent already sent it) if (cureq.SendData.empty()) { out += h2BuildSettings(); } // Process all complete frames in RecvData while (off + H2_FRAME_HEADER_LEN <= cureq.RecvData.size()) { const char *data = cureq.RecvData.data(); uint32_t flen = (static_cast<uint8_t>(data[off]) << 16) | (static_cast<uint8_t>(data[off + 1]) << 8) | static_cast<uint8_t>(data[off + 2]); uint8_t ftype = static_cast<uint8_t>(data[off + 3]); uint8_t fflags = static_cast<uint8_t>(data[off + 4]); uint32_t sid = ((static_cast<uint8_t>(data[off + 5]) & 0x7f) << 24) | (static_cast<uint8_t>(data[off + 6]) << 16) | (static_cast<uint8_t>(data[off + 7]) << 8) | static_cast<uint8_t>(data[off + 8]); if (off + H2_FRAME_HEADER_LEN + flen > cureq.RecvData.size()) { break; // Incomplete frame, wait for more data } off += H2_FRAME_HEADER_LEN; switch (ftype) { case H2_FRAME_SETTINGS: if (!(fflags & H2_FLAG_ACK)) { out += h2BuildSettingsAck(); } break; case H2_FRAME_HEADERS: { // RFC 7540 §6.2: If PRIORITY flag is set, the first 5 bytes // are priority data (stream dependency + weight), not HPACK. const uint8_t *hpack_data = reinterpret_cast<const uint8_t*>(data + off); size_t hpack_len = flen; if ((fflags & H2_FLAG_PRIORITY) && hpack_len >= 5) { hpack_data += 5; hpack_len -= 5; } // Decode HPACK headers using the connection's decoder // (maintains dynamic table state across frames) auto decoded = cureq._h2HpackDecoder.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), ""}; } break; } case H2_FRAME_DATA: { auto it = cureq._h2PendingIncoming.find(sid); if (it != cureq._h2PendingIncoming.end()) { it->second.body.append(data + off, flen); if (fflags & H2_FLAG_END_STREAM) { // All body data received — dispatch the request _dispatchH2Stream(cureq, out, sid, it->second.headers, it->second.body, tid, args); cureq._h2PendingIncoming.erase(it); } } break; } Loading Loading @@ -525,6 +561,12 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, return; } // Two-pass approach: first collect HEADERS and DATA payloads, // then dispatch. This ensures POST body data is available // in RecvData when RequestEvent is called. std::vector<uint8_t> headersPayload; std::string bodyData; size_t offset = 0; while (offset < data.size()) { size_t bytes = 0; Loading @@ -547,14 +589,31 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, } if (frame_type == 0x01) { // HEADERS frame headersPayload.assign(&data[offset], &data[offset + frame_len]); } else if (frame_type == 0x00) { // DATA frame bodyData.append(reinterpret_cast<const char*>(&data[offset]), frame_len); } offset += frame_len; } if (headersPayload.empty()) { return; } // Decode QPACK headers auto decoded = libhttppp::qpack::Decoder::decode( &data[offset], frame_len); headersPayload.data(), headersPayload.size()); // Create a temporary HttpRequest and parse via parseH3 HttpRequest tempreq; tempreq.parseH3(decoded); // Store POST body in RecvData so HttpForm::parse() can access it if (!bodyData.empty()) { tempreq.RecvData.append(bodyData.data(), bodyData.size()); } // Call user's RequestEvent RequestEvent(tempreq, 0, 0); Loading Loading @@ -640,11 +699,6 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, tempreq.deldata(":res-content-type"); tempreq.deldata(":res-content-length"); } break; } offset += frame_len; } } void libhttppp::HttpEvent::RequestEvent(netplus::con &curcon,const int tid,ULONG_PTR args){ Loading src/httpd.h +5 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,11 @@ namespace libhttppp { void CreateConnection(std::shared_ptr<netplus::con> &res); std::string _altSvcH3; // Alt-Svc value for HTTP/3 advertisement private: void _dispatchH2Stream(HttpRequest &cureq, std::string &out, uint32_t sid, const std::vector<hpack::HeaderField> &decoded, const std::string &reqBody, const int tid, ULONG_PTR args); void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args); void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args); void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args); Loading Loading
src/http.h +8 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <memory> #include <deque> #include <map> #include <netplus/socket.h> #include <netplus/connection.h> Loading Loading @@ -289,6 +290,13 @@ namespace libhttppp { }; 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; Loading
src/httpd.cpp +258 −204 Original line number Diff line number Diff line Loading @@ -292,76 +292,22 @@ void libhttppp::HttpEvent::ConnectEvent(HttpRequest &curreq,const int tid,ULONG_ void libhttppp::HttpEvent::DisconnectEvent(HttpRequest &curreq,const int tid,ULONG_PTR args){ } bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args, const std::string &alpn, const netplus::ssl::FramingCallback &frame_cb) { (void)alpn; (void)frame_cb; HttpRequest &cureq = dynamic_cast<HttpRequest&>(curcon); cureq._httpProtocol = 1; // Mark connection as HTTP/2 std::string out; size_t off = 0; // Consume HTTP/2 client connection preface if present if (cureq.RecvData.size() >= H2_CLIENT_PREFACE_LEN && std::memcmp(cureq.RecvData.data(), H2_CLIENT_PREFACE, H2_CLIENT_PREFACE_LEN) == 0) { off += H2_CLIENT_PREFACE_LEN; } // Send server SETTINGS if not already sent (e.g. ConnectEvent already sent it) if (cureq.SendData.empty()) { out += h2BuildSettings(); } // Process all complete frames in RecvData while (off + H2_FRAME_HEADER_LEN <= cureq.RecvData.size()) { const char *data = cureq.RecvData.data(); uint32_t flen = (static_cast<uint8_t>(data[off]) << 16) | (static_cast<uint8_t>(data[off + 1]) << 8) | static_cast<uint8_t>(data[off + 2]); uint8_t ftype = static_cast<uint8_t>(data[off + 3]); uint8_t fflags = static_cast<uint8_t>(data[off + 4]); uint32_t sid = ((static_cast<uint8_t>(data[off + 5]) & 0x7f) << 24) | (static_cast<uint8_t>(data[off + 6]) << 16) | (static_cast<uint8_t>(data[off + 7]) << 8) | static_cast<uint8_t>(data[off + 8]); if (off + H2_FRAME_HEADER_LEN + flen > cureq.RecvData.size()) { break; // Incomplete frame, wait for more data } off += H2_FRAME_HEADER_LEN; switch (ftype) { case H2_FRAME_SETTINGS: if (!(fflags & H2_FLAG_ACK)) { out += h2BuildSettingsAck(); } break; case H2_FRAME_HEADERS: { // RFC 7540 §6.2: If PRIORITY flag is set, the first 5 bytes // are priority data (stream dependency + weight), not HPACK. const uint8_t *hpack_data = reinterpret_cast<const uint8_t*>(data + off); size_t hpack_len = flen; if ((fflags & H2_FLAG_PRIORITY) && hpack_len >= 5) { hpack_data += 5; hpack_len -= 5; } // Decode HPACK headers using the connection's decoder // (maintains dynamic table state across frames) auto decoded = cureq._h2HpackDecoder.decode(hpack_data, hpack_len); void libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, std::string &out, uint32_t sid, const std::vector<hpack::HeaderField> &decoded, const std::string &reqBody, const int tid, ULONG_PTR args) { // Create a per-stream temporary HttpRequest (HTTP/2 multiplexes // multiple streams on one connection — each needs its own state) HttpRequest tempreq; tempreq.parseH2(decoded, sid); // Store POST body in RecvData so HttpForm::parse() can access it if (!reqBody.empty()) { tempreq.RecvData.append(reqBody.data(), reqBody.size()); } // Dispatch to user's virtual RequestEvent RequestEvent(tempreq, tid, args); Loading Loading @@ -475,6 +421,96 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, cureq._h2PendingResponses.push_back(std::move(pending)); } } } bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args, const std::string &alpn, const netplus::ssl::FramingCallback &frame_cb) { (void)alpn; (void)frame_cb; HttpRequest &cureq = dynamic_cast<HttpRequest&>(curcon); cureq._httpProtocol = 1; // Mark connection as HTTP/2 std::string out; size_t off = 0; // Consume HTTP/2 client connection preface if present if (cureq.RecvData.size() >= H2_CLIENT_PREFACE_LEN && std::memcmp(cureq.RecvData.data(), H2_CLIENT_PREFACE, H2_CLIENT_PREFACE_LEN) == 0) { off += H2_CLIENT_PREFACE_LEN; } // Send server SETTINGS if not already sent (e.g. ConnectEvent already sent it) if (cureq.SendData.empty()) { out += h2BuildSettings(); } // Process all complete frames in RecvData while (off + H2_FRAME_HEADER_LEN <= cureq.RecvData.size()) { const char *data = cureq.RecvData.data(); uint32_t flen = (static_cast<uint8_t>(data[off]) << 16) | (static_cast<uint8_t>(data[off + 1]) << 8) | static_cast<uint8_t>(data[off + 2]); uint8_t ftype = static_cast<uint8_t>(data[off + 3]); uint8_t fflags = static_cast<uint8_t>(data[off + 4]); uint32_t sid = ((static_cast<uint8_t>(data[off + 5]) & 0x7f) << 24) | (static_cast<uint8_t>(data[off + 6]) << 16) | (static_cast<uint8_t>(data[off + 7]) << 8) | static_cast<uint8_t>(data[off + 8]); if (off + H2_FRAME_HEADER_LEN + flen > cureq.RecvData.size()) { break; // Incomplete frame, wait for more data } off += H2_FRAME_HEADER_LEN; switch (ftype) { case H2_FRAME_SETTINGS: if (!(fflags & H2_FLAG_ACK)) { out += h2BuildSettingsAck(); } break; case H2_FRAME_HEADERS: { // RFC 7540 §6.2: If PRIORITY flag is set, the first 5 bytes // are priority data (stream dependency + weight), not HPACK. const uint8_t *hpack_data = reinterpret_cast<const uint8_t*>(data + off); size_t hpack_len = flen; if ((fflags & H2_FLAG_PRIORITY) && hpack_len >= 5) { hpack_data += 5; hpack_len -= 5; } // Decode HPACK headers using the connection's decoder // (maintains dynamic table state across frames) auto decoded = cureq._h2HpackDecoder.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), ""}; } break; } case H2_FRAME_DATA: { auto it = cureq._h2PendingIncoming.find(sid); if (it != cureq._h2PendingIncoming.end()) { it->second.body.append(data + off, flen); if (fflags & H2_FLAG_END_STREAM) { // All body data received — dispatch the request _dispatchH2Stream(cureq, out, sid, it->second.headers, it->second.body, tid, args); cureq._h2PendingIncoming.erase(it); } } break; } Loading Loading @@ -525,6 +561,12 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, return; } // Two-pass approach: first collect HEADERS and DATA payloads, // then dispatch. This ensures POST body data is available // in RecvData when RequestEvent is called. std::vector<uint8_t> headersPayload; std::string bodyData; size_t offset = 0; while (offset < data.size()) { size_t bytes = 0; Loading @@ -547,14 +589,31 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, } if (frame_type == 0x01) { // HEADERS frame headersPayload.assign(&data[offset], &data[offset + frame_len]); } else if (frame_type == 0x00) { // DATA frame bodyData.append(reinterpret_cast<const char*>(&data[offset]), frame_len); } offset += frame_len; } if (headersPayload.empty()) { return; } // Decode QPACK headers auto decoded = libhttppp::qpack::Decoder::decode( &data[offset], frame_len); headersPayload.data(), headersPayload.size()); // Create a temporary HttpRequest and parse via parseH3 HttpRequest tempreq; tempreq.parseH3(decoded); // Store POST body in RecvData so HttpForm::parse() can access it if (!bodyData.empty()) { tempreq.RecvData.append(bodyData.data(), bodyData.size()); } // Call user's RequestEvent RequestEvent(tempreq, 0, 0); Loading Loading @@ -640,11 +699,6 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, tempreq.deldata(":res-content-type"); tempreq.deldata(":res-content-length"); } break; } offset += frame_len; } } void libhttppp::HttpEvent::RequestEvent(netplus::con &curcon,const int tid,ULONG_PTR args){ Loading
src/httpd.h +5 −0 Original line number Diff line number Diff line Loading @@ -66,6 +66,11 @@ namespace libhttppp { void CreateConnection(std::shared_ptr<netplus::con> &res); std::string _altSvcH3; // Alt-Svc value for HTTP/3 advertisement private: void _dispatchH2Stream(HttpRequest &cureq, std::string &out, uint32_t sid, const std::vector<hpack::HeaderField> &decoded, const std::string &reqBody, const int tid, ULONG_PTR args); void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args); void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args); void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args); Loading