#pragma once #include #include #include namespace roro::release { // The status line and headers of an HTTP/1.1 response, read as bytes arrive. struct HttpHead { int status = 0; long contentLength = -1; // -1: not given bool chunked = false; std::string location, contentType; }; class HttpHeadParser { public: static constexpr size_t kMaxBytes = 4096; // Takes bytes up to and including the blank line that ends the head; returns how many it used. // What follows is the body. size_t feed(const char* data, size_t len); bool complete() const { return complete_; } bool failed() const { return failed_; } const HttpHead& head() const { return head_; } private: void line(); HttpHead head_; std::string line_; size_t total_ = 0; bool first_ = true, complete_ = false, failed_ = false; }; // Takes the chunks of "Transfer-Encoding: chunked" apart as they arrive. class ChunkedDecoder { public: // `out` has room for `len` bytes (the body is never longer than what carried it). size_t decode(const uint8_t* in, size_t len, uint8_t* out); bool done() const { return state_ == State::Done; } bool failed() const { return failed_; } private: enum class State : uint8_t { Size, Extension, SizeLf, Data, DataCr, DataLf, Trailer, Done }; State state_ = State::Size; size_t remaining_ = 0; bool anyDigit_ = false, failed_ = false; size_t trailerLine_ = 0; }; // "https://git.twis.la/twisla/x/releases/download/v1/a.ota" taken apart. Only http and https. struct Url { bool ok = false; bool https = false; std::string host, path; // path from the first "/", with its query; "/" if none int port = 0; }; Url parseUrl(const std::string& url); } // namespace roro::release