ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_net_curl_credentials.c
Go to the documentation of this file.
1
9#include <curl/curl.h>
10#include <string.h>
11
12#include "mdl_net.h"
14#include "ra8_attributes.h"
15
23
30
47RA8_INTERNAL static char internal_ascii_lower(char value)
48{
49 return (char)(((value >= 'A') && (value <= 'Z')) ? (value - 'A' + 'a') : value);
50}
51
70RA8_INTERNAL static bool
71internal_ascii_has_prefix(const char* data, size_t length, const char* prefix)
72{
73 size_t index = 0U;
74 while ((index < length) && (prefix[index] != '\0')) {
75 if (internal_ascii_lower(data[index]) != prefix[index]) {
76 return false;
77 }
78 ++index;
79 }
80 return prefix[index] == '\0';
81}
82
101RA8_INTERNAL static bool
102internal_cookie_field_is(const char* data, size_t length, const char* literal)
103{
104 const size_t literal_length = strlen(literal);
105 return (length == literal_length) && (memcmp(data, literal, length) == 0);
106}
107
125RA8_INTERNAL static bool internal_cookie_expiry_valid(const char* data, size_t length)
126{
127 if (length == 0U) {
128 return false;
129 }
130 for (size_t index = 0U; index < length; ++index) {
131 if ((data[index] < '0') || (data[index] > '9')) {
132 return false;
133 }
134 }
135 return true;
136}
137
155RA8_INTERNAL static bool internal_cookie_name_valid(const char* data, size_t length)
156{
157 static const char k_symbols[] = "!#$%&'*+-.^_`|~";
158 if (length == 0U) {
159 return false;
160 }
161 for (size_t index = 0U; index < length; ++index) {
162 const char value = data[index];
163 const bool alpha_numeric = ((value >= 'A') && (value <= 'Z')) ||
164 ((value >= 'a') && (value <= 'z')) ||
165 ((value >= '0') && (value <= '9'));
166 if (!alpha_numeric && (strchr(k_symbols, value) == nullptr)) {
167 return false;
168 }
169 }
170 return true;
171}
172
190RA8_INTERNAL static bool internal_netscape_cookie_valid(const char* line, size_t length)
191{
192 size_t field_start = 0U;
193 size_t field_index = 0U;
194 for (size_t index = 0U; index <= length; ++index) {
195 if ((index != length) && (line[index] != '\t')) {
196 continue;
197 }
198 const size_t field_length = index - field_start;
199 if (((field_index == 0U) || (field_index == 2U) || (field_index == k_cookie_name_field)) &&
200 (field_length == 0U)) {
201 return false;
202 }
203 if (((field_index == 1U) || (field_index == 3U)) &&
204 !internal_cookie_field_is(line + field_start, field_length, "TRUE") &&
205 !internal_cookie_field_is(line + field_start, field_length, "FALSE")) {
206 return false;
207 }
208 if ((field_index == 4U) && !internal_cookie_expiry_valid(line + field_start, field_length)) {
209 return false;
210 }
211 if ((field_index == k_cookie_name_field) &&
212 !internal_cookie_name_valid(line + field_start, field_length)) {
213 return false;
214 }
215 ++field_index;
216 field_start = index + 1U;
217 }
218 return field_index == k_cookie_field_count;
219}
220
238RA8_INTERNAL static bool internal_cookie_domain_valid(const char* attribute, size_t length)
239{
240 while ((length > 0U) && ((attribute[length - 1U] == ' ') || (attribute[length - 1U] == '\t'))) {
241 --length;
242 }
243 const size_t prefix_length = sizeof("Domain=") - 1U;
244 return internal_ascii_has_prefix(attribute, length, "domain=") && (length > prefix_length);
245}
246
264RA8_INTERNAL static bool internal_set_cookie_valid(const char* line, size_t length)
265{
266 const size_t prefix_length = sizeof("Set-Cookie:") - 1U;
267 size_t pair_end = prefix_length;
268 while ((pair_end < length) && (line[pair_end] != ';')) {
269 ++pair_end;
270 }
271 size_t name_start = prefix_length;
272 while ((name_start < pair_end) && ((line[name_start] == ' ') || (line[name_start] == '\t'))) {
273 ++name_start;
274 }
275 size_t equals = name_start;
276 while ((equals < pair_end) && (line[equals] != '=')) {
277 ++equals;
278 }
279 if ((equals == pair_end) || !internal_cookie_name_valid(line + name_start, equals - name_start)) {
280 return false;
281 }
282 for (size_t start = pair_end; start < length;) {
283 ++start;
284 while ((start < length) && ((line[start] == ' ') || (line[start] == '\t'))) {
285 ++start;
286 }
287 size_t end = start;
288 while ((end < length) && (line[end] != ';')) {
289 ++end;
290 }
291 if (internal_cookie_domain_valid(line + start, end - start)) {
292 return true;
293 }
294 start = end;
295 }
296 return false;
297}
298
318 size_t length)
319{
320 for (size_t index = 0U; index < length; ++index) {
321 const uint8_t value = (uint8_t)line[index];
322 if ((value >= k_cookie_ascii_del) || ((value < 0x20U) && (value != (uint8_t)'\t'))) {
323 return k_cookie_row_reject;
324 }
325 }
326 while ((length > 0U) && ((line[length - 1U] == ' ') || (line[length - 1U] == '\t'))) {
327 --length;
328 }
329 while ((length > 0U) && ((*line == ' ') || (*line == '\t'))) {
330 ++line;
331 --length;
332 }
333 if (length == 0U) {
334 return k_cookie_row_ignore;
335 }
336 if ((line[0] == '#') && !internal_ascii_has_prefix(line, length, "#httponly_")) {
337 return k_cookie_row_ignore;
338 }
339 const char* const commands[] = {"ALL", "SESS", "FLUSH", "RELOAD"};
340 for (size_t index = 0U; index < (sizeof(commands) / sizeof(commands[0])); ++index) {
341 if (internal_cookie_field_is(line, length, commands[index])) {
342 return k_cookie_row_reject;
343 }
344 }
345 const bool valid = internal_ascii_has_prefix(line, length, "set-cookie:")
346 ? internal_set_cookie_valid(line, length)
347 : internal_netscape_cookie_valid(line, length);
349}
350
370internal_apply_cookie_line(CURL* curl, const uint8_t* data, size_t length)
371{
372 if (length >= (size_t)k_cookie_line_max) {
374 }
375 char line[k_cookie_line_max];
376 memcpy(line, data, length);
377 line[length] = '\0';
378 const cookie_row_disposition_t disposition = internal_cookie_classify(line, length);
379 if (disposition == k_cookie_row_reject) {
381 }
382 if ((disposition == k_cookie_row_accept) &&
383 (curl_easy_setopt(curl, CURLOPT_COOKIELIST, line) != CURLE_OK)) {
384 return k_ra8_fail;
385 }
386 return k_ra8_ok;
387}
388
390{
391 if ((curl == nullptr) || (cookies == nullptr) ||
392 ((cookies->length == 0U) != (cookies->data == nullptr))) {
394 }
395 if (curl_easy_setopt(curl, CURLOPT_COOKIELIST, "ALL") != CURLE_OK) {
396 return k_ra8_fail;
397 }
398 size_t offset = 0U;
399 while (offset < cookies->length) {
400 size_t end = offset;
401 while ((end < cookies->length) && (cookies->data[end] != (uint8_t)'\n') &&
402 (cookies->data[end] != 0U)) {
403 ++end;
404 }
405 if ((end < cookies->length) && (cookies->data[end] == 0U)) {
407 }
408 size_t length = end - offset;
409 if ((length > 0U) && (cookies->data[offset + length - 1U] == (uint8_t)'\r')) {
410 --length;
411 }
412 const ra8_err_t error = internal_apply_cookie_line(curl, cookies->data + offset, length);
413 if (error != k_ra8_ok) {
414 return error;
415 }
416 offset = (end < cookies->length) ? end + 1U : end;
417 }
418 return k_ra8_ok;
419}
420
422priv_mdl_net_curl_apply_ca_blob(CURL* curl, const mdl_net_bytes_t* ca_pem, struct curl_blob* blob)
423{
424 if ((curl == nullptr) || (ca_pem == nullptr) ||
425 ((ca_pem->length == 0U) != (ca_pem->data == nullptr))) {
427 }
428 if (ca_pem->length == 0U) {
429 return k_ra8_ok;
430 }
431 for (size_t index = 0U; index < ca_pem->length; ++index) {
432 if (ca_pem->data[index] == 0U) {
434 }
435 }
436#if LIBCURL_VERSION_NUM >= 0x074D00
437 if (blob == nullptr) {
439 }
440 *blob = (struct curl_blob){.data = (void*)ca_pem->data,
441 .len = ca_pem->length,
442 .flags = CURL_BLOB_NOCOPY};
443 const CURLcode code = curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, blob);
444 if (code == CURLE_OK) {
445 return k_ra8_ok;
446 }
447 return ((code == CURLE_NOT_BUILT_IN) || (code == CURLE_UNKNOWN_OPTION)) ? k_ra8_err_not_supported
448 : k_ra8_fail;
449#else
450 (void)blob;
452#endif
453}
Streaming HTTP(S) GET seam (a real function-pointer vtable) for the host manga downloader (v0).
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.
cookie_row_disposition_t
Cookie-line disposition after strict syntax validation.
@ k_cookie_row_accept
Safe cookie row to import.
@ k_cookie_row_reject
Reject the complete credential input.
@ k_cookie_row_ignore
Recognized blank/comment framing.
static bool internal_cookie_domain_valid(const char *attribute, size_t length)
Test whether a Set-Cookie attribute has a nonempty Domain value.
static bool internal_cookie_field_is(const char *data, size_t length, const char *literal)
Compare one bounded field with an exact ASCII literal.
static ra8_err_t internal_apply_cookie_line(CURL *curl, const uint8_t *data, size_t length)
Import one validated bounded cookie row.
static bool internal_cookie_name_valid(const char *data, size_t length)
Validate an RFC token-style cookie name.
static char internal_ascii_lower(char value)
Lower-case one ASCII byte without locale state.
static cookie_row_disposition_t internal_cookie_classify(const char *line, size_t length)
Classify a bounded cookie-file line for safe libcurl import.
static bool internal_netscape_cookie_valid(const char *line, size_t length)
Validate one Netscape cookie-file row without accepting commands.
static bool internal_cookie_expiry_valid(const char *data, size_t length)
Validate one decimal Netscape-cookie expiry field.
static bool internal_set_cookie_valid(const char *line, size_t length)
Validate a Set-Cookie row and require an explicit Domain attribute.
mdl_cookie_limit_t
Maximum imported cookie row including its terminating NUL.
@ k_cookie_ascii_del
First non-printable high ASCII byte.
@ k_cookie_field_count
Required Netscape cookie field count.
@ k_cookie_name_field
Netscape cookie name-field ordinal.
@ k_cookie_line_max
Honest fixed per-row importer bound.
static bool internal_ascii_has_prefix(const char *data, size_t length, const char *prefix)
Test one bounded value for an ASCII case-insensitive prefix.
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.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_not_supported
Requested feature not compiled in, not wired, or not supported by this MCU variant.
Definition ra8_err.h:180
@ 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_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ 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 memcmp(const void *a, const void *b, size_t n)
Compare bytes in two memory areas.
size_t strlen(const char *s)
Calculate string length.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
char * strchr(const char *s, int c)
Locate first occurrence of character in string.
Read-only caller-owned byte view retained by a network backend.
Definition mdl_net.h:60
const uint8_t * data
First caller-owned byte, or NULL when empty.
Definition mdl_net.h:61
size_t length
Exact readable byte count at data.
Definition mdl_net.h:62