Commit 563eadca authored by jan.koester's avatar jan.koester
Browse files

test

parent b04ede4e
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -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>
@@ -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;

+258 −204
Original line number Diff line number Diff line
@@ -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);

@@ -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;
        }

@@ -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;
@@ -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);

@@ -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){
+5 −0
Original line number Diff line number Diff line
@@ -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);