Commit b8e526b4 authored by jan.koester's avatar jan.koester
Browse files

bugfixes

parent 8c868848
Loading
Loading
Loading
Loading
+290 −3
Original line number Diff line number Diff line
@@ -90,7 +90,6 @@ libhttppp::HttpUrl::HttpUrl(const std::string& url, bool http3) {
        exp[HTTPException::Error] << "Httpurl: protocol not supported!";
        throw exp;
    }

    _path = "/";

    const char* host_start = p;
@@ -216,6 +215,20 @@ libhttppp::HttpClient::HttpClient(const HttpUrl& desturl)
    }
}

bool libhttppp::HttpClient::tryHttp3First(){
  try {
    _cltsock = std::make_unique<netplus::quic>();
    _cltsock->connect(_url.getHost(), _url.getPort(), false);
    _cltsock->setNonBlock();
    _isH2 = false;
    return true;
  } catch (netplus::NetException&) {
    _cltsock.reset();
    _isH2 = false;
    return false;
  }
}

void libhttppp::HttpClient::resetConnection(){
  if (_url.getProtocol() == HttpUrl::HTTP3) {
    _cltsock = std::make_unique<netplus::quic>();
@@ -223,6 +236,9 @@ void libhttppp::HttpClient::resetConnection(){
    _cltsock->setNonBlock();
    _isH2 = false;
  } else if (_url.getProtocol() == HttpUrl::HTTPS) {
    if (tryHttp3First()) {
      return;
    }
    std::map<std::string, netplus::ssl::CertificateBundle> certs;
    auto sslsock = std::make_unique<netplus::ssl>(certs,-1);

@@ -249,7 +265,13 @@ void libhttppp::HttpClient::resetConnection(){
void libhttppp::HttpClient::reconnect(){
  if (_url.getProtocol() == HttpUrl::HTTP3) {
    _cltsock = std::make_unique<netplus::quic>();
    _cltsock->connect(_url.getHost(), _url.getPort(), false);
    _cltsock->setNonBlock();
    _isH2 = false;
  } else if(_url.getProtocol() == HttpUrl::HTTPS) {
    if (tryHttp3First()) {
      return;
    }
    std::map<std::string, netplus::ssl::CertificateBundle> certs;
    auto sslsock = std::make_unique<netplus::ssl>(certs,-1);

@@ -258,11 +280,17 @@ void libhttppp::HttpClient::reconnect(){
      std::string("\x02h2\x08http/1.1", 12);

    _cltsock = std::move(sslsock);
    _cltsock->connect(_url.getHost(), _url.getPort(), false);
    _cltsock->setNonBlock();

    const std::string &alpn = dynamic_cast<netplus::ssl*>(_cltsock.get())->getSelectedAlpn();
    _isH2 = (alpn == "h2");
  } else {
    _cltsock = std::make_unique<netplus::tcp>(-1);
  }
  _cltsock->connect(_url.getHost().c_str(), _url.getPort());
    _cltsock->connect(_url.getHost(), _url.getPort(), false);
    _cltsock->setNonBlock();
    _isH2 = false;
  }
}

static inline std::string tolower_copy(std::string s) {
@@ -300,6 +328,76 @@ static std::string normalize_path(const std::string &location, const std::string
  return base.substr(0, slash + 1) + location;
}

  // --------------- HTTP/3 client frame helpers ---------------
  static size_t h3EncodeVarInt(uint64_t value, uint8_t *out) {
    if (value < 64) {
      out[0] = static_cast<uint8_t>(value);
      return 1;
    }
    if (value < 16384) {
      out[0] = static_cast<uint8_t>(0x40 | (value >> 8));
      out[1] = static_cast<uint8_t>(value & 0xff);
      return 2;
    }
    if (value < 1073741824) {
      out[0] = static_cast<uint8_t>(0x80 | (value >> 24));
      out[1] = static_cast<uint8_t>((value >> 16) & 0xff);
      out[2] = static_cast<uint8_t>((value >> 8) & 0xff);
      out[3] = static_cast<uint8_t>(value & 0xff);
      return 4;
    }
    out[0] = static_cast<uint8_t>(0xc0 | (value >> 56));
    out[1] = static_cast<uint8_t>((value >> 48) & 0xff);
    out[2] = static_cast<uint8_t>((value >> 40) & 0xff);
    out[3] = static_cast<uint8_t>((value >> 32) & 0xff);
    out[4] = static_cast<uint8_t>((value >> 24) & 0xff);
    out[5] = static_cast<uint8_t>((value >> 16) & 0xff);
    out[6] = static_cast<uint8_t>((value >> 8) & 0xff);
    out[7] = static_cast<uint8_t>(value & 0xff);
    return 8;
  }

  static uint64_t h3DecodeVarInt(const uint8_t *data, size_t len, size_t &bytes_read) {
    if (len == 0) {
      bytes_read = 0;
      return 0;
    }
    uint8_t prefix = data[0] >> 6;
    uint64_t value = data[0] & 0x3f;
    if (prefix == 0) {
      bytes_read = 1;
      return value;
    }
    if (prefix == 1) {
      if (len < 2) { bytes_read = 0; return 0; }
      bytes_read = 2;
      return (value << 8) | data[1];
    }
    if (prefix == 2) {
      if (len < 4) { bytes_read = 0; return 0; }
      bytes_read = 4;
      value = (value << 8) | data[1];
      value = (value << 8) | data[2];
      value = (value << 8) | data[3];
      return value;
    }
    if (len < 8) { bytes_read = 0; return 0; }
    bytes_read = 8;
    for (int i = 1; i < 8; ++i) {
      value = (value << 8) | data[i];
    }
    return value;
  }

  static void h3AppendFrame(std::vector<uint8_t> &out, uint64_t type, const std::vector<uint8_t> &payload) {
    uint8_t buf[8];
    size_t tlen = h3EncodeVarInt(type, buf);
    out.insert(out.end(), buf, buf + tlen);
    size_t plen = h3EncodeVarInt(payload.size(), buf);
    out.insert(out.end(), buf, buf + plen);
    out.insert(out.end(), payload.begin(), payload.end());
  }

// --------------- HTTP/2 client frame helpers ---------------
static constexpr uint8_t H2C_FRAME_DATA         = 0x00;
static constexpr uint8_t H2C_FRAME_HEADERS      = 0x01;
@@ -565,6 +663,179 @@ const std::vector<char> libhttppp::HttpClient::_h2Request(
    }
}

  // Generic HTTP/3 client request (GET/POST/PUT/DELETE)
  const std::vector<char> libhttppp::HttpClient::_h3Request(
    const std::string &method,
    HttpRequest &nreq,
    const std::vector<char> *postBody)
  {
    std::vector<char> ret;

    auto *q = dynamic_cast<netplus::quic*>(_cltsock.get());
    if (!q) {
      HTTPException ee;
      ee[HTTPException::Error] << "HTTP/3 requested on non-QUIC socket";
      throw ee;
    }

    const int kMaxRedirects = 1;
    int redirects = 0;

    for (;;) {
      std::string path = nreq.getRequestURL();
      if (path.empty()) path = _url.getPath();

      std::string scheme = (_url.getProtocol() == HttpUrl::HTTP) ? "http" : "https";

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

      bool include_content_length = false;
      size_t content_length = 0;

      std::vector<qpack::HeaderField> extra;
      for (HttpHeader::HeaderData *hd = nreq.getfirstHeaderData(); hd; hd = hd->nextHeaderData()) {
        const std::string &key = hd->getkey();
        if (key.empty() || key[0] == ':') continue;
        if (key == "host" || key == "connection") continue;

        for (HttpHeader::HeaderData::Values *v = hd->getfirstValue(); v; v = v->nextvalue()) {
          if (key == "content-length") {
            include_content_length = true;
            content_length = static_cast<size_t>(std::stoul(v->getvalue()));
          } else {
            extra.push_back({key, v->getvalue(), false});
          }
        }
      }

      if (postBody) {
        include_content_length = true;
        content_length = postBody->size();
      }

      std::vector<uint8_t> headers = qpack::Encoder::encodeRequestHeaders(
        method, scheme, authority.str(), path, extra, include_content_length, content_length);

      std::vector<uint8_t> stream_payload;
      h3AppendFrame(stream_payload, 0x01, headers);

      if (postBody && !postBody->empty()) {
        std::vector<uint8_t> body(postBody->begin(), postBody->end());
        h3AppendFrame(stream_payload, 0x00, body);
      }

      uint64_t stream_id = q->openStream(true);
      q->sendStreamData(stream_id, stream_payload, true);

      std::vector<uint8_t> raw;
      std::vector<char> body;
      bool got_headers = false;
      size_t expected_body = static_cast<size_t>(-1);
      int status_code = 0;
      std::string location;

      auto last_data = std::chrono::steady_clock::now();
      const auto idle_timeout = std::chrono::seconds(15);

      for (;;) {
        uint8_t buf[4096];
        size_t n = q->recvStreamData(stream_id, buf, sizeof(buf));
        if (n > 0) {
          raw.insert(raw.end(), buf, buf + n);
          last_data = std::chrono::steady_clock::now();
        } else {
          try {
            netplus::buffer rb(65535);
            q->recvData(rb, MSG_DONTWAIT);
          } catch (netplus::NetException &e) {
            if (e.getErrorType() != netplus::NetException::Note) {
              HTTPException ee;
              ee[HTTPException::Error] << e.what();
              throw ee;
            }
          }
        }

        size_t pos = 0;
        while (pos < raw.size()) {
          size_t bytes = 0;
          uint64_t frame_type = h3DecodeVarInt(raw.data() + pos, raw.size() - pos, bytes);
          if (bytes == 0) break;
          pos += bytes;
          if (pos >= raw.size()) break;

          uint64_t frame_len = h3DecodeVarInt(raw.data() + pos, raw.size() - pos, bytes);
          if (bytes == 0) break;
          pos += bytes;
          if (pos + frame_len > raw.size()) break;

          if (frame_type == 0x01) {
            auto decoded = qpack::Decoder::decode(raw.data() + pos, frame_len);
            for (const auto &h : decoded) {
              if (h.name == ":status") {
                try { status_code = std::stoi(h.value); } catch (...) { status_code = 0; }
              } else if (h.name == "location") {
                location = h.value;
              } else if (h.name == "content-length") {
                try { expected_body = static_cast<size_t>(std::stoul(h.value)); } catch (...) {}
              }
            }
            got_headers = true;
          } else if (frame_type == 0x00) {
            body.insert(body.end(), raw.begin() + static_cast<ptrdiff_t>(pos),
                  raw.begin() + static_cast<ptrdiff_t>(pos + frame_len));
          }

          pos += frame_len;
        }

        if (pos > 0) {
          raw.erase(raw.begin(), raw.begin() + static_cast<ptrdiff_t>(pos));
        }

        if (got_headers && expected_body != static_cast<size_t>(-1) && body.size() >= expected_body) {
          ret = std::move(body);
          break;
        }

        if (got_headers && expected_body == static_cast<size_t>(-1) &&
          q->isStreamComplete(stream_id) && raw.empty()) {
          ret = std::move(body);
          break;
        }

        if (std::chrono::steady_clock::now() - last_data > idle_timeout) {
          HTTPException ee;
          ee[HTTPException::Error] << "HTTP/3 response timeout";
          throw ee;
        }

        sleep_note();
      }

      if (is_redirect_status(status_code) && redirects < kMaxRedirects && !location.empty()) {
        ++redirects;
        if (location.rfind("http://", 0) == 0 || location.rfind("https://", 0) == 0) {
          _url = HttpUrl(location, false);
          nreq.setRequestURL(_url.getPath());
        } else {
          std::string base = nreq.getRequestURL().empty() ? _url.getPath() : nreq.getRequestURL();
          nreq.setRequestURL(normalize_path(location, base));
        }

        resetConnection();
        q = dynamic_cast<netplus::quic*>(_cltsock.get());
        if (!q) {
          return ret;
        }
        continue;
      }

      return ret;
    }
  }

const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq)
{
    std::vector<char> ret;
@@ -587,6 +858,10 @@ const std::vector<char> libhttppp::HttpClient::Get(libhttppp::HttpRequest &nreq)
            if (nreq.getRequestURL().empty())
                nreq.setRequestURL(_url.getPath());

            if (dynamic_cast<netplus::quic*>(_cltsock.get())) {
              return _h3Request("GET", nreq);
            }

            // HTTP/2: delegate to binary framing path
            if (_isH2) {
                return _h2Request("GET", nreq);
@@ -913,6 +1188,10 @@ const std::vector<char> libhttppp::HttpClient::Post(libhttppp::HttpRequest &nreq
        // Always send content-length for POST body
        nreq.setHeaderData("content-length")->push_back(std::to_string(post.size()));

        if (dynamic_cast<netplus::quic*>(_cltsock.get())) {
          return _h3Request("POST", nreq, &post);
        }

        // HTTP/2: delegate to binary framing path
        if (_isH2) {
          return _h2Request("POST", nreq, &post);
@@ -1250,6 +1529,10 @@ const std::vector<char> libhttppp::HttpClient::Delete(libhttppp::HttpRequest &nr
            if (nreq.getRequestURL().empty())
                nreq.setRequestURL(_url.getPath());

            if (dynamic_cast<netplus::quic*>(_cltsock.get())) {
              return _h3Request("DELETE", nreq);
            }

            // HTTP/2: delegate to binary framing path
            if (_isH2) {
                return _h2Request("DELETE", nreq);
@@ -1549,6 +1832,10 @@ const std::vector<char> libhttppp::HttpClient::Put(libhttppp::HttpRequest &nreq,

        nreq.setHeaderData("content-length")->push_back(std::to_string(put.size()));

        if (dynamic_cast<netplus::quic*>(_cltsock.get())) {
          return _h3Request("PUT", nreq, &put);
        }

        if (_isH2) {
          return _h2Request("PUT", nreq, &put);
        }
+4 −0
Original line number Diff line number Diff line
@@ -93,6 +93,7 @@ namespace libhttppp {
      const std::vector<char> Delete(HttpRequest &nreq);
  private:
      void resetConnection();
      bool tryHttp3First();
      int readchunk(const char *data,int datasize,int &pos);

      // HTTP/2 client helpers
@@ -100,6 +101,9 @@ namespace libhttppp {
      const std::vector<char> _h2Request(const std::string &method,
                                          HttpRequest &nreq,
                                          const std::vector<char> *postBody = nullptr);
      const std::vector<char> _h3Request(const std::string &method,
                  HttpRequest &nreq,
                  const std::vector<char> *postBody = nullptr);
  private:
      HttpUrl _url;
      std::unique_ptr<netplus::socket> _cltsock;
+53 −0
Original line number Diff line number Diff line
@@ -336,4 +336,57 @@ std::vector<uint8_t> Encoder::encodeResponseHeaders(uint16_t status_code,
    return out;
}

std::vector<uint8_t> Encoder::encodeRequestHeaders(const std::string &method,
                                                   const std::string &scheme,
                                                   const std::string &authority,
                                                   const std::string &path,
                                                   const std::vector<HeaderField> &extra,
                                                   bool include_content_length,
                                                   size_t content_length) {
    std::vector<uint8_t> out;

    // Section prefix: Required Insert Count = 0, S=0, Delta Base = 0
    out.push_back(0x00);
    out.push_back(0x00);

    // :method
    if (method == "GET")      encodeIndexedStatic(out, 17);
    else if (method == "POST") encodeIndexedStatic(out, 20);
    else if (method == "PUT")  encodeIndexedStatic(out, 21);
    else if (method == "DELETE") encodeIndexedStatic(out, 16);
    else {
        // :method name reference at index 15
        encodeLiteralStaticNameRef(out, 15, method);
    }

    // :scheme
    if (scheme == "https") encodeIndexedStatic(out, 23);
    else encodeIndexedStatic(out, 22);

    // :path (name index 1)
    if (path == "/") {
        encodeIndexedStatic(out, 1);
    } else {
        encodeLiteralStaticNameRef(out, 1, path);
    }

    // :authority (name index 0)
    if (!authority.empty()) {
        encodeLiteralStaticNameRef(out, 0, authority);
    }

    if (include_content_length) {
        encodeLiteralStaticNameRef(out, 4, std::to_string(content_length));
    }

    for (const auto &h : extra) {
        if (h.name.empty()) {
            continue;
        }
        encodeLiteralName(out, h.name, h.value);
    }

    return out;
}

} // namespace libhttppp::qpack
+7 −0
Original line number Diff line number Diff line
@@ -46,6 +46,13 @@ public:
                                                      const std::string &content_type,
                                                      size_t content_length,
                                                      const std::vector<HeaderField> &extra = {});
    static std::vector<uint8_t> encodeRequestHeaders(const std::string &method,
                                                     const std::string &scheme,
                                                     const std::string &authority,
                                                     const std::string &path,
                                                     const std::vector<HeaderField> &extra = {},
                                                     bool include_content_length = false,
                                                     size_t content_length = 0);

private:
    static void encodeInt(std::vector<uint8_t> &out, uint8_t prefix_byte,