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 */