#include "http_head.h" #include #include #include namespace roro::release { namespace { std::string lower(std::string s) { for (char& c : s) c = static_cast(std::tolower(static_cast(c))); return s; } } // namespace size_t HttpHeadParser::feed(const char* data, size_t len) { size_t used = 0; while (used < len && !complete_ && !failed_) { char c = data[used++]; total_++; if (total_ > kMaxBytes) { failed_ = true; break; } if (c == '\n') { if (!line_.empty() && line_.back() == '\r') line_.pop_back(); line(); line_.clear(); } else { line_ += c; } } return used; } void HttpHeadParser::line() { if (first_) { // "HTTP/1.1 200 OK" first_ = false; if (line_.compare(0, 5, "HTTP/") != 0) { failed_ = true; return; } size_t space = line_.find(' '); head_.status = space == std::string::npos ? 0 : std::atoi(line_.c_str() + space + 1); if (head_.status < 100 || head_.status > 599) failed_ = true; return; } if (line_.empty()) { complete_ = true; return; } size_t colon = line_.find(':'); if (colon == std::string::npos) return; // not a header: ignored std::string name = lower(line_.substr(0, colon)); size_t from = line_.find_first_not_of(" \t", colon + 1); std::string value = from == std::string::npos ? "" : line_.substr(from); if (name == "content-length") head_.contentLength = std::atol(value.c_str()); else if (name == "transfer-encoding") head_.chunked = lower(value).find("chunked") != std::string::npos; else if (name == "location") head_.location = value; else if (name == "content-type") head_.contentType = value; } size_t ChunkedDecoder::decode(const uint8_t* in, size_t len, uint8_t* out) { size_t written = 0; for (size_t i = 0; i < len && !failed_ && state_ != State::Done; i++) { uint8_t c = in[i]; switch (state_) { case State::Size: { int digit = c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1; if (digit >= 0) { if (remaining_ > (SIZE_MAX >> 5)) failed_ = true; remaining_ = remaining_ * 16 + digit; anyDigit_ = true; } else if (c == ';' && anyDigit_) { state_ = State::Extension; } else if (c == '\r' && anyDigit_) { state_ = State::SizeLf; } else { failed_ = true; } break; } case State::Extension: if (c == '\r') state_ = State::SizeLf; break; case State::SizeLf: if (c != '\n') { failed_ = true; } else if (remaining_ == 0) { state_ = State::Trailer; trailerLine_ = 0; } else { state_ = State::Data; } break; case State::Data: { size_t take = std::min(remaining_, len - i); for (size_t k = 0; k < take; k++) out[written + k] = in[i + k]; written += take; remaining_ -= take; i += take - 1; if (remaining_ == 0) state_ = State::DataCr; break; } case State::DataCr: if (c != '\r') failed_ = true; else state_ = State::DataLf; break; case State::DataLf: if (c != '\n') { failed_ = true; } else { state_ = State::Size; anyDigit_ = false; } break; case State::Trailer: // header lines after the last chunk, until an empty one if (c == '\n') { if (trailerLine_ == 0) state_ = State::Done; trailerLine_ = 0; } else if (c != '\r') { trailerLine_++; } break; case State::Done: break; } } return written; } Url parseUrl(const std::string& url) { Url u; size_t scheme = url.find("://"); if (scheme == std::string::npos) return u; std::string s = lower(url.substr(0, scheme)); if (s != "http" && s != "https") return u; u.https = s == "https"; size_t hostStart = scheme + 3, pathStart = url.find('/', hostStart); std::string authority = url.substr(hostStart, pathStart == std::string::npos ? std::string::npos : pathStart - hostStart); u.path = pathStart == std::string::npos ? "/" : url.substr(pathStart); if (authority.empty() || authority.find('@') != std::string::npos) return u; // no credentials in a URL size_t colon = authority.rfind(':'); u.port = u.https ? 443 : 80; if (colon != std::string::npos) { u.port = std::atoi(authority.c_str() + colon + 1); authority.resize(colon); if (u.port <= 0 || u.port > 65535) return u; } for (unsigned char c : authority) if (!(std::isalnum(c) || c == '.' || c == '-')) return u; for (unsigned char c : u.path) if (c <= ' ' || c == 0x7F) return u; u.host = lower(authority); u.ok = !u.host.empty(); return u; } } // namespace roro::release