ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_net_curl.c
Go to the documentation of this file.
1
24#include "mdl_net_curl.h"
25
26#include <curl/curl.h>
27#include <limits.h>
28#include <string.h>
29
30#include "mdl_net.h"
32#include "mdl_net_internal.h"
33#include "mdl_url_guard.h"
34#include "ra8_attributes.h"
35
52
54typedef struct {
55 struct curl_slist nodes[k_request_header_count];
58 struct curl_slist* head;
59 size_t count;
61
63typedef struct {
64 CURL* curl;
67 uint64_t max_bytes;
68 const char* proxy;
69 const char* socks5;
72#if LIBCURL_VERSION_NUM >= 0x074D00
73 struct curl_blob ca_blob;
74#endif
78
79static_assert(sizeof(mdl_curl_ctx_t) <= k_mdl_net_curl_storage_bytes,
80 "mdl_net_curl_storage_t is too small for the private context");
81static_assert(alignof(mdl_net_curl_storage_t) >= alignof(mdl_curl_ctx_t),
82 "mdl_net_curl_storage_t is insufficiently aligned");
83
91
106{
107 if ((c >= 'A') && (c <= 'Z')) {
108 return (char)(c - 'A' + 'a');
109 }
110 return c;
111}
112
129RA8_INTERNAL static bool internal_header_is(const char* line, size_t line_len, const char* prefix)
130{
131 size_t i = 0U;
132 for (; (i < line_len) && (prefix[i] != '\0'); ++i) {
133 if (internal_ascii_lower(line[i]) != prefix[i]) {
134 return false;
135 }
136 }
137 return prefix[i] == '\0';
138}
139
155RA8_INTERNAL static void
156internal_header_value(const char* line, size_t line_len, char* out, size_t cap)
157{
158 size_t start = 0U;
159 while ((start < line_len) && (line[start] != ':')) {
160 ++start;
161 }
162 if (start < line_len) {
163 ++start; /* skip the colon */
164 }
165 while ((start < line_len) && ((line[start] == ' ') || (line[start] == '\t'))) {
166 ++start;
167 }
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'))) {
171 --end;
172 }
173 size_t n = end - start;
174 if (n >= cap) {
175 n = cap - 1U;
176 }
177 for (size_t i = 0U; i < n; ++i) {
178 out[i] = line[start + i];
179 }
180 out[n] = '\0';
181}
182
200RA8_INTERNAL static size_t internal_on_header(char* buffer, size_t size, size_t nitems, void* user)
201{
202 if ((nitems != 0U) && (size > (SIZE_MAX / nitems))) {
203 return 0U;
204 }
205 const size_t len = size * nitems;
206 hdr_sink_t* sink = (hdr_sink_t*)user;
207 if (sink == nullptr) {
208 return len;
209 }
210 /* A new status line starts a fresh response (redirect chain): drop stale values. */
211 if (internal_header_is(buffer, len, "http/")) {
212 sink->retry_after[0] = '\0';
213 sink->etag[0] = '\0';
214 sink->last_modified[0] = '\0';
215 sink->content_type[0] = '\0';
216 } else if (internal_header_is(buffer, len, "retry-after:")) {
217 internal_header_value(buffer, len, sink->retry_after, sizeof(sink->retry_after));
218 } else if (internal_header_is(buffer, len, "etag:")) {
219 internal_header_value(buffer, len, sink->etag, sizeof(sink->etag));
220 } else if (internal_header_is(buffer, len, "last-modified:")) {
221 internal_header_value(buffer, len, sink->last_modified, sizeof(sink->last_modified));
222 } else if (internal_header_is(buffer, len, "content-type:")) {
223 internal_header_value(buffer, len, sink->content_type, sizeof(sink->content_type));
224 }
225 return len;
226}
227
228RA8_PRIV size_t priv_mdl_net_curl_buf_write(char* data, size_t size, size_t nmemb, void* user)
229{
230 buf_sink_t* sink = (buf_sink_t*)user;
231 if ((nmemb != 0U) && (size > (SIZE_MAX / nmemb))) {
232 return 0U;
233 }
234 const size_t bytes = size * nmemb;
235 if (sink == nullptr) {
236 return 0U;
237 }
238 if ((sink->len > sink->cap) || (bytes > (sink->cap - sink->len))) {
239 sink->overflow = true;
240 return 0U; /* Signals libcurl to abort the transfer. */
241 }
242 memcpy(sink->buf + sink->len, data, bytes);
243 sink->len += bytes;
244 return nmemb;
245}
246
247RA8_PRIV size_t priv_mdl_net_curl_body_write(char* data, size_t size, size_t nmemb, void* user)
248{
250 if ((sink == nullptr) || (sink->sink == nullptr) || (sink->sink->write == nullptr) ||
251 (sink->sink->ctx == nullptr)) {
252 return 0U;
253 }
254 if ((nmemb != 0U) && (size > (SIZE_MAX / nmemb))) {
255 sink->overflow = true;
256 return 0U;
257 }
258 const size_t bytes = size * nmemb;
259 if ((bytes > (size_t)UINT32_MAX) || mdl_size_exceeds(sink->written, (uint64_t)bytes, sink->cap)) {
260 sink->overflow = true;
261 return 0U;
262 }
263 uint32_t written = 0U;
264 const ra8_err_t error =
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)) {
267 sink->sink_error = (error != k_ra8_ok) ? error : k_ra8_err_invalid_state;
268 return 0U;
269 }
270 sink->written += (uint64_t)written;
271 return bytes;
272}
273
289{
290 if (net->allow_cross_host || (net->origin_host[0] == '\0')) {
291 return true;
292 }
293 char* eff = nullptr;
294 if ((curl_easy_getinfo(net->curl, CURLINFO_EFFECTIVE_URL, &eff) != CURLE_OK) ||
295 (eff == nullptr)) {
296 return false; /* Security decision cannot be made: fail closed. */
297 }
298 char host[k_origin_host_max];
299 if (!mdl_url_host(eff, host, sizeof(host))) {
300 return false;
301 }
302 return strcmp(host, net->origin_host) == 0;
303}
304
305/* The libcurl CURLOPT_PREREQFUNCTION ABI fixes these parameter types as
306 * non-const `char*`; conn_local_ip is unused here but cannot be re-qualified. */
326 void* clientp,
327 char* conn_primary_ip,
328 char*
329 conn_local_ip, // NOLINT(readability-non-const-parameter) -- libcurl prereq callback ABI fixes char*.
330 int conn_primary_port,
331 int conn_local_port)
332{
333 (void)conn_local_ip;
334 (void)conn_primary_port;
335 (void)conn_local_port;
336 mdl_curl_ctx_t* net = (mdl_curl_ctx_t*)clientp;
337 if (net == nullptr) {
338 return CURL_PREREQFUNC_ABORT;
339 }
340 const mdl_addr_class_t cls = mdl_classify_ip(conn_primary_ip);
341 if (!mdl_addr_is_fetchable(cls, net->allow_private)) {
342 return CURL_PREREQFUNC_ABORT;
343 }
344 if (!internal_redirect_host_ok(net)) {
345 return CURL_PREREQFUNC_ABORT;
346 }
347 return CURL_PREREQFUNC_OK;
348}
349
364RA8_INTERNAL static bool internal_ok_code(CURLcode code)
365{
366 return code == CURLE_OK;
367}
368
388{
389 const bool using_proxy = ((net->socks5 != nullptr) && (net->socks5[0] != '\0')) ||
390 ((net->proxy != nullptr) && (net->proxy[0] != '\0'));
391 /* CURLOPT_PREREQFUNCTION sees the proxy peer, not the proxy's resolution of
392 * the target. Requiring the explicit private-host escape hatch avoids
393 * presenting the default policy as an SSRF guarantee it cannot provide. */
394 if (using_proxy && !net->allow_private) {
395 return k_ra8_fail;
396 }
397 bool proxy_ok = true;
398 if ((net->socks5 != nullptr) && (net->socks5[0] != '\0')) {
399 proxy_ok =
400 internal_ok_code(curl_easy_setopt(curl, CURLOPT_PROXY, net->socks5)) &&
401 internal_ok_code(curl_easy_setopt(curl, CURLOPT_PROXYTYPE, (long)CURLPROXY_SOCKS5_HOSTNAME));
402 } else if ((net->proxy != nullptr) && (net->proxy[0] != '\0')) {
403 proxy_ok = internal_ok_code(curl_easy_setopt(curl, CURLOPT_PROXY, net->proxy));
404 } else {
405 proxy_ok = internal_ok_code(curl_easy_setopt(curl, CURLOPT_PROXY, ""));
406 }
407
408#if LIBCURL_VERSION_NUM >= 0x074D00
409 struct curl_blob* ca_blob = &net->ca_blob;
410#else
411 struct curl_blob* ca_blob = nullptr;
412#endif
413 const ra8_err_t ca_error = priv_mdl_net_curl_apply_ca_blob(curl, &net->ca_pem, ca_blob);
414 if (ca_error != k_ra8_ok) {
415 return ca_error;
416 }
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")) &&
420 /* Defaults are correct today; assert them so the guarantee is in code. */
421 internal_ok_code(curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L)) &&
422 internal_ok_code(curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L)) &&
423 internal_ok_code(curl_easy_setopt(curl, CURLOPT_NETRC, (long)CURL_NETRC_IGNORED)) &&
424 internal_ok_code(curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)) &&
425 internal_ok_code(curl_easy_setopt(curl, CURLOPT_MAXREDIRS, (long)k_curl_max_redirects)) &&
426 internal_ok_code(curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, internal_on_prereq)) &&
427 internal_ok_code(curl_easy_setopt(curl, CURLOPT_PREREQDATA, net));
428 return options_ok ? k_ra8_ok : k_ra8_fail;
429}
430
451{
452 const bool options_ok =
453 internal_ok_code(curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "")) &&
454 internal_ok_code(curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L)) &&
455 internal_ok_code(curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, internal_on_header)) &&
457 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, (long)k_connect_timeout_ms)) &&
458 internal_ok_code(curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, (long)k_low_speed_bytes)) &&
459 internal_ok_code(curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, (long)k_low_speed_secs));
460 return options_ok ? priv_mdl_net_curl_apply_cookies(curl, &net->cookies) : k_ra8_fail;
461}
462
483RA8_INTERNAL static bool
484internal_append_req_header(mdl_req_headers_t* headers, const char* name, const char* value)
485{
486 if (headers->count >= (size_t)k_request_header_count) {
487 return false;
488 }
489 const size_t i = headers->count;
490 const int n =
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]))) {
493 return false;
494 }
495 headers->nodes[i].data = headers->values[i];
496 headers->nodes[i].next = nullptr;
497 if (i == 0U) {
498 headers->head = &headers->nodes[i];
499 } else {
500 headers->nodes[i - 1U].next = &headers->nodes[i];
501 }
502 headers->count += 1U;
503 return true;
504}
505
525 mdl_req_headers_t* headers)
526{
527 *headers = (mdl_req_headers_t){};
528 if ((req != nullptr) && (req->if_none_match != nullptr) && (req->if_none_match[0] != '\0')) {
529 if (!internal_append_req_header(headers, "If-None-Match", req->if_none_match)) {
530 return false;
531 }
532 }
533 if ((req != nullptr) && (req->if_modified_since != nullptr) &&
534 (req->if_modified_since[0] != '\0')) {
535 if (!internal_append_req_header(headers, "If-Modified-Since", req->if_modified_since)) {
536 return false;
537 }
538 }
539 return true;
540}
541
558RA8_INTERNAL static bool
559internal_apply_req(mdl_curl_ctx_t* net, const char* url, const mdl_net_req_t* req)
560{
561 if (!mdl_url_host(url, net->origin_host, sizeof(net->origin_host))) {
562 net->origin_host[0] = '\0';
563 return false;
564 }
565 CURL* curl = net->curl;
566 bool ok = internal_ok_code(curl_easy_setopt(curl, CURLOPT_URL, url)) &&
567 internal_ok_code(curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, (long)req->timeout_ms)) &&
568 /* CURLOPT_REFERER with NULL clears any prior value -- what we want. */
569 internal_ok_code(curl_easy_setopt(curl, CURLOPT_REFERER, req->referer));
570 if (ok && (req->user_agent != nullptr)) {
571 ok = internal_ok_code(curl_easy_setopt(curl, CURLOPT_USERAGENT, req->user_agent));
572 }
573 return ok;
574}
575
592RA8_INTERNAL static void internal_release_req_headers(CURL* curl, const mdl_req_headers_t* headers)
593{
594 if (headers->head == nullptr) {
595 return;
596 }
597 struct curl_slist* const no_headers = nullptr;
598 (void)curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_headers);
599}
600
601RA8_PRIV ra8_err_t priv_mdl_net_curl_classify(CURLcode code, bool overflow, long status)
602{
603 if (overflow) {
604 return k_ra8_err_no_mem;
605 }
606 if (code == CURLE_OPERATION_TIMEDOUT) {
607 return k_ra8_err_timeout;
608 }
609 if (code != CURLE_OK) {
610 return k_ra8_fail;
611 }
612 return priv_mdl_net_classify_http(status);
613}
614
634 CURLcode code,
635 bool overflow,
636 const hdr_sink_t* hdr,
637 mdl_net_resp_t* resp)
638{
639 long status = 0;
640 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
641 if (resp != nullptr) {
642 resp->status = status;
643 (void)__builtin_snprintf(resp->retry_after, sizeof(resp->retry_after), "%s", hdr->retry_after);
644 (void)__builtin_snprintf(resp->etag, sizeof(resp->etag), "%s", hdr->etag);
645 (void)__builtin_snprintf(resp->last_modified,
646 sizeof(resp->last_modified),
647 "%s",
648 hdr->last_modified);
649 (void)
650 __builtin_snprintf(resp->content_type, sizeof(resp->content_type), "%s", hdr->content_type);
651 }
652 return priv_mdl_net_curl_classify(code, overflow, status);
653}
654
676 const char* url,
677 const mdl_net_req_t* req,
678 char* buf,
679 size_t cap,
680 size_t* out_len,
681 mdl_net_resp_t* resp)
682{
683 mdl_curl_ctx_t* net = (mdl_curl_ctx_t*)ctx;
684 if (!mdl_url_scheme_allowed(url)) {
685 return k_ra8_err_invalid_arg; /* refuse file://, gopher://, ... before curl */
686 }
687
690 }
691 struct curl_slist* const req_headers = net->request_headers.head;
692 buf_sink_t sink = {.buf = buf, .cap = cap, .len = 0U, .overflow = false};
693 hdr_sink_t hdr = {};
694 if (!internal_apply_req(net, url, req) ||
695 !internal_ok_code(curl_easy_setopt(net->curl, CURLOPT_HTTPHEADER, req_headers)) ||
696 !internal_ok_code(curl_easy_setopt(net->curl, CURLOPT_HEADERDATA, &hdr)) ||
698 curl_easy_setopt(net->curl, CURLOPT_WRITEFUNCTION, priv_mdl_net_curl_buf_write)) ||
699 !internal_ok_code(curl_easy_setopt(net->curl, CURLOPT_WRITEDATA, &sink))) {
701 return k_ra8_fail;
702 }
703
704 const CURLcode code = curl_easy_perform(net->curl);
705 const ra8_err_t rc = internal_finish_transfer(net->curl, code, sink.overflow, &hdr, resp);
707 if (rc != k_ra8_ok) {
708 return rc;
709 }
710
711 if (sink.len < cap) {
712 buf[sink.len] = '\0'; /* NUL-terminate when room remains. */
713 }
714 if (out_len != nullptr) {
715 *out_len = sink.len;
716 }
717 return k_ra8_ok;
718}
719
741 const char* url,
742 const mdl_net_req_t* req,
744 size_t* out_len,
745 mdl_net_resp_t* resp)
746{
747 mdl_curl_ctx_t* net = (mdl_curl_ctx_t*)ctx;
748 if (!mdl_url_scheme_allowed(url)) {
750 }
751
754 }
755 struct curl_slist* const req_headers = net->request_headers.head;
756 mdl_net_curl_body_state_t body = {.sink = sink,
757 .written = 0U,
758 .cap = net->max_bytes,
759 .sink_error = k_ra8_ok,
760 .overflow = false};
761 hdr_sink_t hdr = {};
762 /* MAXFILESIZE_LARGE checks an advertised Content-Length up front; 0 disables
763 * it, exactly as cap == 0 disables the callback check below. */
764 if (!internal_apply_req(net, url, req) ||
765 !internal_ok_code(curl_easy_setopt(net->curl, CURLOPT_HTTPHEADER, req_headers)) ||
766 !internal_ok_code(curl_easy_setopt(net->curl, CURLOPT_HEADERDATA, &hdr)) ||
768 curl_easy_setopt(net->curl, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)net->max_bytes)) ||
770 curl_easy_setopt(net->curl, CURLOPT_WRITEFUNCTION, priv_mdl_net_curl_body_write)) ||
771 !internal_ok_code(curl_easy_setopt(net->curl, CURLOPT_WRITEDATA, &body))) {
773 return k_ra8_fail;
774 }
775
776 const CURLcode code = curl_easy_perform(net->curl);
777 ra8_err_t rc = internal_finish_transfer(net->curl, code, body.overflow, &hdr, resp);
779 if (!body.overflow && (body.sink_error != k_ra8_ok)) {
780 rc = body.sink_error;
781 }
782 if ((rc == k_ra8_ok) && (out_len != nullptr)) {
783 *out_len = (size_t)body.written;
784 }
785 return rc;
786}
787
801{
802 mdl_curl_ctx_t* net = (mdl_curl_ctx_t*)ctx;
803 if (net == nullptr) {
804 return;
805 }
806 if (net->curl != nullptr) {
807 curl_easy_cleanup(net->curl);
808 }
809 *net = (mdl_curl_ctx_t){};
810}
811
814 .get_buf = internal_curl_get_buf,
815 .get_body = internal_curl_get_body,
816 .destroy = internal_curl_destroy,
817};
818
820 mdl_net_curl_storage_t* storage,
821 const mdl_net_policy_t* policy)
822{
823 if ((net == nullptr) || (storage == nullptr)) {
825 }
826 *net = (mdl_net_iface_t){};
827 *storage = (mdl_net_curl_storage_t){};
828 static bool s_global_ready = false;
829 if (!s_global_ready) {
830 if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) {
831 return k_ra8_fail;
832 }
833 s_global_ready = true;
834 }
835
836 mdl_curl_ctx_t* ctx = (mdl_curl_ctx_t*)storage->bytes;
837 if (policy != nullptr) {
838 ctx->allow_private = policy->allow_private_hosts;
840 ctx->max_bytes = policy->max_response_bytes;
841 ctx->proxy = policy->proxy;
842 ctx->socks5 = policy->socks5;
843 ctx->cookies = policy->cookies;
844 ctx->ca_pem = policy->ca_pem;
845 }
846 ctx->curl = curl_easy_init();
847 if (ctx->curl == nullptr) {
848 return k_ra8_fail;
849 }
850 ra8_err_t init_error = internal_apply_security_opts(ctx->curl, ctx);
851 if (init_error == k_ra8_ok) {
852 init_error = internal_apply_behavior_opts(ctx->curl, ctx);
853 }
854 if (init_error != k_ra8_ok) {
855 curl_easy_cleanup(ctx->curl);
856 *ctx = (mdl_curl_ctx_t){};
857 return init_error;
858 }
859 net->vtable = &s_curl_vtable;
860 net->ctx = ctx;
861 return k_ra8_ok;
862}
ra8_err_t priv_mdl_net_classify_http(long status)
Classify one observed HTTP response status.
Definition mdl_net.c:31
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.
Definition mdl_net.h:95
@ k_mdl_content_type_max
Raw Content-Type header value buffer bytes.
Definition mdl_net.h:96
@ k_mdl_retry_after_max
Raw Retry-After header value buffer bytes.
Definition mdl_net.h:93
@ k_mdl_etag_max
Raw ETag header value buffer bytes.
Definition mdl_net.h:94
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).
Definition ra8_err.h:142
@ k_ra8_fail
Generic unspecified failure.
Definition ra8_err.h:133
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_invalid_state
Module in wrong state for requested operation.
Definition ra8_err.h:161
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_timeout
Operation exceeded its time budget.
Definition ra8_err.h:188
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
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.
Definition mdl_net.h:162
mdl_net_body_write_fn write
Consume one response chunk.
Definition mdl_net.h:164
void * ctx
Caller-owned callback state.
Definition mdl_net.h:165
Read-only caller-owned byte view retained by a network backend.
Definition mdl_net.h:60
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.
Definition mdl_net.h:81
mdl_net_bytes_t cookies
Newline-delimited input cookies.
Definition mdl_net.h:87
bool allow_cross_host_redirect
Permit a redirect to a different host.
Definition mdl_net.h:83
uint64_t max_response_bytes
Per-response byte cap (0 = unlimited).
Definition mdl_net.h:84
const char * socks5
SOCKS5 proxy URL, or NULL for none.
Definition mdl_net.h:86
mdl_net_bytes_t ca_pem
Complete PEM CA bundle, or an empty view.
Definition mdl_net.h:88
const char * proxy
HTTP/HTTPS proxy URL, or NULL for none.
Definition mdl_net.h:85
bool allow_private_hosts
Permit loopback/private/link-local peers.
Definition mdl_net.h:82
Per-request session parameters.
Definition mdl_net.h:43
const char * referer
Referer header value, or NULL to omit.
Definition mdl_net.h:45
const char * if_none_match
If-None-Match conditional header (ETag), or NULL.
Definition mdl_net.h:46
const char * if_modified_since
If-Modified-Since header (Last-Modified), or NULL.
Definition mdl_net.h:47
const char * user_agent
Session User-Agent; never per-request random.
Definition mdl_net.h:44
uint32_t timeout_ms
Whole-request time budget, milliseconds.
Definition mdl_net.h:48
Per-transfer response metadata surfaced to the politeness governor.
Definition mdl_net.h:120
long status
Finished transfer's HTTP status, 0 if none.
Definition mdl_net.h:121
char retry_after[k_mdl_retry_after_max]
Raw Retry-After value, "" when absent.
Definition mdl_net.h:122
char etag[k_mdl_etag_max]
Raw ETag value, "" when absent.
Definition mdl_net.h:123
char last_modified[k_mdl_last_mod_max]
Raw Last-Modified value, "" when absent.
Definition mdl_net.h:124
char content_type[k_mdl_content_type_max]
Raw Content-Type value, "" when absent.
Definition mdl_net.h:125
Method table one network backend registers (Dependency Inversion).
Definition mdl_net.h:186
Fixed, stable request-header list retained by the backend context.
char values[k_request_header_count][k_request_header_max]
Stable conditional-request header text.
size_t count
Active node count.
struct curl_slist * head
First active node.
struct curl_slist nodes[k_request_header_count]
libcurl list nodes.