2.78 xs_webmention.h
  1/* copyright (c) 2025 grunfink et al. / MIT license */
  2
  3#ifndef _XS_WEBMENTION_H
  4
  5#define _XS_WEBMENTION_H
  6
  7int xs_webmention_send(const char *source, const char *target, const char *user_agent);
  8
  9
 10#ifdef XS_IMPLEMENTATION
 11
 12int 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 */