ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_urlname.c
Go to the documentation of this file.
1
13#include "mdl_urlname.h"
14
15#include <stdint.h>
16#include <string.h>
17
18#include "mdl_sanitize.h"
19#include "ra8_attributes.h"
20
29
31typedef enum : uint32_t {
32 k_chapter_whole_max = 999999999U,
36
38static const double s_chapter_fraction_step = 0.1;
39
48
50typedef enum : uint8_t {
55
69RA8_INTERNAL static size_t internal_urlname_path_end(const char* url)
70{
71 const char* q = strpbrk(url, "?#");
72 return (q == nullptr) ? strlen(url) : (size_t)(q - url);
73}
74
75void mdl_urlname_last_segment(const char* url, char* out, size_t cap)
76{
77 if ((url == nullptr) || (out == nullptr) || (cap < 2U)) {
78 if ((out != nullptr) && (cap > 0U)) {
79 out[0] = '\0';
80 }
81 return;
82 }
83 size_t end = internal_urlname_path_end(url);
84 while ((end > 0U) && (url[end - 1U] == '/')) {
85 --end; /* trim trailing slashes */
86 }
87 size_t start = end;
88 while ((start > 0U) && (url[start - 1U] != '/')) {
89 --start;
90 }
91 char raw[k_urlname_raw_bytes];
92 size_t n = 0U;
93 for (size_t i = start; (i < end) && (n + 1U < sizeof(raw)); ++i) {
94 raw[n] = url[i];
95 ++n;
96 }
97 raw[n] = '\0';
98 (void)mdl_sanitize_segment(raw, out, cap);
99}
100
120RA8_INTERNAL static size_t internal_urlname_path_start(const char* url, size_t end)
121{
122 const char* scheme = strstr(url, "://");
123 if ((scheme == nullptr) || ((size_t)(scheme - url) >= end)) {
124 return 0U; /* Relative URL: its entire prefix is a path. */
125 }
126 const size_t authority = (size_t)(scheme - url) + strlen("://");
127 const char* slash = memchr(url + authority, '/', end - authority);
128 return (slash == nullptr) ? end : (size_t)(slash - url);
129}
130
132RA8_INTERNAL static const char*
133internal_urlname_last_path_marker(const char* url, size_t begin, size_t end, const char* marker)
134{
135 const size_t marker_len = strlen(marker);
136 const char* found = nullptr;
137 if (marker_len > (end - begin)) {
138 return nullptr;
139 }
140 for (size_t i = begin; i <= (end - marker_len); ++i) {
141 if (memcmp(url + i, marker, marker_len) == 0) {
142 found = url + i;
143 }
144 }
145 return found;
146}
147
171 const char* end,
172 double* out,
173 const char** out_next)
174{
175 if ((start >= end) || (*start < '0') || (*start > '9')) {
176 return false;
177 }
178 uint32_t whole = 0U;
179 while ((start < end) && (*start >= '0') && (*start <= '9')) {
180 const uint32_t digit = (uint32_t)(*start - '0');
181 if (whole > (((uint32_t)k_chapter_whole_max - digit) / (uint32_t)k_chapter_radix)) {
182 return false;
183 }
184 whole = (whole * (uint32_t)k_chapter_radix) + digit;
185 ++start;
186 }
187 double value = (double)whole;
188 if ((start < end) && ((*start == '-') || (*start == '.')) && ((start + 1) < end) &&
189 (start[1] >= '0') && (start[1] <= '9')) {
190 ++start;
191 double scale = s_chapter_fraction_step;
192 uint32_t digits = 0U;
193 while ((start < end) && (*start >= '0') && (*start <= '9')) {
194 if (digits >= (uint32_t)k_chapter_frac_max) {
195 return false;
196 }
197 value += (double)(*start - '0') * scale;
199 ++digits;
200 ++start;
201 }
202 }
203 *out = value;
204 if (out_next != nullptr) {
205 *out_next = start;
206 }
207 return true;
208}
209
210bool mdl_urlname_chapter_parse(const char* url, double* out)
211{
212 if (out != nullptr) {
213 *out = 0.0;
214 }
215 if ((url == nullptr) || (out == nullptr)) {
216 return false;
217 }
218 const size_t bounded = strnlen(url, (size_t)k_urlname_scan_max);
219 if (bounded == (size_t)k_urlname_scan_max) {
220 return false;
221 }
222 const size_t end = internal_urlname_path_end(url);
223 const size_t begin = internal_urlname_path_start(url, end);
224 static const char* const markers[] = {"chapter-", "/ch-", "/ep"};
225 const char* found = nullptr;
226 size_t skip = 0U;
227 for (size_t i = 0U; i < (sizeof(markers) / sizeof(markers[0])); ++i) {
228 const char* candidate = internal_urlname_last_path_marker(url, begin, end, markers[i]);
229 if ((candidate != nullptr) && ((found == nullptr) || (candidate > found))) {
230 found = candidate;
231 skip = strlen(markers[i]);
232 }
233 }
234 return (found != nullptr) &&
235 internal_urlname_parse_chapter_digits(found + skip, url + end, out, nullptr);
236}
237
238bool mdl_urlname_chapter_text_parse(const char* text, double* out)
239{
240 if (out != nullptr) {
241 *out = 0.0;
242 }
243 if ((text == nullptr) || (out == nullptr)) {
244 return false;
245 }
246 const size_t bounded = strnlen(text, (size_t)k_urlname_scan_max);
247 if (bounded == (size_t)k_urlname_scan_max) {
248 return false;
249 }
250 const char* begin = text;
251 const char* end = text + bounded;
252 while ((begin < end) && ((*begin == ' ') || (*begin == '\t') || (*begin == '\r') ||
253 (*begin == '\n') || (*begin == '\f') || (*begin == '\v'))) {
254 ++begin;
255 }
256 while ((end > begin) && ((end[-1] == ' ') || (end[-1] == '\t') || (end[-1] == '\r') ||
257 (end[-1] == '\n') || (end[-1] == '\f') || (end[-1] == '\v'))) {
258 --end;
259 }
260 const char* next = nullptr;
261 double parsed = 0.0;
262 if (!internal_urlname_parse_chapter_digits(begin, end, &parsed, &next) || (next != end)) {
263 return false;
264 }
265 *out = parsed;
266 return true;
267}
268
269double mdl_urlname_chapter_value(const char* url)
270{
271 double value = 0.0;
272 (void)mdl_urlname_chapter_parse(url, &value);
273 return value;
274}
275
276long mdl_urlname_chapter_number(const char* url)
277{
278 return (long)mdl_urlname_chapter_value(url);
279}
280
295{
296 const int lowered = ((c >= 'A') && (c <= 'Z')) ? (c + (int)k_urlname_case_gap) : (int)c;
297 return (char)lowered;
298}
299
314RA8_INTERNAL static bool internal_urlname_is_known_ext(const char* ext)
315{
316 static const char* const known[] = {"jpg", "jpeg", "png", "gif", "webp", "bmp"};
317 for (size_t i = 0U; i < (sizeof(known) / sizeof(known[0])); ++i) {
318 if (strcmp(ext, known[i]) == 0) {
319 return true;
320 }
321 }
322 return false;
323}
324
339RA8_INTERNAL static void internal_urlname_copy(char* out, size_t capacity, const char* value)
340{
341 if ((out == nullptr) || (capacity == 0U)) {
342 return;
343 }
344 size_t length = strnlen(value, capacity - 1U);
345 memcpy(out, value, length);
346 out[length] = '\0';
347}
348
349void mdl_urlname_ext(const char* url, char* out, size_t cap)
350{
351 if ((out == nullptr) || (cap == 0U)) {
352 return;
353 }
354 internal_urlname_copy(out, cap, "jpg");
355 if (url == nullptr) {
356 return;
357 }
358 const size_t pend = internal_urlname_path_end(url);
359 size_t start = pend;
360 while ((start > 0U) && (url[start - 1U] != '/')) {
361 --start; /* isolate the last path segment */
362 }
363 size_t dot = pend;
364 for (size_t i = start; i < pend; ++i) {
365 if (url[i] == '.') {
366 dot = i;
367 }
368 }
369 if (dot >= pend) {
370 return; /* no '.' in the segment -> keep the default */
371 }
372 char ext[8];
373 size_t n = 0U;
374 for (size_t i = dot + 1U; (i < pend) && (n + 1U < sizeof(ext)); ++i) {
375 ext[n++] = internal_urlname_to_lower_ascii(url[i]);
376 }
377 ext[n] = '\0';
379 internal_urlname_copy(out, cap, ext);
380 }
381}
382
401RA8_INTERNAL static bool
402internal_sniff_magic_image(const void* buf, size_t buf_len, const char** ext, const char** mime)
403{
404 if ((buf == nullptr) || (buf_len < 2U)) {
405 return false;
406 }
407 const uint8_t* b = (const uint8_t*)buf;
408 if ((buf_len >= 3U) && (b[0] == (uint8_t)k_jpeg_marker_byte) &&
409 (b[1] == (uint8_t)k_jpeg_soi_byte) && (b[2] == (uint8_t)k_jpeg_marker_byte)) {
410 *ext = "jpg";
411 *mime = "image/jpeg";
412 return true;
413 }
414 if ((b[0] == (uint8_t)'B') && (b[1] == (uint8_t)'M')) {
415 *ext = "bmp";
416 *mime = "image/bmp";
417 return true;
418 }
419 if ((buf_len >= 4U) && (b[0] == (uint8_t)k_png_lead_byte) && (b[1] == (uint8_t)'P') &&
420 (b[2] == (uint8_t)'N') && (b[3] == (uint8_t)'G')) {
421 *ext = "png";
422 *mime = "image/png";
423 return true;
424 }
425 if ((buf_len >= (size_t)k_webp_sig_bytes) && (b[0] == 'R') && (b[1] == 'I') && (b[2] == 'F') &&
426 (b[3] == 'F') && (b[8] == 'W') && (b[k_webp_e_offset] == 'E') &&
427 (b[k_webp_b_offset] == 'B') && (b[k_webp_p_offset] == 'P')) {
428 *ext = "webp";
429 *mime = "image/webp";
430 return true;
431 }
432 if ((buf_len >= 6U) && (b[0] == 'G') && (b[1] == 'I') && (b[2] == 'F') && (b[3] == '8') &&
433 ((b[4] == '7') || (b[4] == '9')) && (b[k_gif_a_offset] == 'a')) {
434 *ext = "gif";
435 *mime = "image/gif";
436 return true;
437 }
438 return false;
439}
440
458RA8_INTERNAL static bool
459internal_sniff_content_type(const char* content_type, const char** ext, const char** mime)
460{
461 if ((content_type == nullptr) || (content_type[0] == '\0')) {
462 return false;
463 }
464 char ct_lower[128];
465 size_t i = 0U;
466 for (; (content_type[i] != '\0') && (i + 1U < sizeof(ct_lower)); ++i) {
467 ct_lower[i] = internal_urlname_to_lower_ascii(content_type[i]);
468 }
469 ct_lower[i] = '\0';
470 if ((strstr(ct_lower, "image/jpeg") != nullptr) || (strstr(ct_lower, "image/jpg") != nullptr)) {
471 *ext = "jpg";
472 *mime = "image/jpeg";
473 } else if (strstr(ct_lower, "image/png") != nullptr) {
474 *ext = "png";
475 *mime = "image/png";
476 } else if (strstr(ct_lower, "image/webp") != nullptr) {
477 *ext = "webp";
478 *mime = "image/webp";
479 } else if (strstr(ct_lower, "image/gif") != nullptr) {
480 *ext = "gif";
481 *mime = "image/gif";
482 } else if (strstr(ct_lower, "image/bmp") != nullptr) {
483 *ext = "bmp";
484 *mime = "image/bmp";
485 } else {
486 return false;
487 }
488 return true;
489}
490
508RA8_INTERNAL static void internal_copy_image_type(const char* ext,
509 const char* mime,
510 char* out_ext,
511 size_t ext_cap,
512 char* out_mime,
513 size_t mime_cap)
514{
515 if ((out_ext != nullptr) && (ext_cap > 0U)) {
516 internal_urlname_copy(out_ext, ext_cap, ext);
517 }
518 if ((out_mime != nullptr) && (mime_cap > 0U)) {
519 internal_urlname_copy(out_mime, mime_cap, mime);
520 }
521}
522
523bool mdl_urlname_sniff_image_type(const void* buf,
524 size_t buf_len,
525 const char* content_type,
526 char* out_ext,
527 size_t ext_cap,
528 char* out_mime,
529 size_t mime_cap)
530{
531 const char* ext = nullptr;
532 const char* mime = nullptr;
533 if (!internal_sniff_magic_image(buf, buf_len, &ext, &mime) &&
534 !internal_sniff_content_type(content_type, &ext, &mime)) {
535 return false;
536 }
537 internal_copy_image_type(ext, mime, out_ext, ext_cap, out_mime, mime_cap);
538 return true;
539}
541 const char* file_path,
542 const char* content_type,
543 char* out_ext,
544 size_t ext_cap,
545 char* out_mime,
546 size_t mime_cap)
547{
548 if ((storage == nullptr) || (storage->fs == nullptr) || (storage->file_workspace == nullptr) ||
549 (file_path == nullptr)) {
551 }
552 fw_fs_file_t file = {};
553 ra8_err_t err = fw_fs_open(&storage->fs->streams,
554 file_path,
556 &file,
557 storage->file_workspace,
558 storage->file_workspace_bytes);
559 if (err != k_ra8_ok) {
560 return err;
561 }
562
563 uint8_t header[k_urlname_magic_bytes] = {};
564 uint32_t length = 0U;
565 uint32_t calls = 0U;
566 while ((length < sizeof(header)) && (err == k_ra8_ok)) {
567 if (calls >= (uint32_t)k_urlname_read_calls) {
569 break;
570 }
571 uint32_t count = 0U;
572 err = fw_fs_read(&file, header + length, (uint32_t)sizeof(header) - length, &count);
573 ++calls;
574 if ((err == k_ra8_ok) && (count == 0U)) {
575 break;
576 }
577 length += count;
578 }
579 const ra8_err_t close_err = fw_fs_close(&file);
580 if (close_err != k_ra8_ok) {
581 return close_err;
582 }
583 if (err != k_ra8_ok) {
584 return err;
585 }
586 return mdl_urlname_sniff_image_type(header,
587 length,
588 content_type,
589 out_ext,
590 ext_cap,
591 out_mime,
592 mime_cap)
593 ? k_ra8_ok
595}
ra8_err_t fw_fs_open(const fw_fs_stream_port_t *streams, const char *path, fw_fs_open_mode_t mode, fw_fs_file_t *file, void *workspace, uint32_t workspace_size)
Open a file into a caller-owned handle and backend workspace.
Definition fw_if_fs.c:648
ra8_err_t fw_fs_read(fw_fs_file_t *file, uint8_t *dst, uint32_t cap, uint32_t *out_read)
Read up to cap bytes; zero bytes is EOF.
Definition fw_if_fs.c:699
ra8_err_t fw_fs_close(fw_fs_file_t *file)
Close and consume an open handle.
Definition fw_if_fs.c:783
@ k_fw_fs_open_read
Existing file, read-only.
Neutralise untrusted names before they reach a filesystem or XML sink.
bool mdl_sanitize_segment(const char *raw, char *out, size_t cap)
Rewrite an untrusted path segment into a safe single filename.
static void internal_copy_image_type(const char *ext, const char *mime, char *out_ext, size_t ext_cap, char *out_mime, size_t mime_cap)
Copy one canonical image classification to bounded outputs.
static bool internal_sniff_magic_image(const void *buf, size_t buf_len, const char **ext, const char **mime)
Classify a supported image from its leading magic bytes.
static size_t internal_urlname_path_start(const char *url, size_t end)
Start offset of the path, excluding any URL scheme and authority.
bool mdl_urlname_sniff_image_type(const void *buf, size_t buf_len, const char *content_type, char *out_ext, size_t ext_cap, char *out_mime, size_t mime_cap)
Sniff true image extension and MIME type from magic bytes and/or HTTP Content-Type.
void mdl_urlname_ext(const char *url, char *out, size_t cap)
Choose a lower-case image file extension from a URL.
mdl_chapter_num_limit_t
Numeric bounds keep conversion loops and sortable values finite.
Definition mdl_urlname.c:31
@ k_chapter_frac_max
Fractional digits retained.
Definition mdl_urlname.c:33
@ k_chapter_radix
Decimal chapter-number radix.
Definition mdl_urlname.c:34
@ k_chapter_whole_max
Largest accepted integral chapter.
Definition mdl_urlname.c:32
mdl_urlname_const_t
On-stack buffers and parse radix for the URL-name helpers.
Definition mdl_urlname.c:22
@ k_urlname_scan_max
Maximum URL bytes inspected.
Definition mdl_urlname.c:25
@ k_urlname_case_gap
'a' - 'A': ASCII upper-to-lower offset.
Definition mdl_urlname.c:24
@ k_urlname_raw_bytes
Pre-sanitise last-segment scratch bytes.
Definition mdl_urlname.c:23
@ k_urlname_magic_bytes
Largest file-signature prefix consumed.
Definition mdl_urlname.c:26
@ k_urlname_read_calls
One-byte short reads plus clean EOF.
Definition mdl_urlname.c:27
static bool internal_urlname_is_known_ext(const char *ext)
True when ext is one of the accepted raster image extensions.
bool mdl_urlname_chapter_text_parse(const char *text, double *out)
Parse a selector result as one complete bounded chapter number.
static void internal_urlname_copy(char *out, size_t capacity, const char *value)
Copy one NUL-terminated value into bounded caller storage.
static bool internal_sniff_content_type(const char *content_type, const char **ext, const char **mime)
Classify a supported image from an HTTP Content-Type value.
static bool internal_urlname_parse_chapter_digits(const char *start, const char *end, double *out, const char **out_next)
Parse digits at start, optionally followed by a decimal fraction.
ra8_err_t mdl_urlname_sniff_file(mdl_storage_t *storage, const char *file_path, const char *content_type, char *out_ext, size_t ext_cap, char *out_mime, size_t mime_cap)
Sniff true image extension and MIME type from portable storage.
static const char * internal_urlname_last_path_marker(const char *url, size_t begin, size_t end, const char *marker)
Locate the last chapter marker wholly inside [begin,end).
static const double s_chapter_fraction_step
Initial decimal place multiplier for a fractional chapter.
Definition mdl_urlname.c:38
double mdl_urlname_chapter_value(const char *url)
Parse a chapter URL as a possibly-decimal chapter value.
long mdl_urlname_chapter_number(const char *url)
Parse a chapter URL's integral chapter number.
void mdl_urlname_last_segment(const char *url, char *out, size_t cap)
Sanitised last non-empty path segment of a URL.
Definition mdl_urlname.c:75
static char internal_urlname_to_lower_ascii(char c)
Lower-case an ASCII byte.
mdl_image_signature_byte_t
Non-ASCII fixed bytes used by the JPEG and PNG signatures.
Definition mdl_urlname.c:50
@ k_jpeg_marker_byte
JPEG marker prefix.
Definition mdl_urlname.c:51
@ k_jpeg_soi_byte
JPEG Start Of Image marker.
Definition mdl_urlname.c:52
@ k_png_lead_byte
PNG signature lead byte.
Definition mdl_urlname.c:53
mdl_image_signature_offset_t
Signature byte offsets not already exempted by the numeric policy.
Definition mdl_urlname.c:41
@ k_gif_a_offset
'a' trailer in GIF87a/GIF89a.
Definition mdl_urlname.c:46
@ k_webp_p_offset
'P' in the WEBP form type.
Definition mdl_urlname.c:45
@ k_webp_b_offset
'B' in the WEBP form type.
Definition mdl_urlname.c:44
@ k_webp_sig_bytes
RIFF header plus WEBP form type.
Definition mdl_urlname.c:42
@ k_webp_e_offset
'E' in the WEBP form type.
Definition mdl_urlname.c:43
bool mdl_urlname_chapter_parse(const char *url, double *out)
Parse an explicitly-marked integral or decimal chapter value.
static size_t internal_urlname_path_end(const char *url)
End offset of a URL's path, before any ?query/#fragment.
Definition mdl_urlname.c:69
Bounded URL naming and portable image-classification helpers.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ 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
void * memchr(const void *s, int c, size_t n)
Locate a byte in a memory area.
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
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 * strstr(const char *haystack, const char *needle)
Locate substring in string.
size_t strnlen(const char *s, size_t maxlen)
Calculate bounded string length.
Caller-owned open file; fields are private to the facade.
fw_fs_stream_port_t streams
Byte-stream operations.
One non-reentrant downloader filesystem dependency bundle.
Definition mdl_storage.h:40
uint32_t file_workspace_bytes
File workspace extent.
Definition mdl_storage.h:45
const fw_fs_t * fs
Injected portable filesystem.
Definition mdl_storage.h:41
void * file_workspace
Open-file backend state.
Definition mdl_storage.h:42