637 lines
24 KiB
C++
637 lines
24 KiB
C++
/*
|
|
* http.hpp
|
|
* Shared HTTP/1.1 client for MontaukOS.
|
|
*
|
|
* Owns request construction, DNS/transport selection, bounded response
|
|
* collection, response parsing, and chunked-transfer decoding. Applications
|
|
* should use this layer instead of constructing HTTP messages themselves.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <montauk/syscall.h>
|
|
#include <montauk/string.h>
|
|
#include <montauk/heap.h>
|
|
#include <tls/tls.hpp>
|
|
|
|
namespace http {
|
|
|
|
enum class Error {
|
|
NONE = 0,
|
|
INVALID_ARGUMENT,
|
|
DNS_FAILED,
|
|
NO_MEMORY,
|
|
REQUEST_TOO_LARGE,
|
|
SOCKET_FAILED,
|
|
CONNECT_FAILED,
|
|
SEND_FAILED,
|
|
RECEIVE_FAILED,
|
|
TLS_FAILED,
|
|
INVALID_RESPONSE,
|
|
RESPONSE_TOO_LARGE,
|
|
TRUNCATED_RESPONSE
|
|
};
|
|
|
|
inline const char* error_string(Error error) {
|
|
switch (error) {
|
|
case Error::NONE: return "no error";
|
|
case Error::INVALID_ARGUMENT: return "invalid HTTP request";
|
|
case Error::DNS_FAILED: return "DNS resolution failed";
|
|
case Error::NO_MEMORY: return "out of memory";
|
|
case Error::REQUEST_TOO_LARGE: return "HTTP request is too large";
|
|
case Error::SOCKET_FAILED: return "could not create socket";
|
|
case Error::CONNECT_FAILED: return "connection failed";
|
|
case Error::SEND_FAILED: return "request send failed";
|
|
case Error::RECEIVE_FAILED: return "response receive failed";
|
|
case Error::TLS_FAILED: return "TLS exchange failed";
|
|
case Error::INVALID_RESPONSE: return "invalid HTTP response";
|
|
case Error::RESPONSE_TOO_LARGE: return "HTTP response exceeded the buffer";
|
|
case Error::TRUNCATED_RESPONSE: return "truncated HTTP response";
|
|
}
|
|
return "unknown HTTP error";
|
|
}
|
|
|
|
struct Response {
|
|
int status; // HTTP status code, or -1 before a response is parsed
|
|
const char* headers; // Pointers into raw
|
|
int headers_len;
|
|
const char* body;
|
|
int body_len;
|
|
char* raw;
|
|
int raw_len;
|
|
Error error;
|
|
bool owns_raw;
|
|
};
|
|
|
|
struct RequestOptions {
|
|
bool secure; // true for HTTPS, false for HTTP
|
|
uint16_t port; // 0 selects 443 or 80
|
|
uint32_t resolved_ip; // 0 performs DNS resolution
|
|
int response_buffer_size; // used by request(); includes trailing NUL
|
|
const char* host_header; // optional Host authority (e.g. host:port)
|
|
const char* extra_headers; // complete CRLF-terminated header lines
|
|
tls::AbortCheckFn abort_check;
|
|
uint64_t timeout_ms; // inactivity timeout for plain HTTP
|
|
|
|
RequestOptions()
|
|
: secure(true), port(0), resolved_ip(0), response_buffer_size(32768),
|
|
host_header(nullptr), extra_headers(nullptr), abort_check(nullptr),
|
|
timeout_ms(30000) {}
|
|
};
|
|
|
|
inline Response empty_response(Error error = Error::NONE) {
|
|
Response resp = {};
|
|
resp.status = -1;
|
|
resp.error = error;
|
|
return resp;
|
|
}
|
|
|
|
inline const char* find_header_block_end(const char* start, const char* end) {
|
|
if (!start || !end || start >= end) return nullptr;
|
|
for (const char* p = start; p + 3 < end; ++p) {
|
|
if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n')
|
|
return p + 4;
|
|
}
|
|
for (const char* p = start; p + 1 < end; ++p) {
|
|
if (p[0] == '\n' && p[1] == '\n') return p + 2;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
inline int parse_status_code(const char* start, const char* end) {
|
|
if (!start || !end || end - start < 12) return -1;
|
|
if (start[0] != 'H' || start[1] != 'T' || start[2] != 'T' ||
|
|
start[3] != 'P' || start[4] != '/' || start[5] != '1' ||
|
|
start[6] != '.' || (start[7] != '0' && start[7] != '1') ||
|
|
start[8] != ' ') return -1;
|
|
|
|
const char* p = start;
|
|
while (p < end && *p != ' ' && *p != '\r' && *p != '\n') ++p;
|
|
if (p >= end || *p++ != ' ') return -1;
|
|
if (end - p < 3 || p[0] < '0' || p[0] > '9' ||
|
|
p[1] < '0' || p[1] > '9' || p[2] < '0' || p[2] > '9') return -1;
|
|
return (p[0] - '0') * 100 + (p[1] - '0') * 10 + (p[2] - '0');
|
|
}
|
|
|
|
inline const char* find_final_response_start(const char* buf, int len) {
|
|
if (!buf || len <= 0) return nullptr;
|
|
const char* start = buf;
|
|
const char* end = buf + len;
|
|
for (;;) {
|
|
int code = parse_status_code(start, end);
|
|
if (code < 0) return nullptr;
|
|
if (code < 100 || code == 101 || code >= 200) return start;
|
|
const char* next = find_header_block_end(start, end);
|
|
if (!next || next >= end) return nullptr;
|
|
start = next;
|
|
}
|
|
}
|
|
|
|
inline const char* skip_informational_responses(const char* buf, int len) {
|
|
const char* start = find_final_response_start(buf, len);
|
|
return start ? start : buf;
|
|
}
|
|
|
|
inline bool ascii_equal_ci(char a, char b) {
|
|
if (a >= 'A' && a <= 'Z') a += 'a' - 'A';
|
|
if (b >= 'A' && b <= 'Z') b += 'a' - 'A';
|
|
return a == b;
|
|
}
|
|
|
|
inline bool get_header(const Response* resp, const char* name,
|
|
char* out_val, int max_len) {
|
|
if (!resp || !resp->headers || resp->headers_len <= 0 || !name ||
|
|
!out_val || max_len <= 0) return false;
|
|
|
|
int name_len = montauk::slen(name);
|
|
const char* p = resp->headers;
|
|
const char* end = p + resp->headers_len;
|
|
while (p < end) {
|
|
const char* line_end = p;
|
|
while (line_end < end && *line_end != '\r' && *line_end != '\n') ++line_end;
|
|
bool match = line_end - p > name_len && p[name_len] == ':';
|
|
for (int i = 0; match && i < name_len; ++i)
|
|
if (!ascii_equal_ci(p[i], name[i])) match = false;
|
|
|
|
if (match) {
|
|
const char* value = p + name_len + 1;
|
|
while (value < line_end && (*value == ' ' || *value == '\t')) ++value;
|
|
while (line_end > value &&
|
|
(line_end[-1] == ' ' || line_end[-1] == '\t')) --line_end;
|
|
int n = (int)(line_end - value);
|
|
if (n >= max_len) n = max_len - 1;
|
|
if (n > 0) montauk::memcpy(out_val, value, n);
|
|
out_val[n] = '\0';
|
|
return true;
|
|
}
|
|
|
|
p = line_end;
|
|
while (p < end && (*p == '\r' || *p == '\n')) ++p;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline bool header_has_token(const Response* resp, const char* name,
|
|
const char* token) {
|
|
char value[128];
|
|
if (!get_header(resp, name, value, sizeof(value))) return false;
|
|
int token_len = montauk::slen(token);
|
|
for (int i = 0; value[i];) {
|
|
while (value[i] == ' ' || value[i] == '\t' || value[i] == ',') ++i;
|
|
int start = i;
|
|
while (value[i] && value[i] != ',' && value[i] != ' ' && value[i] != '\t') ++i;
|
|
int len = i - start;
|
|
bool match = len == token_len;
|
|
for (int j = 0; match && j < len; ++j)
|
|
if (!ascii_equal_ci(value[start + j], token[j])) match = false;
|
|
if (match) return true;
|
|
while (value[i] && value[i] != ',') ++i;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline int parse_decimal(const char* value) {
|
|
if (!value || !*value) return -1;
|
|
int result = 0;
|
|
for (int i = 0; value[i]; ++i) {
|
|
if (value[i] < '0' || value[i] > '9') return -1;
|
|
if (result > 214748364 || (result == 214748364 && value[i] > '7')) return -1;
|
|
result = result * 10 + value[i] - '0';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
inline int decode_chunked_body(char* body, int encoded_len) {
|
|
int read_pos = 0;
|
|
int write_pos = 0;
|
|
for (;;) {
|
|
unsigned chunk_size = 0;
|
|
int digits = 0;
|
|
while (read_pos < encoded_len && body[read_pos] != '\r' && body[read_pos] != '\n') {
|
|
char c = body[read_pos++];
|
|
if (c == ';') {
|
|
while (read_pos < encoded_len && body[read_pos] != '\r' &&
|
|
body[read_pos] != '\n') ++read_pos;
|
|
break;
|
|
}
|
|
unsigned digit;
|
|
if (c >= '0' && c <= '9') digit = (unsigned)(c - '0');
|
|
else if (c >= 'a' && c <= 'f') digit = (unsigned)(c - 'a' + 10);
|
|
else if (c >= 'A' && c <= 'F') digit = (unsigned)(c - 'A' + 10);
|
|
else return -1;
|
|
if (chunk_size > 0x0FFFFFFFu) return -1;
|
|
chunk_size = chunk_size * 16 + digit;
|
|
++digits;
|
|
}
|
|
if (digits == 0 || read_pos >= encoded_len) return -1;
|
|
if (body[read_pos] == '\r') {
|
|
if (read_pos + 1 >= encoded_len || body[read_pos + 1] != '\n') return -1;
|
|
read_pos += 2;
|
|
} else {
|
|
++read_pos;
|
|
}
|
|
|
|
if (chunk_size == 0) {
|
|
// A zero chunk is followed by either an empty trailer line or a
|
|
// trailer header block. Do not accept a response cut at "0\r\n".
|
|
if (read_pos < encoded_len && body[read_pos] == '\n') return write_pos;
|
|
if (read_pos + 1 < encoded_len && body[read_pos] == '\r' &&
|
|
body[read_pos + 1] == '\n') return write_pos;
|
|
if (find_header_block_end(body + read_pos, body + encoded_len))
|
|
return write_pos;
|
|
return -1;
|
|
}
|
|
if (chunk_size > (unsigned)(encoded_len - read_pos)) return -1;
|
|
montauk::memmove(body + write_pos, body + read_pos, chunk_size);
|
|
write_pos += (int)chunk_size;
|
|
read_pos += (int)chunk_size;
|
|
if (read_pos >= encoded_len) return -1;
|
|
if (body[read_pos] == '\r') {
|
|
if (read_pos + 1 >= encoded_len || body[read_pos + 1] != '\n') return -1;
|
|
read_pos += 2;
|
|
} else if (body[read_pos] == '\n') {
|
|
++read_pos;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parses and normalizes a response in place. Chunked bodies are decoded in
|
|
// the same buffer. Content-Length mismatches are reported as truncation.
|
|
inline int parse_response(char* buf, int len, Response* out) {
|
|
if (!out) return -1;
|
|
bool owns_raw = out->owns_raw;
|
|
*out = empty_response();
|
|
out->raw = buf;
|
|
out->raw_len = len;
|
|
out->owns_raw = owns_raw;
|
|
if (!buf || len <= 0) {
|
|
out->error = Error::INVALID_RESPONSE;
|
|
return -1;
|
|
}
|
|
|
|
const char* start = find_final_response_start(buf, len);
|
|
const char* end = buf + len;
|
|
if (!start) {
|
|
out->error = Error::INVALID_RESPONSE;
|
|
return -1;
|
|
}
|
|
int code = parse_status_code(start, end);
|
|
if (code < 0) {
|
|
out->error = Error::INVALID_RESPONSE;
|
|
return -1;
|
|
}
|
|
|
|
const char* status_end = start;
|
|
while (status_end < end && *status_end != '\n') ++status_end;
|
|
if (status_end >= end) {
|
|
out->error = Error::INVALID_RESPONSE;
|
|
return -1;
|
|
}
|
|
const char* headers = status_end + 1;
|
|
const char* body = find_header_block_end(headers, end);
|
|
if (!body) {
|
|
out->error = Error::INVALID_RESPONSE;
|
|
return -1;
|
|
}
|
|
|
|
const char* headers_end = body;
|
|
if (headers_end - headers >= 4 && headers_end[-4] == '\r')
|
|
headers_end -= 4;
|
|
else
|
|
headers_end -= 2;
|
|
|
|
out->status = code;
|
|
out->headers = headers;
|
|
out->headers_len = (int)(headers_end - headers);
|
|
out->body = body;
|
|
out->body_len = (int)(end - body);
|
|
|
|
if (header_has_token(out, "Transfer-Encoding", "chunked")) {
|
|
int decoded = decode_chunked_body((char*)out->body, out->body_len);
|
|
if (decoded < 0) {
|
|
out->error = Error::TRUNCATED_RESPONSE;
|
|
return code;
|
|
}
|
|
out->body_len = decoded;
|
|
((char*)out->body)[decoded] = '\0';
|
|
} else {
|
|
char value[32];
|
|
if (get_header(out, "Content-Length", value, sizeof(value))) {
|
|
int expected = parse_decimal(value);
|
|
if (expected < 0) {
|
|
out->error = Error::INVALID_RESPONSE;
|
|
return code;
|
|
}
|
|
if (out->body_len < expected) {
|
|
out->error = Error::TRUNCATED_RESPONSE;
|
|
return code;
|
|
}
|
|
out->body_len = expected;
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
inline void free_response(Response* resp) {
|
|
if (!resp) return;
|
|
if (resp->raw && resp->owns_raw) montauk::mfree(resp->raw);
|
|
*resp = empty_response();
|
|
}
|
|
|
|
inline bool contains_crlf(const char* value) {
|
|
if (!value) return false;
|
|
for (; *value; ++value)
|
|
if (*value == '\r' || *value == '\n') return true;
|
|
return false;
|
|
}
|
|
|
|
inline bool contains_header_terminator(const char* value) {
|
|
if (!value) return false;
|
|
for (int i = 0; value[i]; ++i) {
|
|
if (value[i] == '\n' && value[i + 1] == '\n') return true;
|
|
if (value[i] == '\r' && value[i + 1] == '\n' &&
|
|
value[i + 2] == '\r' && value[i + 3] == '\n') return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct RequestWriter {
|
|
char* p;
|
|
char* end;
|
|
bool overflow;
|
|
|
|
void text(const char* value) {
|
|
if (!value) return;
|
|
while (*value) {
|
|
if (p >= end) { overflow = true; return; }
|
|
*p++ = *value++;
|
|
}
|
|
}
|
|
void number(int value) {
|
|
char digits[16];
|
|
int n = 0;
|
|
if (value == 0) digits[n++] = '0';
|
|
while (value > 0 && n < (int)sizeof(digits)) {
|
|
digits[n++] = (char)('0' + value % 10);
|
|
value /= 10;
|
|
}
|
|
while (n > 0) {
|
|
if (p >= end) { overflow = true; return; }
|
|
*p++ = digits[--n];
|
|
}
|
|
}
|
|
};
|
|
|
|
inline int build_request(char* buf, int buf_size,
|
|
const char* method, const char* host,
|
|
const char* path, const char* content_type,
|
|
const char* body_data, int body_len,
|
|
const char* extra_headers) {
|
|
if (!buf || buf_size <= 0 || !method || !*method || !host || !*host ||
|
|
!path || path[0] != '/' || body_len < 0 ||
|
|
(body_len > 0 && !body_data) || contains_crlf(method) ||
|
|
contains_crlf(host) || contains_crlf(path) ||
|
|
(content_type && contains_crlf(content_type)) ||
|
|
contains_header_terminator(extra_headers)) return -1;
|
|
|
|
RequestWriter w = {buf, buf + buf_size, false};
|
|
w.text(method); w.text(" "); w.text(path); w.text(" HTTP/1.1\r\n");
|
|
w.text("Host: "); w.text(host); w.text("\r\n");
|
|
if (body_data || body_len > 0) {
|
|
if (content_type) {
|
|
w.text("Content-Type: "); w.text(content_type); w.text("\r\n");
|
|
}
|
|
w.text("Content-Length: "); w.number(body_len); w.text("\r\n");
|
|
}
|
|
if (extra_headers) {
|
|
w.text(extra_headers);
|
|
int n = montauk::slen(extra_headers);
|
|
if (n > 0 && extra_headers[n - 1] != '\n') w.text("\r\n");
|
|
}
|
|
w.text("Connection: close\r\n\r\n");
|
|
if (!w.overflow && body_len > 0) {
|
|
if (w.end - w.p < body_len) w.overflow = true;
|
|
else {
|
|
montauk::memcpy(w.p, body_data, body_len);
|
|
w.p += body_len;
|
|
}
|
|
}
|
|
return w.overflow ? -1 : (int)(w.p - buf);
|
|
}
|
|
|
|
inline bool plain_send_all(int fd, const char* data, int len,
|
|
uint64_t timeout_ms, tls::AbortCheckFn abort_check) {
|
|
int sent = 0;
|
|
uint64_t deadline = montauk::get_milliseconds() + timeout_ms;
|
|
while (sent < len) {
|
|
if (abort_check && abort_check()) return false;
|
|
int n = montauk::send(fd, data + sent, (uint32_t)(len - sent));
|
|
if (n < 0) return false;
|
|
if (n > 0) {
|
|
sent += n;
|
|
deadline = montauk::get_milliseconds() + timeout_ms;
|
|
continue;
|
|
}
|
|
uint64_t now = montauk::get_milliseconds();
|
|
if (now >= deadline) return false;
|
|
uint32_t signals = montauk::wait_handle(
|
|
fd, montauk::abi::IPC_SIGNAL_WRITABLE |
|
|
montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now);
|
|
if (signals == (uint32_t)-1 ||
|
|
(signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline int plain_receive(int fd, char* buf, int capacity, uint64_t timeout_ms,
|
|
tls::AbortCheckFn abort_check, Error* error) {
|
|
int total = 0;
|
|
uint64_t deadline = montauk::get_milliseconds() + timeout_ms;
|
|
while (total < capacity) {
|
|
if (abort_check && abort_check()) {
|
|
*error = Error::RECEIVE_FAILED;
|
|
return -1;
|
|
}
|
|
int n = montauk::recv(fd, buf + total, (uint32_t)(capacity - total));
|
|
if (n > 0) {
|
|
total += n;
|
|
deadline = montauk::get_milliseconds() + timeout_ms;
|
|
continue;
|
|
}
|
|
if (n < 0) return total;
|
|
uint64_t now = montauk::get_milliseconds();
|
|
if (now >= deadline) {
|
|
*error = Error::RECEIVE_FAILED;
|
|
return total > 0 ? total : -1;
|
|
}
|
|
uint32_t signals = montauk::wait_handle(
|
|
fd, montauk::abi::IPC_SIGNAL_READABLE |
|
|
montauk::abi::IPC_SIGNAL_PEER_CLOSED, deadline - now);
|
|
if (signals == (uint32_t)-1) {
|
|
*error = Error::RECEIVE_FAILED;
|
|
return total > 0 ? total : -1;
|
|
}
|
|
if ((signals & montauk::abi::IPC_SIGNAL_PEER_CLOSED) &&
|
|
!(signals & montauk::abi::IPC_SIGNAL_READABLE)) return total;
|
|
}
|
|
*error = Error::RESPONSE_TOO_LARGE;
|
|
return total;
|
|
}
|
|
|
|
// Generic request using a caller-owned response buffer.
|
|
inline Response request_into(const char* method, const char* host, const char* path,
|
|
const char* content_type, const char* body_data,
|
|
int body_len, const tls::TrustAnchors* tas,
|
|
char* response_buffer, int response_buffer_size,
|
|
const RequestOptions& options = RequestOptions()) {
|
|
Response resp = empty_response();
|
|
if (!response_buffer || response_buffer_size < 2 || !method || !host || !path ||
|
|
body_len < 0 || (body_len > 0 && !body_data) ||
|
|
(options.secure && (!tas || tas->count == 0))) {
|
|
resp.error = Error::INVALID_ARGUMENT;
|
|
return resp;
|
|
}
|
|
|
|
uint64_t request_size64 = 160u + (uint64_t)montauk::slen(method) +
|
|
(uint64_t)montauk::slen(host) + (uint64_t)montauk::slen(path) +
|
|
(uint64_t)(content_type ? montauk::slen(content_type) : 0) +
|
|
(uint64_t)(options.extra_headers ? montauk::slen(options.extra_headers) : 0) +
|
|
(uint64_t)body_len;
|
|
if (request_size64 > 8u * 1024u * 1024u) {
|
|
resp.error = Error::REQUEST_TOO_LARGE;
|
|
return resp;
|
|
}
|
|
int request_size = (int)request_size64;
|
|
char* request_data = (char*)montauk::malloc(request_size);
|
|
if (!request_data) {
|
|
resp.error = Error::NO_MEMORY;
|
|
return resp;
|
|
}
|
|
int request_len = build_request(request_data, request_size, method,
|
|
options.host_header ? options.host_header : host, path,
|
|
content_type, body_data, body_len,
|
|
options.extra_headers);
|
|
if (request_len < 0) {
|
|
montauk::mfree(request_data);
|
|
resp.error = Error::REQUEST_TOO_LARGE;
|
|
return resp;
|
|
}
|
|
|
|
uint32_t ip = options.resolved_ip ? options.resolved_ip : montauk::resolve(host);
|
|
if (!ip) {
|
|
montauk::mfree(request_data);
|
|
resp.error = Error::DNS_FAILED;
|
|
return resp;
|
|
}
|
|
uint16_t port = options.port ? options.port : (options.secure ? 443 : 80);
|
|
int received = -1;
|
|
Error transport_error = Error::NONE;
|
|
|
|
if (options.secure) {
|
|
received = tls::https_fetch(host, ip, port, request_data, request_len, *tas,
|
|
response_buffer, response_buffer_size,
|
|
options.abort_check);
|
|
if (received < 0) transport_error = Error::TLS_FAILED;
|
|
else if (received >= response_buffer_size - 1)
|
|
transport_error = Error::RESPONSE_TOO_LARGE;
|
|
} else {
|
|
int fd = montauk::socket(montauk::abi::SOCK_TCP);
|
|
if (fd < 0) {
|
|
transport_error = Error::SOCKET_FAILED;
|
|
} else if (montauk::connect(fd, ip, port) < 0) {
|
|
transport_error = Error::CONNECT_FAILED;
|
|
montauk::closesocket(fd);
|
|
} else {
|
|
if (!plain_send_all(fd, request_data, request_len, options.timeout_ms,
|
|
options.abort_check)) {
|
|
transport_error = Error::SEND_FAILED;
|
|
} else {
|
|
received = plain_receive(fd, response_buffer, response_buffer_size - 1,
|
|
options.timeout_ms, options.abort_check,
|
|
&transport_error);
|
|
}
|
|
montauk::closesocket(fd);
|
|
}
|
|
}
|
|
montauk::mfree(request_data);
|
|
|
|
if (received <= 0) {
|
|
resp.error = transport_error == Error::NONE ? Error::RECEIVE_FAILED : transport_error;
|
|
return resp;
|
|
}
|
|
response_buffer[received] = '\0';
|
|
resp.owns_raw = false;
|
|
parse_response(response_buffer, received, &resp);
|
|
if (resp.error == Error::NONE && transport_error != Error::NONE)
|
|
resp.error = transport_error;
|
|
return resp;
|
|
}
|
|
|
|
// Generic request with a library-owned response buffer.
|
|
inline Response request(const char* method, const char* host, const char* path,
|
|
const char* content_type, const char* body_data, int body_len,
|
|
const tls::TrustAnchors* tas,
|
|
const RequestOptions& options = RequestOptions()) {
|
|
if (options.response_buffer_size < 2)
|
|
return empty_response(Error::INVALID_ARGUMENT);
|
|
char* buffer = (char*)montauk::malloc(options.response_buffer_size);
|
|
if (!buffer) return empty_response(Error::NO_MEMORY);
|
|
Response resp = request_into(method, host, path, content_type, body_data, body_len,
|
|
tas, buffer, options.response_buffer_size, options);
|
|
if (!resp.raw) {
|
|
montauk::mfree(buffer);
|
|
} else {
|
|
resp.owns_raw = true;
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
inline Response get(const char* host, const char* path,
|
|
const tls::TrustAnchors& tas, int response_buffer_size = 32768,
|
|
const char* extra_headers = nullptr,
|
|
tls::AbortCheckFn abort_check = nullptr) {
|
|
RequestOptions options;
|
|
options.response_buffer_size = response_buffer_size;
|
|
options.extra_headers = extra_headers;
|
|
options.abort_check = abort_check;
|
|
return request("GET", host, path, nullptr, nullptr, 0, &tas, options);
|
|
}
|
|
|
|
inline Response post(const char* host, const char* path, const char* content_type,
|
|
const char* body_data, int body_len,
|
|
const tls::TrustAnchors& tas, int response_buffer_size = 32768,
|
|
const char* extra_headers = nullptr,
|
|
tls::AbortCheckFn abort_check = nullptr) {
|
|
RequestOptions options;
|
|
options.response_buffer_size = response_buffer_size;
|
|
options.extra_headers = extra_headers;
|
|
options.abort_check = abort_check;
|
|
return request("POST", host, path, content_type, body_data, body_len, &tas, options);
|
|
}
|
|
|
|
inline Response request(const char* method, const char* host, const char* path,
|
|
const char* content_type, const char* body_data, int body_len,
|
|
const tls::TrustAnchors& tas, int response_buffer_size = 32768,
|
|
const char* extra_headers = nullptr,
|
|
tls::AbortCheckFn abort_check = nullptr) {
|
|
RequestOptions options;
|
|
options.response_buffer_size = response_buffer_size;
|
|
options.extra_headers = extra_headers;
|
|
options.abort_check = abort_check;
|
|
return request(method, host, path, content_type, body_data, body_len, &tas, options);
|
|
}
|
|
|
|
inline Response get_plain(const char* host, const char* path,
|
|
int response_buffer_size = 32768,
|
|
const char* extra_headers = nullptr) {
|
|
RequestOptions options;
|
|
options.secure = false;
|
|
options.response_buffer_size = response_buffer_size;
|
|
options.extra_headers = extra_headers;
|
|
return request("GET", host, path, nullptr, nullptr, 0, nullptr, options);
|
|
}
|
|
|
|
} // namespace http
|