Loading src/http.cpp +23 −5 Original line number Diff line number Diff line Loading @@ -679,7 +679,9 @@ std::vector<char> libhttppp::HttpClient::_h1ReadResponse(const std::string &labe } if (chunk_size == 0) { for (;;) { // Consume trailers (limit to 128 to prevent unbounded // memory growth from a malicious server). for (int trailer_i = 0; trailer_i < 128; ++trailer_i) { std::string tline = read_line(); if (tline.empty()) break; } Loading Loading @@ -1299,6 +1301,10 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { } }; // Iterative loop: avoids tail-recursion when a new chunk is parsed // but no data is returned yet (chunk header consumed, data follows). for (;;) { // If we're in the middle of a chunk, continue reading chunk data if (_streamChunkRemaining > 0) { size_t want = (std::min)(bufsize, _streamChunkRemaining); Loading Loading @@ -1356,8 +1362,9 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { } if (chunk_size == 0) { // Final chunk — consume trailers for (;;) { // Final chunk — consume trailers (limit to 128 to prevent // unbounded memory growth from a malicious server). for (int trailer_i = 0; trailer_i < 128; ++trailer_i) { std::string tline = readLine(); if (tline.empty()) break; } Loading @@ -1367,8 +1374,10 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { } _streamChunkRemaining = chunk_size; // Recurse to actually read data from this chunk return readBodyChunk(buf, bufsize); // Loop back to read data from this new chunk (iterative instead of // recursive to avoid stack overflow on many consecutive chunks). continue; } // end for(;;) } // ── EOF mode (no content-length, not chunked) ── Loading Loading @@ -2319,7 +2328,16 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( } }; // Overall response deadline: prevents infinite loops when a // malicious server sends endless non-terminal frames (e.g. PINGs). auto h2_response_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(120); while (!got_end_stream) { if (std::chrono::steady_clock::now() >= h2_response_deadline) { HTTPException ee; ee[HTTPException::Error] << "HTTP/2 response timeout: no END_STREAM received"; throw ee; } // Read frame header (9 bytes) ensure_bytes(H2C_FRAME_HEADER_LEN); Loading src/httpd.cpp +10 −1 Original line number Diff line number Diff line Loading @@ -757,6 +757,7 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, std::string out; out.reserve(4096); // Pre-allocate to avoid repeated reallocations size_t off = 0; int reprocess_count = 0; // Consume HTTP/2 client connection preface if present if (cureq.RecvData.size() >= H2_CLIENT_PREFACE_LEN && Loading Loading @@ -1063,7 +1064,10 @@ done: // Only reprocess if we actually made progress (consumed frames or // generated output). Otherwise we'd spin forever on an incomplete // frame that needs more network data to arrive. if ((!out.empty() || off > 0) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { // Guard: limit reprocessing iterations to prevent infinite spin when // frames continuously generate output (e.g. ACKs) without consuming data. if (reprocess_count < 16 && (!out.empty() || off > 0) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { ++reprocess_count; out.clear(); out.reserve(4096); off = 0; Loading Loading @@ -1377,6 +1381,11 @@ void libhttppp::HttpEvent::RequestEvent(netplus::con &curcon,const int tid,ULONG } REQUESTHANDLING: // Guard: only loop for HTTP/1.x; HTTP/2 and HTTP/3 parse() always // returns 1, which would cause an infinite loop here. if (cureq._httpProtocol != 0) { return; } switch(cureq.getRequestType()){ case PARSEREQUEST: if(cureq.parse()!=0) Loading Loading
src/http.cpp +23 −5 Original line number Diff line number Diff line Loading @@ -679,7 +679,9 @@ std::vector<char> libhttppp::HttpClient::_h1ReadResponse(const std::string &labe } if (chunk_size == 0) { for (;;) { // Consume trailers (limit to 128 to prevent unbounded // memory growth from a malicious server). for (int trailer_i = 0; trailer_i < 128; ++trailer_i) { std::string tline = read_line(); if (tline.empty()) break; } Loading Loading @@ -1299,6 +1301,10 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { } }; // Iterative loop: avoids tail-recursion when a new chunk is parsed // but no data is returned yet (chunk header consumed, data follows). for (;;) { // If we're in the middle of a chunk, continue reading chunk data if (_streamChunkRemaining > 0) { size_t want = (std::min)(bufsize, _streamChunkRemaining); Loading Loading @@ -1356,8 +1362,9 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { } if (chunk_size == 0) { // Final chunk — consume trailers for (;;) { // Final chunk — consume trailers (limit to 128 to prevent // unbounded memory growth from a malicious server). for (int trailer_i = 0; trailer_i < 128; ++trailer_i) { std::string tline = readLine(); if (tline.empty()) break; } Loading @@ -1367,8 +1374,10 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { } _streamChunkRemaining = chunk_size; // Recurse to actually read data from this chunk return readBodyChunk(buf, bufsize); // Loop back to read data from this new chunk (iterative instead of // recursive to avoid stack overflow on many consecutive chunks). continue; } // end for(;;) } // ── EOF mode (no content-length, not chunked) ── Loading Loading @@ -2319,7 +2328,16 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( } }; // Overall response deadline: prevents infinite loops when a // malicious server sends endless non-terminal frames (e.g. PINGs). auto h2_response_deadline = std::chrono::steady_clock::now() + std::chrono::seconds(120); while (!got_end_stream) { if (std::chrono::steady_clock::now() >= h2_response_deadline) { HTTPException ee; ee[HTTPException::Error] << "HTTP/2 response timeout: no END_STREAM received"; throw ee; } // Read frame header (9 bytes) ensure_bytes(H2C_FRAME_HEADER_LEN); Loading
src/httpd.cpp +10 −1 Original line number Diff line number Diff line Loading @@ -757,6 +757,7 @@ bool libhttppp::HttpEvent::Http2RequestEvent(netplus::con &curcon, std::string out; out.reserve(4096); // Pre-allocate to avoid repeated reallocations size_t off = 0; int reprocess_count = 0; // Consume HTTP/2 client connection preface if present if (cureq.RecvData.size() >= H2_CLIENT_PREFACE_LEN && Loading Loading @@ -1063,7 +1064,10 @@ done: // Only reprocess if we actually made progress (consumed frames or // generated output). Otherwise we'd spin forever on an incomplete // frame that needs more network data to arrive. if ((!out.empty() || off > 0) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { // Guard: limit reprocessing iterations to prevent infinite spin when // frames continuously generate output (e.g. ACKs) without consuming data. if (reprocess_count < 16 && (!out.empty() || off > 0) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { ++reprocess_count; out.clear(); out.reserve(4096); off = 0; Loading Loading @@ -1377,6 +1381,11 @@ void libhttppp::HttpEvent::RequestEvent(netplus::con &curcon,const int tid,ULONG } REQUESTHANDLING: // Guard: only loop for HTTP/1.x; HTTP/2 and HTTP/3 parse() always // returns 1, which would cause an infinite loop here. if (cureq._httpProtocol != 0) { return; } switch(cureq.getRequestType()){ case PARSEREQUEST: if(cureq.parse()!=0) Loading