Commit 5040517d authored by jan.koester's avatar jan.koester
Browse files

added delete support

parent c1d1d644
Loading
Loading
Loading
Loading
+266 −15
Original line number Diff line number Diff line
@@ -227,9 +227,12 @@ void libhttppp::HttpClient::resetConnection(){
    std::map<std::string, netplus::ssl::CertificateBundle> certs;
    auto sslsock = std::make_unique<netplus::ssl>(certs,-1);

    // Advertise h2 and http/1.1 via ALPN
    // IMPORTANT: For compatibility with servers that may not handle HTTP/2 framing properly,
    // we prefer HTTP/1.1 as default and only enable HTTP/2 if explicitly negotiated.
    // Many servers claim H2 support but have bugs in HTTP/2 frame handling.
    // Set ALPN to prefer http/1.1 over h2
    sslsock->getTls().client_alpn_protocols =
      std::string("\x02h2\x08http/1.1", 12);
      std::string("\x08http/1.1\x02h2", 12);

    _cltsock = std::move(sslsock);
    _cltsock->connect(_url.getHost(), _url.getPort(), false);
@@ -238,7 +241,22 @@ void libhttppp::HttpClient::resetConnection(){
    // Check negotiated ALPN after TLS handshake
    const std::string &alpn = dynamic_cast<netplus::ssl*>(_cltsock.get())->getSelectedAlpn();
    _isH2 = (alpn == "h2");
    std::cerr << "[HttpClient] ALPN negotiated: '" << alpn << "' isH2=" << _isH2 << std::endl;
    std::cerr << "[HttpClient] ALPN negotiated: '" << alpn << "' (length=" << alpn.size() << ")"
              << " isH2=" << _isH2 << std::endl;
    
    // Debug: print ALPN bytes
    if (!alpn.empty()) {
      std::cerr << "[HttpClient] ALPN bytes: ";
      for (unsigned char c : alpn) {
        if (c >= 32 && c < 127) {
          std::cerr << c;
        } else {
          std::cerr << "[" << std::hex << std::setw(2) << std::setfill('0') 
                    << (int)c << std::dec << "]";
        }
      }
      std::cerr << std::endl;
    }
  } else {
    _cltsock = std::make_unique<netplus::tcp>(-1);
    _cltsock->connect(_url.getHost(), _url.getPort(), false);
@@ -891,6 +909,195 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq)
    }
}

const std::vector<char> libhttppp::HttpClient::Delete(libhttppp::HttpRequest &nreq)
{
    // Delete() is identical to Get() except it uses DELETEREQUEST
    std::vector<char> ret;

    try {
        resetConnection();

        std::stringstream host;
        host << _url.getHost() << ":" << _url.getPort();

        nreq.setHeaderData("host")->push_back(host.str());
        nreq.setRequestType(DELETEREQUEST);

        if (nreq.getRequestURL().empty())
            nreq.setRequestURL(_url.getPath());

        // HTTP/2: delegate to binary framing path
        if (_isH2) {
            return _h2Request("DELETE", nreq);
        }

        if (nreq.getRequestVersion().empty())
            nreq.setRequestVersion(HTTPVERSION(1.1));

        nreq.send(_url, _cltsock);

        auto recv_blocking = [&](netplus::buffer& b) -> size_t {
            for (;;) {
                try {
                    size_t n = _cltsock->recvData(b, 0);
                    return n;
                } catch (netplus::NetException& e) {
                    if (e.getErrorType() == netplus::NetException::Note) {
                        sleep_note();
                        continue;
                    }
                    throw;
                }
            }
        };

        std::vector<char> raw;
        raw.reserve(8192);

        size_t header_end = std::string::npos;

        for (;;) {
            if (raw.size() >= 4) {
                for (size_t i = 0; i + 3 < raw.size(); ++i) {
                    if (raw[i] == '\r' && raw[i+1] == '\n' &&
                        raw[i+2] == '\r' && raw[i+3] == '\n')
                    {
                        header_end = i + 4;
                        break;
                    }
                }
            }
            if (header_end != std::string::npos)
                break;

            netplus::buffer buf(BLOCKSIZE);
            size_t n = recv_blocking(buf);
            if (n == 0) {
                netplus::NetException ne;
                ne[netplus::NetException::Error] << "HTTP: EOF while reading header";
                throw ne;
            }
            raw.insert(raw.end(), buf.data.buf, buf.data.buf + n);
        }

        libhttppp::HttpResponse res;
        size_t parsed_hsize = res.parse(raw.data(), raw.size());

        if (parsed_hsize == 0 || parsed_hsize > raw.size()) {
            libhttppp::HTTPException he;
            he[libhttppp::HTTPException::Error] << "HTTP: response header parse failed";
            throw he;
        }

        size_t body_off = parsed_hsize;

        bool chunked = false;
        ptrdiff_t content_len = -1;

        try {
            for (libhttppp::HttpHeader::HeaderData::Values *v = res.getTransferEncoding();
                 v; v = v->nextvalue())
            {
                std::string val = tolower_copy(v->getvalue());
                if (val == "chunked") {
                    chunked = true;
                    break;
                }
            }
        } catch (...) {}

        if (!chunked) {
            try {
                content_len = res.getContentLength();
                if (content_len < 0) content_len = -1;
            } catch (...) {
                content_len = -1;
            }
        }

        auto append_from_raw = [&](size_t& take_pos, size_t take_len) {
            if (take_len == 0) return;
            ret.insert(ret.end(), raw.begin() + (ptrdiff_t)take_pos,
                                raw.begin() + (ptrdiff_t)(take_pos + take_len));
            take_pos += take_len;
        };

        if (!chunked && content_len >= 0) {
            size_t remaining = (size_t)content_len;
            size_t pos = body_off;
            size_t can = (std::min)(remaining, raw.size() - pos);
            append_from_raw(pos, can);
            remaining -= can;

            while (remaining > 0) {
                netplus::buffer buf(BLOCKSIZE);
                size_t n = recv_blocking(buf);
                if (n == 0) {
                    netplus::NetException ne;
                    ne[netplus::NetException::Error] << "HTTP: EOF before Content-Length complete";
                    throw ne;
                }
                size_t take = (std::min)(remaining, n);
                ret.insert(ret.end(), buf.data.buf, buf.data.buf + take);
                remaining -= take;
            }
            return ret;
        }

        if (chunked) {
            size_t pos = body_off;
            auto ensure_bytes = [&](size_t need) {
                while (raw.size() - pos < need) {
                    netplus::buffer buf(BLOCKSIZE);
                    size_t n = recv_blocking(buf);
                    if (n == 0) break;
                    raw.insert(raw.end(), buf.data.buf, buf.data.buf + n);
                }
            };
            for (;;) {
                ensure_bytes(3);
                size_t line_end = std::string::npos;
                for (size_t i = pos; i + 1 < raw.size(); ++i) {
                    if (raw[i] == '\r' && raw[i+1] == '\n') {
                        line_end = i;
                        break;
                    }
                }
                if (line_end == std::string::npos) break;
                std::string hex(raw.begin() + pos, raw.begin() + line_end);
                pos = line_end + 2;
                size_t chunk_sz = 0;
                try { chunk_sz = std::stoull(hex, nullptr, 16); } catch(...) { break; }
                if (chunk_sz == 0) break;
                ensure_bytes(chunk_sz + 2);
                ret.insert(ret.end(), raw.begin() + pos, raw.begin() + pos + chunk_sz);
                pos += chunk_sz + 2;
            }
            return ret;
        }

        // read-until-close fallback
        {
            size_t pos = body_off;
            if (pos < raw.size())
                ret.insert(ret.end(), raw.begin() + pos, raw.end());
            for (;;) {
                netplus::buffer buf(BLOCKSIZE);
                size_t n = 0;
                try { n = recv_blocking(buf); } catch (...) { break; }
                if (n == 0) break;
                ret.insert(ret.end(), buf.data.buf, buf.data.buf + n);
            }
        }
        return ret;

    } catch (netplus::NetException &e) {
        libhttppp::HTTPException ee;
        ee[libhttppp::HTTPException::Error] << e.what();
        throw ee;
    }
}

const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq,
                                                    const std::vector<char> &post)
{
@@ -1894,12 +2101,42 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) {
        ltrim_inplace(s);
    };

    // --- 1) find end of header: "\r\n\r\n" ---
    // --- 1) Detect HTTP/2 frames (not HTTP/1.1 text headers) ---
    // HTTP/2 frames have structure: 3-byte length, 1-byte type, 1-byte flags, 4-byte stream ID
    // First byte of length would typically be 0x00, 0x00 for SETTINGS frames
    // If we see patterns like [0x00 0x00 xx | frame_type] where frame_type is 0x00-0x0A,
    // we're likely dealing with HTTP/2.
    if (inlen >= 9) {
        uint8_t b0 = (uint8_t)data[0];
        uint8_t b1 = (uint8_t)data[1];
        uint8_t b2 = (uint8_t)data[2];
        uint8_t frame_type = (uint8_t)data[3];
        
        // Heuristic: HTTP/2 frames typically start with length bytes that would be
        // invalid at the start of "HTTP/x.x"
        // If first 4 bytes look like frame header (small length + valid frame type),
        // this is likely HTTP/2 being received when HTTP/1.1 was expected.
        bool looks_like_h2_frame = (b0 <= 0x40 && b1 == 0x00); // length <= 16KB with b1=0
        bool valid_frame_type = (frame_type <= 0x0A); // SETTINGS(0x04), HEADERS(0x01), etc.
        
        if (looks_like_h2_frame && valid_frame_type) {
            // Log diagnostic info and throw with clearer message
            std::cerr << "[HttpResponse::parse] Detected HTTP/2 frame when expecting HTTP/1.1" << std::endl;
            std::cerr << "[HttpResponse::parse] Frame bytes: " 
                      << std::hex << std::setw(2) << std::setfill('0')
                      << (int)b0 << " " << (int)b1 << " " << (int)b2 << " " << (int)frame_type 
                      << std::dec << std::endl;
            excep[HTTPException::Error] << "HTTP/2 frame received but HTTP/1.1 parser active - ALPN negotiation issue";
            throw excep;
        }
    }

    // --- 2) find end of header: "\r\n\r\n" ---
    const char* header_end_marker = "\r\n\r\n";
    const char* end_ptr = data + inlen - 4;
    const char* header_end_pos = nullptr;

    for (const char* p = data; p < end_ptr; ++p) {
    for (const char* p = data; p <= end_ptr; ++p) {
        if (std::equal(p, p + 4, header_end_marker)) {
            header_end_pos = p;
            break;
@@ -1907,6 +2144,15 @@ size_t libhttppp::HttpResponse::parse(const char *data, size_t inlen) {
    }

    if (!header_end_pos) {
        // Give more diagnostic information when header parsing fails
        std::cerr << "[HttpResponse::parse] HTTP/1.1 header terminator not found in "
                  << inlen << " bytes" << std::endl;
        std::cerr << "[HttpResponse::parse] First 64 bytes (hex): ";
        for (size_t i = 0; i < std::min(inlen, (size_t)64); ++i) {
            std::cerr << std::hex << std::setw(2) << std::setfill('0') 
                      << (int)(uint8_t)data[i] << " ";
        }
        std::cerr << std::dec << std::endl;
        excep[HTTPException::Error] << "HttpHeader end not found!";
        throw excep;
    }
@@ -2162,6 +2408,7 @@ size_t libhttppp::HttpRequest::parseH2(const std::vector<hpack::HeaderField> &he
  std::string method = (methodHd && methodHd->getfirstValue()) ? methodHd->getfirstValue()->getvalue() : "GET";
  if (method == "GET")          _RequestType = GETREQUEST;
  else if (method == "POST")    _RequestType = POSTREQUEST;
  else if (method == "DELETE")  _RequestType = DELETEREQUEST;
  else                          _RequestType = GETREQUEST;

  auto *pathHd = getHeaderData(":path");
@@ -2228,6 +2475,7 @@ size_t libhttppp::HttpRequest::parseH3(const std::vector<qpack::HeaderField> &he
  std::string method = (methodHd && methodHd->getfirstValue()) ? methodHd->getfirstValue()->getvalue() : "GET";
  if (method == "GET")          _RequestType = GETREQUEST;
  else if (method == "POST")    _RequestType = POSTREQUEST;
  else if (method == "DELETE")  _RequestType = DELETEREQUEST;
  else                          _RequestType = GETREQUEST;

  auto *pathHd = getHeaderData(":path");
@@ -2314,6 +2562,7 @@ size_t libhttppp::HttpRequest::parseH1() {
    // 5) Set request type
    if (method == "GET")          _RequestType = GETREQUEST;
    else if (method == "POST")    _RequestType = POSTREQUEST;
    else if (method == "DELETE")  _RequestType = DELETEREQUEST;
    else                          _RequestType = PARSEREQUEST; // or a dedicated enum for others

    // 6) Store pseudo-headers in _firstHeaderData (unified storage)
@@ -2441,6 +2690,8 @@ void libhttppp::HttpRequest::printHeader(std::string &buffer){
    buffer="GET ";
  else if(_RequestType==POSTREQUEST)
    buffer="POST ";
  else if(_RequestType==DELETEREQUEST)
    buffer="DELETE ";

  buffer.append(_cachedRequest);
  buffer.append(" ");
@@ -2573,7 +2824,7 @@ void libhttppp::HttpRequest::send(const HttpUrl &dest,
}

void libhttppp::HttpRequest::setRequestType(int req){
  if(req == POSTREQUEST || req == GETREQUEST){
  if(req == POSTREQUEST || req == GETREQUEST || req == DELETEREQUEST){
    _RequestType=req;
    return;
  }
+1 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ namespace libhttppp {
      void reconnect();
      const std::vector<char> Get(HttpRequest &nreq);
      const std::vector<char> Post(HttpRequest &nreq,const std::vector<char> &post);
      const std::vector<char> Delete(HttpRequest &nreq);
  private:
      void resetConnection();
      int readchunk(const char *data,int datasize,int &pos);
+4 −3
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define PARSEREQUEST  0
#define GETREQUEST    1
#define POSTREQUEST   2
#define DELETEREQUEST 3

//define http version