22typedef enum : uint16_t {
31typedef enum : uint32_t {
41typedef enum : uint8_t {
50typedef enum : uint8_t {
71 const char* q = strpbrk(url,
"?#");
72 return (q ==
nullptr) ?
strlen(url) : (size_t)(q - url);
77 if ((url ==
nullptr) || (out ==
nullptr) || (cap < 2U)) {
78 if ((out !=
nullptr) && (cap > 0U)) {
84 while ((end > 0U) && (url[end - 1U] ==
'/')) {
88 while ((start > 0U) && (url[start - 1U] !=
'/')) {
93 for (
size_t i = start; (i < end) && (n + 1U <
sizeof(raw)); ++i) {
122 const char* scheme =
strstr(url,
"://");
123 if ((scheme ==
nullptr) || ((
size_t)(scheme - url) >= end)) {
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);
135 const size_t marker_len =
strlen(marker);
136 const char* found =
nullptr;
137 if (marker_len > (end - begin)) {
140 for (
size_t i = begin; i <= (end - marker_len); ++i) {
141 if (
memcmp(url + i, marker, marker_len) == 0) {
173 const char** out_next)
175 if ((start >= end) || (*start <
'0') || (*start >
'9')) {
179 while ((start < end) && (*start >=
'0') && (*start <=
'9')) {
180 const uint32_t digit = (uint32_t)(*start -
'0');
187 double value = (double)whole;
188 if ((start < end) && ((*start ==
'-') || (*start ==
'.')) && ((start + 1) < end) &&
189 (start[1] >=
'0') && (start[1] <=
'9')) {
192 uint32_t digits = 0U;
193 while ((start < end) && (*start >=
'0') && (*start <=
'9')) {
197 value += (double)(*start -
'0') * scale;
204 if (out_next !=
nullptr) {
212 if (out !=
nullptr) {
215 if ((url ==
nullptr) || (out ==
nullptr)) {
224 static const char*
const markers[] = {
"chapter-",
"/ch-",
"/ep"};
225 const char* found =
nullptr;
227 for (
size_t i = 0U; i < (
sizeof(markers) /
sizeof(markers[0])); ++i) {
229 if ((candidate !=
nullptr) && ((found ==
nullptr) || (candidate > found))) {
231 skip =
strlen(markers[i]);
234 return (found !=
nullptr) &&
240 if (out !=
nullptr) {
243 if ((text ==
nullptr) || (out ==
nullptr)) {
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'))) {
256 while ((end > begin) && ((end[-1] ==
' ') || (end[-1] ==
'\t') || (end[-1] ==
'\r') ||
257 (end[-1] ==
'\n') || (end[-1] ==
'\f') || (end[-1] ==
'\v'))) {
260 const char* next =
nullptr;
296 const int lowered = ((c >=
'A') && (c <=
'Z')) ? (c + (
int)
k_urlname_case_gap) : (int)c;
297 return (
char)lowered;
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) {
341 if ((out ==
nullptr) || (capacity == 0U)) {
344 size_t length =
strnlen(value, capacity - 1U);
345 memcpy(out, value, length);
351 if ((out ==
nullptr) || (cap == 0U)) {
355 if (url ==
nullptr) {
360 while ((start > 0U) && (url[start - 1U] !=
'/')) {
364 for (
size_t i = start; i < pend; ++i) {
374 for (
size_t i = dot + 1U; (i < pend) && (n + 1U <
sizeof(ext)); ++i) {
404 if ((buf ==
nullptr) || (buf_len < 2U)) {
407 const uint8_t* b = (
const uint8_t*)buf;
411 *mime =
"image/jpeg";
414 if ((b[0] == (uint8_t)
'B') && (b[1] == (uint8_t)
'M')) {
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')) {
425 if ((buf_len >= (
size_t)
k_webp_sig_bytes) && (b[0] ==
'R') && (b[1] ==
'I') && (b[2] ==
'F') &&
429 *mime =
"image/webp";
432 if ((buf_len >= 6U) && (b[0] ==
'G') && (b[1] ==
'I') && (b[2] ==
'F') && (b[3] ==
'8') &&
461 if ((content_type ==
nullptr) || (content_type[0] ==
'\0')) {
466 for (; (content_type[i] !=
'\0') && (i + 1U <
sizeof(ct_lower)); ++i) {
470 if ((
strstr(ct_lower,
"image/jpeg") !=
nullptr) || (
strstr(ct_lower,
"image/jpg") !=
nullptr)) {
472 *mime =
"image/jpeg";
473 }
else if (
strstr(ct_lower,
"image/png") !=
nullptr) {
476 }
else if (
strstr(ct_lower,
"image/webp") !=
nullptr) {
478 *mime =
"image/webp";
479 }
else if (
strstr(ct_lower,
"image/gif") !=
nullptr) {
482 }
else if (
strstr(ct_lower,
"image/bmp") !=
nullptr) {
515 if ((out_ext !=
nullptr) && (ext_cap > 0U)) {
518 if ((out_mime !=
nullptr) && (mime_cap > 0U)) {
525 const char* content_type,
531 const char* ext =
nullptr;
532 const char* mime =
nullptr;
541 const char* file_path,
542 const char* content_type,
548 if ((storage ==
nullptr) || (storage->
fs ==
nullptr) || (storage->
file_workspace ==
nullptr) ||
549 (file_path ==
nullptr)) {
564 uint32_t length = 0U;
566 while ((length <
sizeof(header)) && (err ==
k_ra8_ok)) {
572 err =
fw_fs_read(&file, header + length, (uint32_t)
sizeof(header) - length, &count);
574 if ((err ==
k_ra8_ok) && (count == 0U)) {
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.
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.
ra8_err_t fw_fs_close(fw_fs_file_t *file)
Close and consume an open handle.
@ 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.
@ k_chapter_frac_max
Fractional digits retained.
@ k_chapter_radix
Decimal chapter-number radix.
@ k_chapter_whole_max
Largest accepted integral chapter.
mdl_urlname_const_t
On-stack buffers and parse radix for the URL-name helpers.
@ k_urlname_scan_max
Maximum URL bytes inspected.
@ k_urlname_case_gap
'a' - 'A': ASCII upper-to-lower offset.
@ k_urlname_raw_bytes
Pre-sanitise last-segment scratch bytes.
@ k_urlname_magic_bytes
Largest file-signature prefix consumed.
@ k_urlname_read_calls
One-byte short reads plus clean EOF.
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.
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.
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.
@ k_jpeg_marker_byte
JPEG marker prefix.
@ k_jpeg_soi_byte
JPEG Start Of Image marker.
@ k_png_lead_byte
PNG signature lead byte.
mdl_image_signature_offset_t
Signature byte offsets not already exempted by the numeric policy.
@ k_gif_a_offset
'a' trailer in GIF87a/GIF89a.
@ k_webp_p_offset
'P' in the WEBP form type.
@ k_webp_b_offset
'B' in the WEBP form type.
@ k_webp_sig_bytes
RIFF header plus WEBP form type.
@ k_webp_e_offset
'E' in the WEBP form type.
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.
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.
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
@ 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.
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.
uint32_t file_workspace_bytes
File workspace extent.
const fw_fs_t * fs
Injected portable filesystem.
void * file_workspace
Open-file backend state.