snac2

Fork of https://codeberg.org/grunfink/snac2
git clone https://git.inz.fi/snac2
Log | Files | Refs | README | LICENSE

xs_webmention.h (3712B)


      1 /* copyright (c) 2025 grunfink et al. / MIT license */
      2 
      3 #ifndef _XS_WEBMENTION_H
      4 
      5 #define _XS_WEBMENTION_H
      6 
      7 int xs_webmention_send(const char *source, const char *target, const char *user_agent);
      8 
      9 
     10 #ifdef XS_IMPLEMENTATION
     11 
     12 int xs_webmention_send(const char *source, const char *target, const char *user_agent)
     13 /* sends a Webmention to target.
     14    Returns: < 0, error; 0, no Webmention endpoint; > 0, Webmention sent */
     15 {
     16     int status = 0;
     17     xs *endpoint = NULL;
     18 
     19     xs *ua = xs_fmt("%s (Webmention)", user_agent ? user_agent : "xs_webmention");
     20     xs *headers = xs_dict_new();
     21     headers = xs_dict_set(headers, "accept", "text/html");
     22     headers = xs_dict_set(headers, "user-agent", ua);
     23 
     24     xs *h_req = NULL;
     25     int p_size = 0;
     26 
     27     /* try first a HEAD, to see if there is a Webmention Link header */
     28     h_req = xs_http_request("HEAD", target, headers, NULL, 0, &status, NULL, &p_size, 0);
     29 
     30     /* return immediate failures */
     31     if (status < 200 || status > 299)
     32         return -1;
     33 
     34     const char *link = xs_dict_get(h_req, "link");
     35 
     36     if (xs_is_string(link) && xs_regex_match(link, "rel *= *(\"|')?webmention")) {
     37         /* endpoint is between < and > */
     38         xs *r = xs_regex_select_n(link, "<[^>]+>", 1);
     39 
     40         if (xs_list_len(r) == 1) {
     41             endpoint = xs_dup(xs_list_get(r, 0));
     42             endpoint = xs_strip_chars_i(endpoint, "<>");
     43         }
     44     }
     45 
     46     if (endpoint == NULL) {
     47         /* no Link header; get the content */
     48         xs *g_req = NULL;
     49         xs *payload = NULL;
     50 
     51         g_req = xs_http_request("GET", target, headers, NULL, 0, &status, &payload, &p_size, 0);
     52 
     53         if (status < 200 || status > 299)
     54             return -1;
     55 
     56         const char *ctype = xs_dict_get(g_req, "content-type");
     57 
     58         /* not HTML? no point in looking inside */
     59         if (!xs_is_string(ctype) || xs_str_in(ctype, "text/html") == -1)
     60             return -2;
     61 
     62         if (!xs_is_string(payload))
     63             return -3;
     64 
     65         xs *links = xs_regex_select(payload, "<(a +|link +)[^>]+>");
     66         const char *link;
     67 
     68         xs_list_foreach(links, link) {
     69             if (xs_regex_match(link, "rel *= *(\"|')?webmention")) {
     70                 /* found; extract the href */
     71                 xs *r = xs_regex_select_n(link, "href *= *(\"|')?[^\"]+(\"|')", 1);
     72 
     73                 if (xs_list_len(r) == 1) {
     74                     xs *l = xs_split_n(xs_list_get(r, 0), "=", 1);
     75 
     76                     if (xs_list_len(l) == 2) {
     77                         endpoint = xs_dup(xs_list_get(l, 1));
     78                         endpoint = xs_strip_chars_i(endpoint, " \"'");
     79 
     80                         break;
     81                     }
     82                 }
     83             }
     84         }
     85     }
     86 
     87     /* is it a relative endpoint? */
     88     if (xs_is_string(endpoint)) {
     89         if (!xs_startswith(endpoint, "https://") && !xs_startswith(endpoint, "http://")) {
     90             xs *l = xs_split(target, "/");
     91 
     92             if (xs_list_len(l) < 3)
     93                 endpoint = xs_free(endpoint);
     94             else {
     95                 xs *s = xs_fmt("%s/" "/%s", xs_list_get(l, 0), xs_list_get(l, 2));
     96                 endpoint = xs_str_wrap_i(s, endpoint, NULL);
     97             }
     98         }
     99     }
    100 
    101     if (xs_is_string(endpoint)) {
    102         /* got it! */
    103         headers = xs_dict_set(headers, "content-type", "application/x-www-form-urlencoded");
    104 
    105         xs *body = xs_fmt("source=%s&target=%s", source, target);
    106 
    107         xs *rsp = xs_http_request("POST", endpoint, headers, body, strlen(body), &status, NULL, 0, 0);
    108 
    109         if (status < 200 || status > 299)
    110             status = -4;
    111         else
    112             status = 1;
    113     }
    114     else
    115         status = 0;
    116 
    117     return status;
    118 }
    119 
    120 
    121 #endif /* XS_IMPLEMENTATION */
    122 
    123 #endif /* _XS_WEBMENTION_H */