25constexpr size_t MaxHeaderBytes = 16 * 1024;
28 unsigned int status_code;
29 std::string status_message;
33Parsed_Head parse_status_and_headers(std::string_view block) {
34 const auto first_eol = block.find(
"\r\n");
35 const auto status_line_end = (first_eol == std::string_view::npos) ? block.size() : first_eol;
36 if(status_line_end == 0) {
40 std::stringstream ss{std::string(block.substr(0, status_line_end))};
41 std::string http_version;
42 unsigned int status_code = 0;
43 ss >> http_version >> status_code;
44 std::string status_message;
45 std::getline(ss, status_message);
46 if(!status_message.empty() && status_message.front() ==
' ') {
47 status_message.erase(0, 1);
50 if(!ss || !http_version.starts_with(
"HTTP/")) {
55 if(status_code < 100 || status_code > 599) {
56 throw HTTP_Error(
fmt(
"Invalid HTTP status code {}", status_code));
62 constexpr auto is_ows = [](
char c) {
return c ==
' ' || c ==
'\t'; };
65 size_t pos = (first_eol == std::string_view::npos) ? block.size() : first_eol + 2;
66 while(pos < block.size()) {
67 const auto eol = block.find(
"\r\n", pos);
68 const auto line_end = (eol == std::string_view::npos) ? block.size() : eol;
69 const auto line = block.substr(pos, line_end - pos);
72 const auto sep = line.find(
':');
73 if(sep == std::string_view::npos || sep == 0) {
77 const auto name = line.substr(0, sep);
78 if(!std::all_of(name.begin(), name.end(), is_tchar)) {
82 auto value = line.substr(sep + 1);
83 while(!value.empty() && is_ows(value.front())) {
84 value.remove_prefix(1);
86 while(!value.empty() && is_ows(value.back())) {
87 value.remove_suffix(1);
90 auto [it, inserted] = headers.emplace(std::string(name), std::string(value));
92 throw HTTP_Error(
fmt(
"Duplicate HTTP header '{}'", it->first));
95 if(eol == std::string_view::npos) {
101 return {status_code, std::move(status_message), std::move(headers)};
110std::optional<size_t> validate_response_headers(
const Headers& headers, std::optional<size_t> max_body_size) {
113 if(headers.contains(
"Transfer-Encoding")) {
114 throw HTTP_Error(
"Server sent Transfer-Encoding header in response to HTTP/1.0 request");
117 std::optional<size_t> content_length;
118 if(
auto it = headers.find(
"Content-Length"); it != headers.end()) {
120 if(
const auto cl =
parse_sz(it->second)) {
123 throw HTTP_Error(
fmt(
"Invalid Content-Length value '{}'", it->second));
127 if(content_length && max_body_size && *content_length > *max_body_size) {
128 throw HTTP_Error(
fmt(
"Content-Length {} exceeds maximum body size {}", *content_length, *max_body_size));
131 return content_length;
139Response http_transact(std::string_view hostname,
140 std::string_view service,
141 std::string_view message,
142 std::chrono::milliseconds timeout,
143 std::optional<size_t> max_body_size) {
144 std::unique_ptr<OS::Socket> socket;
150 }
catch(std::exception& e) {
151 throw HTTP_Error(
fmt(
"HTTP connection to {} failed: {}", hostname, e.what()));
158void check_no_crlf_nul(std::string_view field, std::string_view value) {
159 for(
const char c : value) {
160 if(c ==
'\r' || c ==
'\n' || c ==
'\0') {
161 throw HTTP_Error(
fmt(
"Invalid character in HTTP {}", field));
173std::optional<URI> resolve_location(
const URI& base, std::string_view location) {
177 if(location.starts_with(
"/") && !location.starts_with(
"//")) {
179 if(!raw_authority.has_value()) {
182 const std::string composed = base.
scheme() +
"://" + std::string(*raw_authority) + std::string(location);
191 std::chrono::milliseconds timeout,
192 std::optional<size_t> max_body_size) {
193 const auto start_time = std::chrono::system_clock::now();
194 const auto deadline_exceeded = [&] {
return std::chrono::system_clock::now() - start_time > timeout; };
196 if(deadline_exceeded()) {
197 throw HTTP_Error(
"Timeout before reading response");
202 size_t header_end = std::string::npos;
204 while(header_end == std::string::npos) {
205 const size_t got = socket.
read(chunk.data(), chunk.size());
207 throw HTTP_Error(
"Server closed connection before headers complete");
209 if(deadline_exceeded()) {
210 throw HTTP_Error(
"Timeout while reading headers");
213 header_end = buf.find(
"\r\n\r\n");
214 if(header_end == std::string::npos && buf.size() > MaxHeaderBytes) {
215 throw HTTP_Error(
"HTTP headers exceed maximum size");
221 if(header_end > MaxHeaderBytes) {
222 throw HTTP_Error(
"HTTP headers exceed maximum size");
225 auto parsed = parse_status_and_headers(std::string_view(buf).substr(0, header_end));
226 const auto content_length = validate_response_headers(parsed.headers, max_body_size);
228 const size_t body_cap = std::min(max_body_size.value_or(std::numeric_limits<size_t>::max()),
229 content_length.value_or(std::numeric_limits<size_t>::max()));
231 std::vector<uint8_t> body;
233 body.reserve(*content_length);
235 const size_t body_start = header_end + 4;
236 if(body_start < buf.size()) {
237 const size_t spill = buf.size() - body_start;
238 if(spill > body_cap) {
239 throw HTTP_Error(
"Response body exceeds maximum size");
241 body.insert(body.end(),
242 reinterpret_cast<const uint8_t*
>(buf.data() + body_start),
243 reinterpret_cast<const uint8_t*
>(buf.data() + buf.size()));
246 while(!content_length || body.size() < *content_length) {
247 const size_t got = socket.
read(chunk.data(), chunk.size());
251 if(deadline_exceeded()) {
252 throw HTTP_Error(
"Timeout while reading body");
254 if(body.size() + got > body_cap) {
255 throw HTTP_Error(
"Response body exceeds maximum size");
257 body.insert(body.end(), chunk.data(), chunk.data() + got);
260 if(content_length && body.size() != *content_length) {
261 throw HTTP_Error(
fmt(
"Content-Length disagreement, header says {} got {}", *content_length, body.size()));
264 return Response(parsed.status_code, std::move(parsed.status_message), std::move(body), std::move(parsed.headers));
269 constexpr std::string_view hex_digits =
"0123456789ABCDEF";
272 out.reserve(in.size());
273 for(
const char c : in) {
274 if(needs_url_encoding(c)) {
275 const auto byte =
static_cast<uint8_t
>(c);
277 out += hex_digits[
byte >> 4];
278 out += hex_digits[
byte & 0x0F];
288 for(
const auto& h : resp.
headers()) {
289 o <<
"Header '" << h.first <<
"' = '" << h.second <<
"'\n";
291 o <<
"Body " << std::to_string(resp.
body().size()) <<
" bytes:\n";
297 std::string_view verb,
299 std::string_view content_type,
300 const std::vector<uint8_t>& body,
302 if(uri.
scheme() !=
"http") {
303 throw HTTP_Error(
fmt(
"Cannot initiate HTTP request to URI with scheme of '{}'", uri.
scheme()));
307 if(!authority.has_value()) {
308 throw HTTP_Error(
"Cannot initiate HTTP request to URI without authority");
311 check_no_crlf_nul(
"verb", verb);
312 check_no_crlf_nul(
"content type", content_type);
314 const std::string hostname = authority->host_to_string();
315 const auto port = authority->port();
316 const std::string service = port.has_value() ? std::to_string(*port) : uri.
scheme();
321 std::string loc = uri.
path().empty() ?
"/" : uri.
path();
322 if(
const auto& q = uri.
query()) {
327 const std::string host_header = [&]() -> std::string {
328 const std::string h = (authority->host_kind() == URI::HostKind::IPv6) ?
"[" + hostname +
"]" : hostname;
329 return port.has_value() ? h +
":" + std::to_string(*port) : h;
332 std::ostringstream outbuf;
334 outbuf << verb <<
" " << loc <<
" HTTP/1.0\r\n";
335 outbuf <<
"Host: " << host_header <<
"\r\n";
338 outbuf <<
"Accept: */*\r\n";
339 outbuf <<
"Cache-Control: no-cache\r\n";
340 }
else if(verb ==
"POST") {
341 outbuf <<
"Content-Length: " << body.size() <<
"\r\n";
344 if(!content_type.empty()) {
345 outbuf <<
"Content-Type: " << content_type <<
"\r\n";
347 outbuf <<
"Connection: close\r\n\r\n";
353 const bool is_redirect = (sc == 301 || sc == 302 || sc == 303 || sc == 307 || sc == 308);
355 const auto loc_it = resp.
headers().find(
"Location");
356 if(loc_it != resp.
headers().end()) {
358 throw HTTP_Error(
"HTTP redirection count exceeded");
360 auto redir = resolve_location(uri, loc_it->second);
362 throw HTTP_Error(
"HTTP redirected to invalid URL");
375 return http_sync(http_transact,
"GET", *redir,
"", std::vector<uint8_t>(), next);
377 return http_sync(http_transact, verb, *redir, content_type, body, next);
387 std::string_view content_type,
388 const std::vector<uint8_t>& body,
390 auto transact_with_timeout =
392 std::string_view hostname, std::string_view service, std::string_view message, std::optional<size_t> mbs) {
393 return http_transact(hostname, service, message, timeout, mbs);
396 return http_sync(transact_with_timeout, verb, uri, content_type, body, limits);
400 return http_sync(
"GET", uri,
"", std::vector<uint8_t>(), limits);
404 std::string_view content_type,
405 const std::vector<uint8_t>& body,
407 return http_sync(
"POST", uri, content_type, body, limits);