/* See LICENSE file for copyright and license details. */ #include #include #include #include #include "robots.h" #include "fetch.h" #include "util.h" /* * Parse robots.txt content for our user-agent. * * We look for rules matching "sbot" first, * then fall back to "*" (wildcard) rules. */ Robots * robots_fetch(const char *domain) { Robots *r; Response *resp; char url[1024]; char *line, *saveptr, *text; int in_our_group, in_star_group, found_our_group; int star_nrules, star_delay; RobotsRule star_rules[MAX_RULES]; r = xmalloc(sizeof(Robots)); r->domain = xstrdup(domain); r->nrules = 0; r->crawl_delay = 0; snprintf(url, sizeof(url), "https://%s/robots.txt", domain); resp = fetch_url(url); if (!resp || resp->status_code >= 400 || !resp->data) { /* No robots.txt = everything allowed */ response_free(resp); return r; } text = xstrdup(resp->data); response_free(resp); in_our_group = 0; in_star_group = 0; found_our_group = 0; star_nrules = 0; star_delay = 0; for (line = strtok_r(text, "\n", &saveptr); line; line = strtok_r(NULL, "\n", &saveptr)) { char *trimmed, *colon, *key, *val; trimmed = str_trim(line); /* Skip empty lines and comments */ if (!*trimmed || *trimmed == '#') continue; /* Strip inline comments */ colon = strchr(trimmed, '#'); if (colon) *colon = '\0'; /* Find key: value */ colon = strchr(trimmed, ':'); if (!colon) continue; *colon = '\0'; key = str_trim(trimmed); val = str_trim(colon + 1); if (strcasecmp(key, "user-agent") == 0) { /* New user-agent group */ if (strcasestr(val, "sbot")) { in_our_group = 1; in_star_group = 0; found_our_group = 1; } else if (strcmp(val, "*") == 0 && !found_our_group) { in_star_group = 1; in_our_group = 0; } else { in_our_group = 0; in_star_group = 0; } continue; } if (strcasecmp(key, "disallow") == 0) { if (in_our_group && r->nrules < MAX_RULES) { r->rules[r->nrules].path = xstrdup(val); r->rules[r->nrules].allow = 0; r->nrules++; } else if (in_star_group && star_nrules < MAX_RULES) { star_rules[star_nrules].path = xstrdup(val); star_rules[star_nrules].allow = 0; star_nrules++; } } else if (strcasecmp(key, "allow") == 0) { if (in_our_group && r->nrules < MAX_RULES) { r->rules[r->nrules].path = xstrdup(val); r->rules[r->nrules].allow = 1; r->nrules++; } else if (in_star_group && star_nrules < MAX_RULES) { star_rules[star_nrules].path = xstrdup(val); star_rules[star_nrules].allow = 1; star_nrules++; } } else if (strcasecmp(key, "crawl-delay") == 0) { int delay = atoi(val); if (delay > 0) { if (in_our_group) r->crawl_delay = delay; else if (in_star_group) star_delay = delay; } } } /* If no specific rules for us, use wildcard rules */ if (!found_our_group && star_nrules > 0) { int i; for (i = 0; i < star_nrules; i++) r->rules[i] = star_rules[i]; r->nrules = star_nrules; r->crawl_delay = star_delay; } else if (!found_our_group) { /* Free star rules if we didn't use them */ int i; for (i = 0; i < star_nrules; i++) free(star_rules[i].path); } free(text); return r; } int robots_allowed(Robots *r, const char *path) { int i, best_len, allowed; if (!r || r->nrules == 0) return 1; /* * Match the most specific (longest) rule. * If multiple rules of same length, Allow wins. */ best_len = -1; allowed = 1; for (i = 0; i < r->nrules; i++) { int plen = strlen(r->rules[i].path); /* Empty disallow = allow all */ if (plen == 0 && !r->rules[i].allow) continue; if (strncmp(path, r->rules[i].path, plen) == 0) { if (plen > best_len) { best_len = plen; allowed = r->rules[i].allow; } else if (plen == best_len && r->rules[i].allow) { allowed = 1; } } } return allowed; } int robots_delay(Robots *r) { if (!r) return 0; return r->crawl_delay; } void robots_free(Robots *r) { int i; if (!r) return; for (i = 0; i < r->nrules; i++) free(r->rules[i].path); free(r->domain); free(r); }