refactor: network - harden TCP/IP and unify HTTP clients

This commit is contained in:
2026-07-29 15:35:17 +01:00
parent 75ae7ede56
commit a288dee7df
28 changed files with 1852 additions and 1361 deletions
+536 -277
View File
@@ -1,7 +1,10 @@
/*
* http.hpp
* Simple HTTP request builder and response parser for MontaukOS
* Wraps tls::https_fetch() and raw sockets for ergonomic HTTP usage.
* 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
@@ -13,67 +16,113 @@
namespace http {
// ----------------------------------------------------------------------------
// Response
// ----------------------------------------------------------------------------
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 (200, 404, etc.) or -1 on error
const char* headers; // Pointer into raw buffer (header block)
int status; // HTTP status code, or -1 before a response is parsed
const char* headers; // Pointers into raw
int headers_len;
const char* body; // Pointer into raw buffer (body)
const char* body;
int body_len;
char* raw; // Owned buffer — caller must free with montauk::mfree()
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* s = start; s < end - 3; s++) {
if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n')
return s + 4;
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* s = start; s < end - 1; s++) {
if (s[0] == '\n' && s[1] == '\n')
return s + 2;
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 != ' ') p++;
if (p >= end || *p != ' ') return -1;
p++;
int code = 0;
int digits = 0;
while (digits < 3 && p < end && *p >= '0' && *p <= '9') {
code = code * 10 + (*p - '0');
p++;
digits++;
}
return digits == 3 ? code : -1;
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 >= 200) return start;
if (code < 100 || code == 101 || code >= 200) return start;
const char* next = find_header_block_end(start, end);
if (!next || next >= end) return nullptr;
if (end - next < 5) return nullptr;
if (!(next[0] == 'H' && next[1] == 'T' && next[2] == 'T' && next[3] == 'P' && next[4] == '/'))
return nullptr;
start = next;
}
}
@@ -83,295 +132,505 @@ inline const char* skip_informational_responses(const char* buf, int len) {
return start ? start : buf;
}
// Parse raw HTTP response in-place. Sets pointers into buf (does not copy).
// Skips leading informational 1xx responses such as "100 Continue".
// Returns status code, or -1 if unparseable.
inline int parse_response(char* buf, int len, Response* out) {
out->raw = buf;
out->raw_len = len;
out->status = -1;
out->headers = nullptr;
out->headers_len = 0;
out->body = nullptr;
out->body_len = 0;
const char* start = find_final_response_start(buf, len);
const char* end = buf + len;
if (!start || end - start < 12) return -1; // "HTTP/1.x NNN"
// Parse status code from "HTTP/1.x NNN"
int code = parse_status_code(start, end);
if (code < 0) return -1;
out->status = code;
// Headers start after the status line
const char* hdr_start = start;
while (hdr_start < end - 1) {
if (*hdr_start == '\r' && *(hdr_start + 1) == '\n') { hdr_start += 2; break; }
if (*hdr_start == '\n') { hdr_start++; break; }
hdr_start++;
}
out->headers = hdr_start;
// Find \r\n\r\n boundary between headers and body
const char* body = find_header_block_end(hdr_start, end);
if (body) {
if (body >= hdr_start + 4 &&
body[-4] == '\r' && body[-3] == '\n' && body[-2] == '\r' && body[-1] == '\n')
out->headers_len = (int)((body - 4) - hdr_start);
else if (body >= hdr_start + 2 &&
body[-2] == '\n' && body[-1] == '\n')
out->headers_len = (int)((body - 2) - hdr_start);
else
out->headers_len = (int)(body - hdr_start);
out->body = body;
out->body_len = len - (int)(out->body - buf);
return code;
}
// No body separator found — entire remainder is headers
out->headers_len = len - (int)(hdr_start - buf);
return code;
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;
}
// Find a header value by name (case-insensitive match on the name).
// Writes value into out_val (up to max_len), returns true if found.
inline bool get_header(const Response* resp, const char* name, char* out_val, int max_len) {
if (!resp->headers || resp->headers_len == 0) return false;
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 = resp->headers + resp->headers_len;
const char* end = p + resp->headers_len;
while (p < end) {
// Case-insensitive prefix match
bool match = true;
if (p + name_len >= end) { match = false; }
else {
for (int i = 0; i < name_len; i++) {
char a = p[i], b = name[i];
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) { match = false; break; }
}
if (match && p[name_len] != ':') match = false;
}
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* v = p + name_len + 1;
while (v < end && *v == ' ') v++; // skip OWS
int i = 0;
while (v < end && *v != '\r' && *v != '\n' && i < max_len - 1)
out_val[i++] = *v++;
out_val[i] = 0;
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;
}
// Skip to next line
while (p < end && *p != '\n') p++;
if (p < end) p++;
p = line_end;
while (p < end && (*p == '\r' || *p == '\n')) ++p;
}
return false;
}
// Free a response's raw buffer.
inline void free_response(Response* resp) {
if (resp->raw) { montauk::mfree(resp->raw); resp->raw = nullptr; }
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;
}
// ----------------------------------------------------------------------------
// Request builder (internal)
// ----------------------------------------------------------------------------
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) {
char* p = buf;
char* end = buf + buf_size - 1;
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;
auto append = [&](const char* s) {
while (*s && p < end) *p++ = *s++;
};
auto append_int = [&](int n) {
char tmp[16]; int ti = 0;
if (n == 0) { if (p < end) *p++ = '0'; return; }
while (n > 0) { tmp[ti++] = '0' + (n % 10); n /= 10; }
for (int j = ti - 1; j >= 0 && p < end; j--) *p++ = tmp[j];
};
// Request line
append(method); append(" "); append(path); append(" HTTP/1.1\r\n");
// Host
append("Host: "); append(host); append("\r\n");
// Content headers (for POST/PUT/PATCH)
if (body_data && body_len > 0) {
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) {
append("Content-Type: "); append(content_type); append("\r\n");
w.text("Content-Type: "); w.text(content_type); w.text("\r\n");
}
append("Content-Length: "); append_int(body_len); append("\r\n");
w.text("Content-Length: "); w.number(body_len); w.text("\r\n");
}
// Extra headers (caller-supplied, must include \r\n terminators)
if (extra_headers) append(extra_headers);
append("Connection: close\r\n");
append("\r\n");
int header_len = (int)(p - buf);
// Append body
if (body_data && body_len > 0 && header_len + body_len < buf_size) {
montauk::memcpy(p, body_data, body_len);
p += body_len;
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");
}
return (int)(p - buf);
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);
}
// ----------------------------------------------------------------------------
// Public API
// ----------------------------------------------------------------------------
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;
}
// GET request over HTTPS. Returns parsed response. Caller must free_response().
inline Response get(const char* host, const char* path,
const tls::TrustAnchors& tas,
int resp_buf_size = 32768,
const tls::TrustAnchors& tas, int response_buffer_size = 32768,
const char* extra_headers = nullptr,
tls::AbortCheckFn abort_check = nullptr) {
Response resp = {};
resp.status = -1;
uint32_t ip = montauk::resolve(host);
if (!ip) return resp;
char req[1024];
int reqLen = build_request(req, sizeof(req), "GET", host, path,
nullptr, nullptr, 0, extra_headers);
char* buf = (char*)montauk::malloc(resp_buf_size);
if (!buf) return resp;
int n = tls::https_fetch(host, ip, 443, req, reqLen, tas,
buf, resp_buf_size - 1, abort_check);
if (n <= 0) { montauk::mfree(buf); return resp; }
buf[n] = 0;
parse_response(buf, n, &resp);
return resp;
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);
}
// POST request over HTTPS. Returns parsed response. Caller must free_response().
inline Response post(const char* host, const char* path,
const char* content_type,
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 resp_buf_size = 32768,
const tls::TrustAnchors& tas, int response_buffer_size = 32768,
const char* extra_headers = nullptr,
tls::AbortCheckFn abort_check = nullptr) {
Response resp = {};
resp.status = -1;
uint32_t ip = montauk::resolve(host);
if (!ip) return resp;
int req_size = 1024 + body_len;
char* req = (char*)montauk::malloc(req_size);
if (!req) return resp;
int reqLen = build_request(req, req_size, "POST", host, path,
content_type, body_data, body_len,
extra_headers);
char* buf = (char*)montauk::malloc(resp_buf_size);
if (!buf) { montauk::mfree(req); return resp; }
int n = tls::https_fetch(host, ip, 443, req, reqLen, tas,
buf, resp_buf_size - 1, abort_check);
montauk::mfree(req);
if (n <= 0) { montauk::mfree(buf); return resp; }
buf[n] = 0;
parse_response(buf, n, &resp);
return resp;
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);
}
// Generic request over HTTPS (PUT, PATCH, DELETE, etc.).
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 resp_buf_size = 32768,
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) {
Response resp = {};
resp.status = -1;
uint32_t ip = montauk::resolve(host);
if (!ip) return resp;
int req_size = 1024 + (body_len > 0 ? body_len : 0);
char* req = (char*)montauk::malloc(req_size);
if (!req) return resp;
int reqLen = build_request(req, req_size, method, host, path,
content_type, body_data, body_len,
extra_headers);
char* buf = (char*)montauk::malloc(resp_buf_size);
if (!buf) { montauk::mfree(req); return resp; }
int n = tls::https_fetch(host, ip, 443, req, reqLen, tas,
buf, resp_buf_size - 1, abort_check);
montauk::mfree(req);
if (n <= 0) { montauk::mfree(buf); return resp; }
buf[n] = 0;
parse_response(buf, n, &resp);
return resp;
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);
}
// Plain HTTP (no TLS) GET over port 80.
inline Response get_plain(const char* host, const char* path,
int resp_buf_size = 32768,
int response_buffer_size = 32768,
const char* extra_headers = nullptr) {
Response resp = {};
resp.status = -1;
uint32_t ip = montauk::resolve(host);
if (!ip) return resp;
char req[1024];
int reqLen = build_request(req, sizeof(req), "GET", host, path,
nullptr, nullptr, 0, extra_headers);
int sock = montauk::socket(montauk::abi::SOCK_TCP);
if (sock < 0) return resp;
if (montauk::connect(sock, ip, 80) < 0) { montauk::closesocket(sock); return resp; }
montauk::send(sock, req, reqLen);
char* buf = (char*)montauk::malloc(resp_buf_size);
if (!buf) { montauk::closesocket(sock); return resp; }
int total = 0;
while (total < resp_buf_size - 1) {
int n = montauk::recv(sock, buf + total, resp_buf_size - 1 - total);
if (n <= 0) break;
total += n;
}
montauk::closesocket(sock);
if (total <= 0) { montauk::mfree(buf); return resp; }
buf[total] = 0;
parse_response(buf, total, &resp);
return resp;
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
+1
View File
@@ -23,6 +23,7 @@ struct TrustAnchors {
};
TrustAnchors load_trust_anchors();
void free_trust_anchors(TrustAnchors* tas);
void get_bearssl_time(uint32_t* days, uint32_t* seconds);
int tls_send_all(int fd, const unsigned char* data, size_t len);
int tls_recv_some(int fd, unsigned char* buf, size_t maxlen);
Binary file not shown.