37typedef enum : uint32_t {
72#if LIBCURL_VERSION_NUM >= 0x074D00
73 struct curl_blob ca_blob;
80 "mdl_net_curl_storage_t is too small for the private context");
82 "mdl_net_curl_storage_t is insufficiently aligned");
107 if ((c >=
'A') && (c <=
'Z')) {
108 return (
char)(c -
'A' +
'a');
132 for (; (i < line_len) && (prefix[i] !=
'\0'); ++i) {
137 return prefix[i] ==
'\0';
159 while ((start < line_len) && (line[start] !=
':')) {
162 if (start < line_len) {
165 while ((start < line_len) && ((line[start] ==
' ') || (line[start] ==
'\t'))) {
168 size_t end = line_len;
169 while ((end > start) && ((line[end - 1U] ==
'\r') || (line[end - 1U] ==
'\n') ||
170 (line[end - 1U] ==
' ') || (line[end - 1U] ==
'\t'))) {
173 size_t n = end - start;
177 for (
size_t i = 0U; i < n; ++i) {
178 out[i] = line[start + i];
202 if ((nitems != 0U) && (size > (SIZE_MAX / nitems))) {
205 const size_t len = size * nitems;
207 if (sink ==
nullptr) {
213 sink->
etag[0] =
'\0';
231 if ((nmemb != 0U) && (size > (SIZE_MAX / nmemb))) {
234 const size_t bytes = size * nmemb;
235 if (sink ==
nullptr) {
238 if ((sink->
len > sink->
cap) || (bytes > (sink->
cap - sink->
len))) {
250 if ((sink ==
nullptr) || (sink->
sink ==
nullptr) || (sink->
sink->
write ==
nullptr) ||
251 (sink->
sink->
ctx ==
nullptr)) {
254 if ((nmemb != 0U) && (size > (SIZE_MAX / nmemb))) {
258 const size_t bytes = size * nmemb;
263 uint32_t written = 0U;
265 sink->
sink->
write(sink->
sink->
ctx, (
const uint8_t*)data, (uint32_t)bytes, &written);
266 if ((error !=
k_ra8_ok) || (written != (uint32_t)bytes)) {
270 sink->
written += (uint64_t)written;
294 if ((curl_easy_getinfo(net->
curl, CURLINFO_EFFECTIVE_URL, &eff) != CURLE_OK) ||
327 char* conn_primary_ip,
330 int conn_primary_port,
334 (void)conn_primary_port;
335 (void)conn_local_port;
337 if (net ==
nullptr) {
338 return CURL_PREREQFUNC_ABORT;
342 return CURL_PREREQFUNC_ABORT;
345 return CURL_PREREQFUNC_ABORT;
347 return CURL_PREREQFUNC_OK;
366 return code == CURLE_OK;
389 const bool using_proxy = ((net->
socks5 !=
nullptr) && (net->
socks5[0] !=
'\0')) ||
390 ((net->
proxy !=
nullptr) && (net->
proxy[0] !=
'\0'));
397 bool proxy_ok =
true;
398 if ((net->
socks5 !=
nullptr) && (net->
socks5[0] !=
'\0')) {
401 internal_ok_code(curl_easy_setopt(curl, CURLOPT_PROXYTYPE, (
long)CURLPROXY_SOCKS5_HOSTNAME));
402 }
else if ((net->
proxy !=
nullptr) && (net->
proxy[0] !=
'\0')) {
408#if LIBCURL_VERSION_NUM >= 0x074D00
409 struct curl_blob* ca_blob = &net->ca_blob;
411 struct curl_blob* ca_blob =
nullptr;
417 const bool options_ok =
418 proxy_ok &&
internal_ok_code(curl_easy_setopt(curl, CURLOPT_PROTOCOLS_STR,
"http,https")) &&
419 internal_ok_code(curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS_STR,
"http,https")) &&
423 internal_ok_code(curl_easy_setopt(curl, CURLOPT_NETRC, (
long)CURL_NETRC_IGNORED)) &&
452 const bool options_ok =
489 const size_t i = headers->
count;
491 __builtin_snprintf(headers->
values[i],
sizeof(headers->
values[i]),
"%s: %s", name, value);
492 if ((n < 0) || ((
size_t)n >=
sizeof(headers->
values[i]))) {
496 headers->
nodes[i].next =
nullptr;
500 headers->
nodes[i - 1U].next = &headers->
nodes[i];
502 headers->
count += 1U;
565 CURL* curl = net->
curl;
594 if (headers->
head ==
nullptr) {
597 struct curl_slist*
const no_headers =
nullptr;
598 (void)curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_headers);
606 if (code == CURLE_OPERATION_TIMEDOUT) {
609 if (code != CURLE_OK) {
640 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
641 if (resp !=
nullptr) {
644 (void)__builtin_snprintf(resp->
etag,
sizeof(resp->
etag),
"%s", hdr->
etag);
692 buf_sink_t sink = {.buf = buf, .cap = cap, .len = 0U, .overflow =
false};
704 const CURLcode code = curl_easy_perform(net->
curl);
711 if (sink.
len < cap) {
712 buf[sink.
len] =
'\0';
714 if (out_len !=
nullptr) {
768 curl_easy_setopt(net->
curl, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)net->
max_bytes)) ||
776 const CURLcode code = curl_easy_perform(net->
curl);
782 if ((rc ==
k_ra8_ok) && (out_len !=
nullptr)) {
783 *out_len = (size_t)body.
written;
803 if (net ==
nullptr) {
806 if (net->
curl !=
nullptr) {
807 curl_easy_cleanup(net->
curl);
823 if ((net ==
nullptr) || (storage ==
nullptr)) {
828 static bool s_global_ready =
false;
829 if (!s_global_ready) {
830 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
833 s_global_ready =
true;
837 if (policy !=
nullptr) {
846 ctx->
curl = curl_easy_init();
847 if (ctx->
curl ==
nullptr) {
855 curl_easy_cleanup(ctx->
curl);
ra8_err_t priv_mdl_net_classify_http(long status)
Classify one observed HTTP response status.
Streaming HTTP(S) GET seam (a real function-pointer vtable) for the host manga downloader (v0).
@ k_mdl_last_mod_max
Raw Last-Modified header value buffer bytes.
@ k_mdl_content_type_max
Raw Content-Type header value buffer bytes.
@ k_mdl_retry_after_max
Raw Retry-After header value buffer bytes.
@ k_mdl_etag_max
Raw ETag header value buffer bytes.
static const mdl_net_vtable_t s_curl_vtable
The libcurl backend's immutable method table.
static bool internal_ok_code(CURLcode code)
True if code is CURLE_OK (setopt success).
static char internal_ascii_lower(char c)
Lower-case an ASCII byte (locale-independent).
static bool internal_apply_req(mdl_curl_ctx_t *net, const char *url, const mdl_net_req_t *req)
Apply the per-request options shared by both fetch paths.
ra8_err_t priv_mdl_net_curl_classify(CURLcode code, bool overflow, long status)
Map a completed libcurl transfer to an ra8_err_t (pure classifier).
static ra8_err_t internal_apply_security_opts(CURL *curl, mdl_curl_ctx_t *net)
Apply the security-critical, life-of-handle options (all checked).
static ra8_err_t internal_finish_transfer(CURL *curl, CURLcode code, bool overflow, const hdr_sink_t *hdr, mdl_net_resp_t *resp)
Read status and captured headers, fill resp, and classify the result.
static void internal_release_req_headers(CURL *curl, const mdl_req_headers_t *headers)
Detach request headers; their backing remains valid in backend state.
mdl_curl_limits_t
Backend tunables.
@ k_origin_host_max
Stored origin-host buffer bytes.
@ k_curl_max_redirects
Redirect hops to follow.
@ k_low_speed_bytes
Below this many B/s...
@ k_request_header_count
ETag and Last-Modified header slots.
@ k_http_too_many_req
Too Many Requests (throttle).
@ k_http_client_err_min
First HTTP status treated as an error.
@ k_connect_timeout_ms
TCP/TLS connect budget, ms.
@ k_http_not_found
Absent resource (client error).
@ k_low_speed_secs
...for this long, abort a stalled xfer.
@ k_http_not_modified
Conditional GET reused the held entity.
@ k_request_header_max
Bytes in one conditional-request header.
@ k_http_server_err_min
First HTTP status that is a server error.
@ k_http_unavailable
Service Unavailable (throttle).
static int internal_on_prereq(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port)
Refuse non-public peers and forbidden cross-host redirects before transfer.
static void internal_header_value(const char *line, size_t line_len, char *out, size_t cap)
Copy a header value (after the colon), trimmed of CR/LF/space, bounded.
static bool internal_header_is(const char *line, size_t line_len, const char *prefix)
True when line begins with prefix, ASCII case-insensitively.
size_t priv_mdl_net_curl_buf_write(char *data, size_t size, size_t nmemb, void *user)
libcurl write callback: append into a bounded buffer, abort on cap.
static ra8_err_t internal_curl_get_body(void *ctx, const char *url, const mdl_net_req_t *req, mdl_net_body_sink_t *sink, size_t *out_len, mdl_net_resp_t *resp)
Vtable method: GET url through a caller-owned body sink.
ra8_err_t mdl_net_curl_init(mdl_net_iface_t *net, mdl_net_curl_storage_t *storage, const mdl_net_policy_t *policy)
Initialise a libcurl-backed host network interface in caller storage.
size_t priv_mdl_net_curl_body_write(char *data, size_t size, size_t nmemb, void *user)
Adapt one libcurl body chunk to an injected bounded sink.
static size_t internal_on_header(char *buffer, size_t size, size_t nitems, void *user)
Capture selected headers from the final libcurl response.
static ra8_err_t internal_apply_behavior_opts(CURL *curl, const mdl_curl_ctx_t *net)
Apply the behavioural, life-of-handle options (all checked).
static bool internal_build_req_headers(const mdl_net_req_t *req, mdl_req_headers_t *headers)
Build conditional request headers (If-None-Match, If-Modified-Since).
static bool internal_redirect_host_ok(mdl_curl_ctx_t *net)
Verify that libcurl's effective redirect host is permitted.
static bool internal_append_req_header(mdl_req_headers_t *headers, const char *name, const char *value)
Append one bounded header to caller-owned stable list storage.
static void internal_curl_destroy(void *ctx)
Release the libcurl handle and clear this backend state.
static ra8_err_t internal_curl_get_buf(void *ctx, const char *url, const mdl_net_req_t *req, char *buf, size_t cap, size_t *out_len, mdl_net_resp_t *resp)
Vtable method: GET url into a caller buffer.
Factory for the concrete libcurl-backed mdl_net_iface_t backend.
struct mdl_net_curl_storage mdl_net_curl_storage_t
@ k_mdl_net_curl_storage_bytes
Private backend storage capacity.
ra8_err_t priv_mdl_net_curl_apply_cookies(CURL *curl, const mdl_net_bytes_t *cookies)
Enable the cookie engine and import validated caller-owned rows.
ra8_err_t priv_mdl_net_curl_apply_ca_blob(CURL *curl, const mdl_net_bytes_t *ca_pem, struct curl_blob *blob)
Bind complete caller-owned CA PEM bytes with NOCOPY semantics.
Module-private libcurl-backend helpers promoted for host unit tests.
Private HTTP classification shared by downloader network backends.
Pure URL / address safety predicates for the libcurl backend.
bool mdl_url_host(const char *url, char *out, size_t cap)
Extract the authority (host and optional port) from an http(s) URL.
bool mdl_url_scheme_allowed(const char *url)
True when url uses an allowlisted scheme (http:// or https://).
bool mdl_addr_is_fetchable(mdl_addr_class_t cls, bool allow_private)
Decide whether an address class is fetchable under the active policy.
bool mdl_size_exceeds(uint64_t have, uint64_t add, uint64_t cap)
True when appending add bytes to have would exceed cap.
mdl_addr_class_t mdl_classify_ip(const char *ip)
Classify a resolved peer IP string into a mdl_addr_class_t.
mdl_addr_class_t
Reachability class of a resolved peer address.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
@ k_ra8_fail
Generic unspecified failure.
@ k_ra8_err_invalid_arg
Invalid function argument.
@ k_ra8_err_invalid_state
Module in wrong state for requested operation.
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
@ k_ra8_err_timeout
Operation exceeded its time budget.
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
Bounded-buffer sink state for a page fetch.
char * buf
Destination buffer.
size_t len
Bytes written so far.
size_t cap
Capacity in bytes.
bool overflow
Set once the body exceeds cap.
Response-header capture sink: keeps the final response's Retry-After.
char content_type[k_mdl_content_type_max]
Raw Content-Type, "" when absent.
char etag[k_mdl_etag_max]
Raw ETag, "" when absent.
char retry_after[k_mdl_retry_after_max]
Raw value, "" when absent.
char last_modified[k_mdl_last_mod_max]
Raw Last-Modified, "" when absent.
Private state of the libcurl backend (the vtable's ctx).
const char * socks5
SOCKS5 proxy URL.
mdl_req_headers_t request_headers
Stable conditional headers.
uint64_t max_bytes
Per-response cap (0 = none).
mdl_net_bytes_t cookies
Imported cookie-file bytes.
mdl_net_bytes_t ca_pem
Caller-owned CA PEM bytes.
char origin_host[k_origin_host_max]
Host of the current request.
CURL * curl
Reused easy handle.
const char * proxy
HTTP/HTTPS proxy URL.
bool allow_private
SSRF opt-in (private peers).
bool allow_cross_host
Cross-host redirect opt-in.
Caller-owned lifecycle and write seam for one response body.
mdl_net_body_write_fn write
Consume one response chunk.
void * ctx
Caller-owned callback state.
Read-only caller-owned byte view retained by a network backend.
Test-visible bounded adapter from libcurl chunks to an injected sink.
ra8_err_t sink_error
First callback failure, or k_ra8_ok.
uint64_t written
Bytes accepted across completed chunks.
bool overflow
Arithmetic or configured cap was hit.
uint64_t cap
Response cap, or zero for no cap.
mdl_net_body_sink_t * sink
Borrowed injected body sink.
uint8_t bytes[k_mdl_net_curl_storage_bytes]
Private context bytes.
A network backend handle: an immutable method table plus its state.
Session-wide security policy for the network backend.
mdl_net_bytes_t cookies
Newline-delimited input cookies.
bool allow_cross_host_redirect
Permit a redirect to a different host.
uint64_t max_response_bytes
Per-response byte cap (0 = unlimited).
const char * socks5
SOCKS5 proxy URL, or NULL for none.
mdl_net_bytes_t ca_pem
Complete PEM CA bundle, or an empty view.
const char * proxy
HTTP/HTTPS proxy URL, or NULL for none.
bool allow_private_hosts
Permit loopback/private/link-local peers.
Per-request session parameters.
const char * referer
Referer header value, or NULL to omit.
const char * if_none_match
If-None-Match conditional header (ETag), or NULL.
const char * if_modified_since
If-Modified-Since header (Last-Modified), or NULL.
const char * user_agent
Session User-Agent; never per-request random.
uint32_t timeout_ms
Whole-request time budget, milliseconds.
Per-transfer response metadata surfaced to the politeness governor.
long status
Finished transfer's HTTP status, 0 if none.
char retry_after[k_mdl_retry_after_max]
Raw Retry-After value, "" when absent.
char etag[k_mdl_etag_max]
Raw ETag value, "" when absent.
char last_modified[k_mdl_last_mod_max]
Raw Last-Modified value, "" when absent.
char content_type[k_mdl_content_type_max]
Raw Content-Type value, "" when absent.
Method table one network backend registers (Dependency Inversion).