Botan 3.13.0
Crypto and TLS for C&
Botan::HTTP Namespace Reference

Classes

struct  Case_Insensitive_Less
class  HTTP_Error
class  RequestLimits
class  Response

Typedefs

using Headers = std::map<std::string, std::string, Case_Insensitive_Less>
typedef std::function< Response(std::string_view, std::string_view, std::string_view, std::optional< size_t >)> http_exch_fn

Functions

Response GET_sync (const URI &uri, const RequestLimits &limits)
Response http_sync (const http_exch_fn &http_transact, std::string_view verb, const URI &uri, std::string_view content_type, const std::vector< uint8_t > &body, const RequestLimits &limits)
Response http_sync (std::string_view verb, const URI &uri, std::string_view content_type, const std::vector< uint8_t > &body, const RequestLimits &limits)
std::ostream & operator<< (std::ostream &o, const Response &resp)
Response POST_sync (const URI &uri, std::string_view content_type, const std::vector< uint8_t > &body, const RequestLimits &limits)
Response read_response_from_socket (OS::Socket &socket, std::chrono::milliseconds timeout, std::optional< size_t > max_body_size)
std::string url_encode (std::string_view in)

Typedef Documentation

◆ Headers

using Botan::HTTP::Headers = std::map<std::string, std::string, Case_Insensitive_Less>

Definition at line 61 of file http_util.h.

◆ http_exch_fn

typedef std::function<Response(std::string_view, std::string_view, std::string_view, std::optional<size_t>)> Botan::HTTP::http_exch_fn

Definition at line 131 of file http_util.h.

Function Documentation

◆ GET_sync()

Response BOTAN_TEST_API Botan::HTTP::GET_sync ( const URI & uri,
const RequestLimits & limits )

Definition at line 399 of file http_util.cpp.

399 {
400 return http_sync("GET", uri, "", std::vector<uint8_t>(), limits);
401}
Response http_sync(const http_exch_fn &http_transact, std::string_view verb, const URI &uri, std::string_view content_type, const std::vector< uint8_t > &body, const RequestLimits &limits)

References http_sync().

◆ http_sync() [1/2]

Response BOTAN_TEST_API Botan::HTTP::http_sync ( const http_exch_fn & http_transact,
std::string_view verb,
const URI & uri,
std::string_view content_type,
const std::vector< uint8_t > & body,
const RequestLimits & limits )

Definition at line 296 of file http_util.cpp.

301 {
302 if(uri.scheme() != "http") {
303 throw HTTP_Error(fmt("Cannot initiate HTTP request to URI with scheme of '{}'", uri.scheme()));
304 }
305
306 const auto& authority = uri.authority();
307 if(!authority.has_value()) {
308 throw HTTP_Error("Cannot initiate HTTP request to URI without authority");
309 }
310
311 check_no_crlf_nul("verb", verb);
312 check_no_crlf_nul("content type", content_type);
313
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();
317
318 // RFC 9112 3.2.1: request-target origin-form is "absolute-path [ '?' query ]".
319 // If the URI has an empty path, the client MUST send "/". Fragment is
320 // excluded from the request-target per RFC 9110 7.1.
321 std::string loc = uri.path().empty() ? "/" : uri.path();
322 if(const auto& q = uri.query()) {
323 loc += '?';
324 loc += *q;
325 }
326
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;
330 }();
331
332 std::ostringstream outbuf;
333
334 outbuf << verb << " " << loc << " HTTP/1.0\r\n";
335 outbuf << "Host: " << host_header << "\r\n";
336
337 if(verb == "GET") {
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";
342 }
343
344 if(!content_type.empty()) {
345 outbuf << "Content-Type: " << content_type << "\r\n";
346 }
347 outbuf << "Connection: close\r\n\r\n";
348 outbuf.write(cast_uint8_ptr_to_char(body.data()), body.size());
349
350 Response resp = http_transact(hostname, service, outbuf.str(), limits.max_body_size());
351
352 const auto sc = resp.status_code();
353 const bool is_redirect = (sc == 301 || sc == 302 || sc == 303 || sc == 307 || sc == 308);
354 if(is_redirect) {
355 const auto loc_it = resp.headers().find("Location");
356 if(loc_it != resp.headers().end()) {
357 if(limits.max_redirects() == 0) {
358 throw HTTP_Error("HTTP redirection count exceeded");
359 }
360 auto redir = resolve_location(uri, loc_it->second);
361 if(!redir) {
362 throw HTTP_Error("HTTP redirected to invalid URL");
363 }
364 RequestLimits next = limits;
365 next.set_max_redirects(limits.max_redirects() - 1);
366
367 // 303 (RFC 9110 15.4.4) re-issues as GET; 301/302/307/308 preserve the
368 // original method and content. The POST->GET downgrade allowed for
369 // 301/302 by RFC 9110 15.4.2/3 exists for browser form-submission
370 // legacy and would silently drop the request body, which is wrong here.
371 //
372 // The recursion goes through the same http_exch_fn so a test seam (or
373 // any caller wrapping the network layer) sees every hop.
374 if(sc == 303) {
375 return http_sync(http_transact, "GET", *redir, "", std::vector<uint8_t>(), next);
376 } else {
377 return http_sync(http_transact, verb, *redir, content_type, body, next);
378 }
379 }
380 }
381
382 return resp;
383}
std::optional< size_t > max_body_size() const
Definition http_util.h:107
const std::string & scheme() const
Definition uri.h:127
const std::optional< std::string > & query() const
Definition uri.h:161
const std::optional< Authority > & authority() const
Definition uri.h:132
const std::string & path() const
Definition uri.h:154
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
const char * cast_uint8_ptr_to_char(const uint8_t *b)
Definition mem_ops.h:323

References Botan::URI::authority(), Botan::cast_uint8_ptr_to_char(), Botan::fmt(), Botan::HTTP::Response::headers(), http_sync(), Botan::HTTP::RequestLimits::max_body_size(), Botan::HTTP::RequestLimits::max_redirects(), Botan::URI::path(), Botan::URI::query(), Botan::URI::scheme(), Botan::HTTP::RequestLimits::set_max_redirects(), and Botan::HTTP::Response::status_code().

Referenced by GET_sync(), http_sync(), http_sync(), and POST_sync().

◆ http_sync() [2/2]

Response Botan::HTTP::http_sync ( std::string_view verb,
const URI & uri,
std::string_view content_type,
const std::vector< uint8_t > & body,
const RequestLimits & limits )

Definition at line 385 of file http_util.cpp.

389 {
390 auto transact_with_timeout =
391 [timeout = limits.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);
394 };
395
396 return http_sync(transact_with_timeout, verb, uri, content_type, body, limits);
397}
std::chrono::milliseconds timeout() const
Definition http_util.h:105

References http_sync(), and Botan::HTTP::RequestLimits::timeout().

◆ operator<<()

BOTAN_TEST_API std::ostream & Botan::HTTP::operator<< ( std::ostream & o,
const Response & resp )

Definition at line 286 of file http_util.cpp.

286 {
287 o << "HTTP " << resp.status_code() << " " << resp.status_message() << "\n";
288 for(const auto& h : resp.headers()) {
289 o << "Header '" << h.first << "' = '" << h.second << "'\n";
290 }
291 o << "Body " << std::to_string(resp.body().size()) << " bytes:\n";
292 o.write(cast_uint8_ptr_to_char(resp.body().data()), resp.body().size());
293 return o;
294}
const std::vector< uint8_t > & body() const
Definition http_util.h:75
const Headers & headers() const
Definition http_util.h:77
unsigned int status_code() const
Definition http_util.h:73
std::string status_message() const
Definition http_util.h:79

References Botan::HTTP::Response::body(), Botan::cast_uint8_ptr_to_char(), Botan::HTTP::Response::headers(), Botan::HTTP::Response::status_code(), and Botan::HTTP::Response::status_message().

◆ POST_sync()

Response Botan::HTTP::POST_sync ( const URI & uri,
std::string_view content_type,
const std::vector< uint8_t > & body,
const RequestLimits & limits )

Definition at line 403 of file http_util.cpp.

406 {
407 return http_sync("POST", uri, content_type, body, limits);
408}

References http_sync().

◆ read_response_from_socket()

Response BOTAN_TEST_API Botan::HTTP::read_response_from_socket ( OS::Socket & socket,
std::chrono::milliseconds timeout,
std::optional< size_t > max_body_size )

Read a complete HTTP/1.0 response from an already-connected socket and return the parsed Response. Enforces header- and body-size limits during the read. Exposed via BOTAN_TEST_API so the parser can be exercised against a fake socket; production code reaches it through http_transact.

Definition at line 190 of file http_util.cpp.

192 {
193 const auto start_time = std::chrono::system_clock::now();
194 const auto deadline_exceeded = [&] { return std::chrono::system_clock::now() - start_time > timeout; };
195
196 if(deadline_exceeded()) {
197 throw HTTP_Error("Timeout before reading response");
198 }
199
200 std::string buf;
201 std::vector<uint8_t> chunk(DefaultBufferSize);
202 size_t header_end = std::string::npos;
203
204 while(header_end == std::string::npos) {
205 const size_t got = socket.read(chunk.data(), chunk.size());
206 if(got == 0) {
207 throw HTTP_Error("Server closed connection before headers complete");
208 }
209 if(deadline_exceeded()) {
210 throw HTTP_Error("Timeout while reading headers");
211 }
212 buf.append(cast_uint8_ptr_to_char(chunk.data()), got);
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");
216 }
217 }
218
219 // Same cap re-checked once the terminator is found, since the terminator
220 // can arrive in the chunk that crosses the limit.
221 if(header_end > MaxHeaderBytes) {
222 throw HTTP_Error("HTTP headers exceed maximum size");
223 }
224
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);
227
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()));
230
231 std::vector<uint8_t> body;
232 if(content_length) {
233 body.reserve(*content_length);
234 }
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");
240 }
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()));
244 }
245
246 while(!content_length || body.size() < *content_length) {
247 const size_t got = socket.read(chunk.data(), chunk.size());
248 if(got == 0) {
249 break;
250 }
251 if(deadline_exceeded()) {
252 throw HTTP_Error("Timeout while reading body");
253 }
254 if(body.size() + got > body_cap) {
255 throw HTTP_Error("Response body exceeds maximum size");
256 }
257 body.insert(body.end(), chunk.data(), chunk.data() + got);
258 }
259
260 if(content_length && body.size() != *content_length) {
261 throw HTTP_Error(fmt("Content-Length disagreement, header says {} got {}", *content_length, body.size()));
262 }
263
264 return Response(parsed.status_code, std::move(parsed.status_message), std::move(body), std::move(parsed.headers));
265}
virtual size_t read(uint8_t buf[], size_t len)=0
constexpr size_t DefaultBufferSize
Definition types.h:150

References Botan::cast_uint8_ptr_to_char(), Botan::DefaultBufferSize, Botan::fmt(), and Botan::OS::Socket::read().

◆ url_encode()

std::string BOTAN_TEST_API Botan::HTTP::url_encode ( std::string_view in)

Definition at line 267 of file http_util.cpp.

267 {
268 constexpr auto needs_url_encoding = CharacterValidityTable::alpha_numeric_plus("-_.~").invert();
269 constexpr std::string_view hex_digits = "0123456789ABCDEF";
270
271 std::string out;
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);
276 out += '%';
277 out += hex_digits[byte >> 4];
278 out += hex_digits[byte & 0x0F];
279 } else {
280 out += c;
281 }
282 }
283 return out;
284}
static constexpr CharacterValidityTable alpha_numeric_plus(std::string_view extras)
Definition charset.h:114
constexpr CharacterValidityTable invert() const
Definition charset.h:130

References Botan::CharacterValidityTable::alpha_numeric_plus(), and Botan::CharacterValidityTable::invert().