Commit 2419ccdb authored by jan.koester's avatar jan.koester
Browse files

TEST

parent 001b347b
Loading
Loading
Loading
Loading
+9 −2
Changes for src/hpack.cpp: 9 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -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
+6 −0
Changes for src/hpack.h: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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;
+88 −40
Changes for src/http.cpp: 88 added lines, 40 removed lines.
Original line number Diff line number Diff line
@@ -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;
                }
@@ -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>();
@@ -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();
@@ -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;
                }

@@ -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();
@@ -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") {
@@ -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){