ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_robots.c
Go to the documentation of this file.
1
12#include "mdl_robots.h"
13
14#include <math.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18
19#include "ra8_attributes.h"
20
22typedef enum : uint16_t {
26
28typedef enum : uint32_t {
29 k_ms_per_s = 1000U,
30 k_crawl_cap_ms = 60000U,
32
34typedef enum : int8_t {
37
52{
53 return (char)(((c >= 'A') && (c <= 'Z')) ? (c + ('a' - 'A')) : c);
54}
55
71RA8_INTERNAL static bool internal_ieq(const char* a, const char* b)
72{
73 size_t i = 0U;
74 while ((a[i] != '\0') && (internal_lower_ascii(a[i]) == internal_lower_ascii(b[i]))) {
75 ++i;
76 }
77 return internal_lower_ascii(a[i]) == internal_lower_ascii(b[i]);
78}
79
95RA8_INTERNAL static bool internal_ci_prefix(const char* prefix, const char* s)
96{
97 size_t i = 0U;
98 while (prefix[i] != '\0') {
99 if (internal_lower_ascii(s[i]) != internal_lower_ascii(prefix[i])) {
100 return false;
101 }
102 ++i;
103 }
104 return true;
105}
106
108RA8_INTERNAL static char* internal_trim_ws(char* s)
109{
110 while ((*s == ' ') || (*s == '\t') || (*s == '\r')) {
111 ++s;
112 }
113 size_t n = strlen(s);
114 while ((n > 0U) && ((s[n - 1U] == ' ') || (s[n - 1U] == '\t') || (s[n - 1U] == '\r'))) {
115 s[n - 1U] = '\0';
116 --n;
117 }
118 return s;
119}
120
143RA8_INTERNAL static bool
144internal_next_line(const char** pp, const char* end, char* buf, size_t cap, bool* truncated)
145{
146 const char* p = *pp;
147 if (p >= end) {
148 return false;
149 }
150 size_t n = 0U;
151 while ((p < end) && (*p != '\n')) {
152 if ((n + 1U) < cap) {
153 buf[n] = *p;
154 ++n;
155 } else {
156 *truncated = true;
157 }
158 ++p;
159 }
160 if (p < end) {
161 ++p; /* consume the '\n' */
162 }
163 *pp = p;
164 buf[n] = '\0';
165 char* hash = strchr(buf, '#');
166 if (hash != nullptr) {
167 *hash = '\0';
168 }
169 return true;
170}
171
188RA8_INTERNAL static bool internal_split_field(char* line, char** field, char** value)
189{
190 char* colon = strchr(line, ':');
191 if (colon == nullptr) {
192 return false;
193 }
194 *colon = '\0';
195 *field = internal_trim_ws(line);
196 *value = internal_trim_ws(colon + 1);
197 return (*field)[0] != '\0';
198}
199
215RA8_INTERNAL static int internal_agent_spec(const char* agent, const char* ua_token)
216{
217 if (strcmp(agent, "*") == 0) {
218 return 0;
219 }
220 if ((agent[0] != '\0') && internal_ci_prefix(agent, ua_token)) {
221 return (int)strlen(agent);
222 }
223 return (int)k_spec_none;
224}
225
248RA8_INTERNAL static bool
249internal_scan_spec(const char* text, size_t len, const char* ua_token, int* best, bool* wild)
250{
251 *best = (int)k_spec_none;
252 *wild = false;
253 const char* p = text;
254 const char* end = text + len;
255 char line[k_robots_line_max];
256 bool truncated = false;
257 while (internal_next_line(&p, end, line, sizeof(line), &truncated)) {
258 char* field = nullptr;
259 char* value = nullptr;
260 if (!internal_split_field(line, &field, &value)) {
261 continue;
262 }
263 if (internal_ieq(field, "user-agent")) {
264 const int spec = internal_agent_spec(value, ua_token);
265 if (spec == 0) {
266 *wild = true;
267 } else if (spec > *best) {
268 *best = spec;
269 }
270 }
271 }
272 return !truncated;
273}
274
292RA8_INTERNAL static bool
293internal_group_selected(int target, bool wildcard_target, int grp_spec, bool grp_wild)
294{
295 return wildcard_target ? grp_wild : ((target >= 0) && (grp_spec == target));
296}
297
312RA8_INTERNAL static void
314{
315 if ((value[0] == '\0') || (out->count >= (size_t)k_mdl_robots_max_rules)) {
316 if (value[0] != '\0') {
317 out->valid = false;
318 }
319 return; /* empty pattern imposes no restriction; full table stops growing */
320 }
322 out->valid = false;
323 return;
324 }
325 mdl_robots_rule_t* r = &out->rules[out->count];
326 r->kind = kind;
327 (void)snprintf(r->path, sizeof(r->path), "%s", value);
328 r->len = (uint16_t)strlen(r->path);
329 out->count++;
330}
331
345RA8_INTERNAL static void internal_set_crawl(mdl_robots_t* out, const char* value)
346{
347 char* end = nullptr;
348 const double secs = strtod(value, &end);
349 if ((end == value) || (*end != '\0') || !isfinite(secs) || (secs <= 0.0)) {
350 return;
351 }
352 uint32_t ms = (secs >= ((double)k_crawl_cap_ms / (double)k_ms_per_s))
353 ? (uint32_t)k_crawl_cap_ms
354 : (uint32_t)(secs * (double)k_ms_per_s);
355 if (!out->have_crawl_delay || (ms > out->crawl_delay_ms)) {
356 out->crawl_delay_ms = ms;
357 }
358 out->have_crawl_delay = true;
359}
360
375RA8_INTERNAL static void
376internal_apply_directive(mdl_robots_t* out, const char* field, const char* value)
377{
378 if (internal_ieq(field, "disallow")) {
380 } else if (internal_ieq(field, "allow")) {
382 } else if (internal_ieq(field, "crawl-delay")) {
383 internal_set_crawl(out, value);
384 }
385}
386
404RA8_INTERNAL static void internal_harvest(const char* text,
405 size_t len,
406 const char* ua_token,
407 int target,
408 bool wildcard_target,
409 mdl_robots_t* out)
410{
411 const char* p = text;
412 const char* end = text + len;
413 bool prev_agent = false;
414 bool selected = false;
415 int grp_spec = (int)k_spec_none;
416 bool grp_wild = false;
417 char line[k_robots_line_max];
418 bool truncated = false;
419 while (internal_next_line(&p, end, line, sizeof(line), &truncated)) {
420 char* field = nullptr;
421 char* value = nullptr;
422 if (!internal_split_field(line, &field, &value)) {
423 continue;
424 }
425 if (internal_ieq(field, "user-agent")) {
426 if (!prev_agent) {
427 grp_spec = (int)k_spec_none;
428 grp_wild = false;
429 }
430 const int spec = internal_agent_spec(value, ua_token);
431 if (spec == 0) {
432 grp_wild = true;
433 } else if (spec > grp_spec) {
434 grp_spec = spec;
435 }
436 selected = internal_group_selected(target, wildcard_target, grp_spec, grp_wild);
437 prev_agent = true;
438 } else {
439 prev_agent = false;
440 if (selected) {
441 internal_apply_directive(out, field, value);
442 }
443 }
444 }
445 if (truncated) {
446 out->valid = false;
447 }
448}
449
450void mdl_robots_parse(const char* text, size_t len, const char* ua_token, mdl_robots_t* out)
451{
452 if (out == nullptr) {
453 return;
454 }
455 *out = (mdl_robots_t){};
456 out->valid = true;
457 if ((text == nullptr) && (len != 0U)) {
458 out->valid = false;
459 return;
460 }
461 if (ua_token == nullptr) {
462 out->valid = false;
463 return;
464 }
465 if (len == 0U) {
466 return;
467 }
468 int best = (int)k_spec_none;
469 bool wild = false;
470 if (!internal_scan_spec(text, len, ua_token, &best, &wild)) {
471 out->valid = false;
472 return;
473 }
474 if ((best < 0) && !wild) {
475 return; /* no group matches us -> no restrictions */
476 }
477 const bool wildcard_target = (best < 0) && wild;
478 internal_harvest(text, len, ua_token, best, wildcard_target, out);
479}
480
498RA8_INTERNAL static bool
499internal_glob_prefix(const char* pat, size_t patlen, const char* path, bool anchored)
500{
501 size_t pi = 0U;
502 const char* s = path;
503 size_t star = patlen + 1U; /* > patlen means "no star seen yet" */
504 const char* ss = nullptr;
505 while (*s != '\0') {
506 if ((pi < patlen) && (pat[pi] == '*')) {
507 star = pi;
508 ++pi;
509 ss = s;
510 } else if ((pi < patlen) && (pat[pi] == *s)) {
511 ++pi;
512 ++s;
513 } else if ((star <= patlen) && (ss != nullptr)) {
514 /* star <= patlen holds only after `ss = s` ran, so ss is non-NULL here;
515 * the explicit check makes that invariant visible to the analyser. */
516 pi = star + 1U;
517 ++ss;
518 s = ss;
519 } else {
520 return false;
521 }
522 if ((pi == patlen) && !anchored) {
523 return true; /* whole pattern consumed -> prefix match */
524 }
525 }
526 while ((pi < patlen) && (pat[pi] == '*')) {
527 ++pi;
528 }
529 return pi == patlen;
530}
531
548RA8_INTERNAL static bool internal_robots_match(const char* pat, size_t len, const char* path)
549{
550 bool anchored = false;
551 if ((len > 0U) && (pat[len - 1U] == '$')) {
552 anchored = true;
553 --len;
554 }
555 return internal_glob_prefix(pat, len, path, anchored);
556}
557
560 const char* path)
561{
562 const mdl_robots_rule_t* best = nullptr;
563 int best_len = (int)k_spec_none;
564 for (size_t i = 0U; i < robots->count; ++i) {
565 const mdl_robots_rule_t* r = &robots->rules[i];
566 if (!internal_robots_match(r->path, r->len, path)) {
567 continue;
568 }
569 const bool is_allow = (r->kind == k_mdl_rule_allow);
570 if (((int)r->len > best_len) || (((int)r->len == best_len) && is_allow)) {
571 best_len = (int)r->len;
572 best = r;
573 }
574 }
575 return best;
576}
577
578bool mdl_robots_allows(const mdl_robots_t* robots, const char* path)
579{
580 if ((robots == nullptr) || (path == nullptr) || !robots->valid) {
581 return false;
582 }
583 const mdl_robots_rule_t* best = internal_best_matching_rule(robots, path);
584 return (best == nullptr) || (best->kind == k_mdl_rule_allow);
585}
586
587const char* mdl_robots_disallow_reason(const mdl_robots_t* robots, const char* path)
588{
589 if ((robots == nullptr) || (path == nullptr)) {
590 return nullptr;
591 }
592 const mdl_robots_rule_t* best = internal_best_matching_rule(robots, path);
593 return ((best != nullptr) && (best->kind == k_mdl_rule_disallow)) ? best->path : nullptr;
594}
595
598internal_cache_find(mdl_robots_cache_t* cache, const char* scheme, const char* host)
599{
600 for (size_t i = 0U; i < (size_t)k_mdl_robots_max_hosts; ++i) {
601 if (cache->hosts[i].used && (strcmp(cache->hosts[i].scheme, scheme) == 0) &&
602 (strcmp(cache->hosts[i].host, host) == 0)) {
603 return &cache->hosts[i];
604 }
605 }
606 return nullptr;
607}
608
611{
612 for (size_t i = 0U; i < (size_t)k_mdl_robots_max_hosts; ++i) {
613 if (!cache->hosts[i].used) {
614 return &cache->hosts[i];
615 }
616 }
617 return &cache->overflow;
618}
619
621 const char* scheme,
622 const char* host,
623 const char* ua_token,
625 void* ctx,
626 char* scratch,
627 size_t scratch_cap)
628{
629 if ((cache == nullptr) || (scheme == nullptr) || (host == nullptr) || (ua_token == nullptr) ||
630 (fetch == nullptr) || (scratch == nullptr) || (scratch_cap == 0U) ||
631 ((strcmp(scheme, "http") != 0) && (strcmp(scheme, "https") != 0)) ||
633 return nullptr;
634 }
635 mdl_robots_cache_entry_t* e = internal_cache_find(cache, scheme, host);
636 if (e == nullptr) {
637 e = internal_cache_slot(cache);
639 e->rules.valid = true;
640 (void)snprintf(e->scheme, sizeof(e->scheme), "%s", scheme);
641 (void)snprintf(e->host, sizeof(e->host), "%s", host);
642 char url[k_robots_url_max];
643 const int url_len = snprintf(url, sizeof(url), "%s://%s/robots.txt", scheme, host);
644 if ((url_len < 0) || ((size_t)url_len >= sizeof(url))) {
645 e->disallow_all = true;
646 e->used = true;
647 return nullptr;
648 }
649 size_t got = 0U;
650 const mdl_robots_fetch_result_t rc = fetch(ctx, url, scratch, scratch_cap, &got);
651 if (rc == k_mdl_robots_fetch_denied) {
652 e->disallow_all = true;
653 } else if (rc == k_mdl_robots_fetch_ok) {
654 mdl_robots_parse(scratch, got, ua_token, &e->rules);
655 if (!e->rules.valid) {
656 e->disallow_all = true;
657 }
658 }
659 e->used = true;
660 }
661 return e->disallow_all ? nullptr : &e->rules;
662}
#define nullptr
@ k_ms_per_s
Milliseconds per second.
static void internal_harvest(const char *text, size_t len, const char *ua_token, int target, bool wildcard_target, mdl_robots_t *out)
Pass 2: collect rules/crawl-delay from the selected group(s).
Definition mdl_robots.c:404
mdl_robots_local_size_t
Local parse/format sizes.
Definition mdl_robots.c:22
@ k_robots_url_max
Max /robots.txt URL length.
Definition mdl_robots.c:24
@ k_robots_line_max
Max robots.txt line length.
Definition mdl_robots.c:23
static const mdl_robots_rule_t * internal_best_matching_rule(const mdl_robots_t *robots, const char *path)
Longest matching rule for path (Allow wins ties), or NULL.
Definition mdl_robots.c:559
void mdl_robots_parse(const char *text, size_t len, const char *ua_token, mdl_robots_t *out)
Parse robots.txt text for the group matching ua_token.
Definition mdl_robots.c:450
static bool internal_ci_prefix(const char *prefix, const char *s)
True if prefix (case-insensitive) is a prefix of s.
Definition mdl_robots.c:95
bool mdl_robots_allows(const mdl_robots_t *robots, const char *path)
Decide whether path is allowed by a parsed robots group.
Definition mdl_robots.c:578
static bool internal_robots_match(const char *pat, size_t len, const char *path)
Match a rule pattern (honouring a trailing $) against path.
Definition mdl_robots.c:548
static int internal_agent_spec(const char *agent, const char *ua_token)
Specificity of a robots user-agent token vs ours (-1 = no match).
Definition mdl_robots.c:215
static void internal_add_rule(mdl_robots_t *out, mdl_robots_rule_kind_t kind, const char *value)
Append an Allow/Disallow rule (non-empty patterns only).
Definition mdl_robots.c:313
mdl_robots_spec_t
"No user-agent match" sentinel for the specificity score.
Definition mdl_robots.c:34
@ k_spec_none
No group matched our user-agent.
Definition mdl_robots.c:35
mdl_robots_ms_t
Millisecond conversions and the crawl-delay ceiling.
Definition mdl_robots.c:28
@ k_crawl_cap_ms
Clamp a Crawl-delay to at most 60s.
Definition mdl_robots.c:30
static mdl_robots_cache_entry_t * internal_cache_slot(mdl_robots_cache_t *cache)
Pick a free cache slot, or the dedicated uncached overflow slot.
Definition mdl_robots.c:610
static bool internal_group_selected(int target, bool wildcard_target, int grp_spec, bool grp_wild)
True if a group of this specificity is the selected group.
Definition mdl_robots.c:293
static bool internal_scan_spec(const char *text, size_t len, const char *ua_token, int *best, bool *wild)
Pass 1: find the best specific match length and wildcard presence.
Definition mdl_robots.c:249
static mdl_robots_cache_entry_t * internal_cache_find(mdl_robots_cache_t *cache, const char *scheme, const char *host)
Locate an existing cache entry for one origin, or NULL if absent.
Definition mdl_robots.c:598
static char * internal_trim_ws(char *s)
Trim leading/trailing ASCII whitespace in place; return start.
Definition mdl_robots.c:108
static bool internal_glob_prefix(const char *pat, size_t patlen, const char *path, bool anchored)
Match pat (len patlen, * glob) against a prefix of path.
Definition mdl_robots.c:499
static bool internal_next_line(const char **pp, const char *end, char *buf, size_t cap, bool *truncated)
Read one line into buf; strip its # comment and trim whitespace.
Definition mdl_robots.c:144
static char internal_lower_ascii(char c)
ASCII lower-case of one character (locale-independent).
Definition mdl_robots.c:51
static bool internal_ieq(const char *a, const char *b)
Case-insensitive equality of two NUL-terminated strings.
Definition mdl_robots.c:71
const char * mdl_robots_disallow_reason(const mdl_robots_t *robots, const char *path)
Return the Disallow pattern that forbids path, or NULL if allowed.
Definition mdl_robots.c:587
static void internal_apply_directive(mdl_robots_t *out, const char *field, const char *value)
Apply one non-user-agent directive line to the selected group.
Definition mdl_robots.c:376
const mdl_robots_t * mdl_robots_cache_consult(mdl_robots_cache_t *cache, const char *scheme, const char *host, const char *ua_token, mdl_robots_fetch_fn fetch, void *ctx, char *scratch, size_t scratch_cap)
Return the cached robots rules for host, fetching on first contact.
Definition mdl_robots.c:620
static void internal_set_crawl(mdl_robots_t *out, const char *value)
Record the strictest Crawl-delay seen (clamped to the ceiling).
Definition mdl_robots.c:345
static bool internal_split_field(char *line, char **field, char **value)
Split a trimmed line into field/value on the first :.
Definition mdl_robots.c:188
robots.txt parser, path matcher, and per-host cache.
mdl_robots_fetch_result_t(* mdl_robots_fetch_fn)(void *ctx, const char *robots_url, char *buf, size_t cap, size_t *out_len)
Fetch-callback type: retrieve robots_url into buf.
Definition mdl_robots.h:201
@ k_mdl_robots_max_rules
Max Allow/Disallow rules retained.
Definition mdl_robots.h:27
@ k_mdl_robots_path_max
Max bytes per rule path pattern.
Definition mdl_robots.h:28
mdl_robots_rule_kind_t
Whether a rule permits or forbids a matching path.
Definition mdl_robots.h:38
@ k_mdl_rule_disallow
A Disallow rule.
Definition mdl_robots.h:39
@ k_mdl_rule_allow
An Allow rule.
Definition mdl_robots.h:40
mdl_robots_fetch_result_t
Outcome class of a robots.txt fetch, per RFC 9309 convention.
Definition mdl_robots.h:44
@ k_mdl_robots_fetch_denied
5xx status: disallow all.
Definition mdl_robots.h:47
@ k_mdl_robots_fetch_ok
A body was retrieved to parse.
Definition mdl_robots.h:45
@ k_mdl_robots_host_max
Max host string bytes.
Definition mdl_robots.h:34
@ k_mdl_robots_max_hosts
Distinct hosts cached per run.
Definition mdl_robots.h:33
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
size_t strlen(const char *s)
Calculate string length.
char * strchr(const char *s, int c)
Locate first occurrence of character in string.
size_t strnlen(const char *s, size_t maxlen)
Calculate bounded string length.
One host's cached robots.txt outcome.
Definition mdl_robots.h:167
char scheme[6]
Lower-case http or https.
Definition mdl_robots.h:168
bool used
Entry is populated.
Definition mdl_robots.h:170
mdl_robots_t rules
Parsed rules for our UA.
Definition mdl_robots.h:172
char host[k_mdl_robots_host_max]
Host this entry describes.
Definition mdl_robots.h:169
bool disallow_all
5xx convention: refuse all.
Definition mdl_robots.h:171
Fixed-size per-host robots.txt cache for one run.
Definition mdl_robots.h:185
mdl_robots_cache_entry_t overflow
Full-cache scratch entry.
Definition mdl_robots.h:187
mdl_robots_cache_entry_t hosts[k_mdl_robots_max_hosts]
Cached hosts.
Definition mdl_robots.h:186
One Allow/Disallow rule from the selected group.
Definition mdl_robots.h:58
mdl_robots_rule_kind_t kind
Allow or disallow.
Definition mdl_robots.h:59
char path[k_mdl_robots_path_max]
Path pattern.
Definition mdl_robots.h:61
uint16_t len
Pattern length in bytes.
Definition mdl_robots.h:60
Rules and crawl-delay for the group matching our user-agent.
Definition mdl_robots.h:74
bool have_crawl_delay
A Crawl-delay was seen.
Definition mdl_robots.h:78
bool valid
False if input exceeded a bound.
Definition mdl_robots.h:77
uint32_t crawl_delay_ms
Crawl-delay in ms.
Definition mdl_robots.h:79
mdl_robots_rule_t rules[k_mdl_robots_max_rules]
Selected group's rules.
Definition mdl_robots.h:75
size_t count
Number of valid rules.
Definition mdl_robots.h:76