commit 64d99af

grunfink  ·  2025-05-04 09:05:47 +0000 UTC
parent bca51ef
xs_webmention.h new file.
6 files changed,  +128, -8
M snac.c
+3, -3
 1@@ -56,7 +56,7 @@ format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \
 2  xs_time.h xs_match.h snac.h http_codes.h
 3 html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \
 4  xs_time.h xs_mime.h xs_match.h xs_html.h xs_curl.h xs_unicode.h xs_url.h \
 5- snac.h http_codes.h
 6+ xs_random.h snac.h http_codes.h
 7 http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \
 8  snac.h http_codes.h
 9 httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_unix_socket.h \
10@@ -71,10 +71,10 @@ sandbox.o: sandbox.c xs.h snac.h http_codes.h
11 snac.o: snac.c xs.h xs_hex.h xs_io.h xs_unicode_tbl.h xs_unicode.h \
12  xs_json.h xs_curl.h xs_openssl.h xs_socket.h xs_unix_socket.h xs_url.h \
13  xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h xs_random.h \
14- xs_match.h xs_fcgi.h xs_html.h xs_po.h snac.h http_codes.h
15+ xs_match.h xs_fcgi.h xs_html.h xs_po.h xs_webmention.h snac.h \
16+ http_codes.h
17 upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h http_codes.h
18 utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
19  xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h
20 webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \
21  http_codes.h
22-tests/smtp.o: tests/smtp.c xs.h xs_curl.h
+3, -2
 1@@ -45,7 +45,7 @@ format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \
 2  xs_time.h xs_match.h snac.h http_codes.h
 3 html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \
 4  xs_time.h xs_mime.h xs_match.h xs_html.h xs_curl.h xs_unicode.h xs_url.h \
 5- snac.h http_codes.h
 6+ xs_random.h snac.h http_codes.h
 7 http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \
 8  snac.h http_codes.h
 9 httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_unix_socket.h \
10@@ -60,7 +60,8 @@ sandbox.o: sandbox.c xs.h snac.h http_codes.h
11 snac.o: snac.c xs.h xs_hex.h xs_io.h xs_unicode_tbl.h xs_unicode.h \
12  xs_json.h xs_curl.h xs_openssl.h xs_socket.h xs_unix_socket.h xs_url.h \
13  xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h xs_random.h \
14- xs_match.h xs_fcgi.h xs_html.h xs_po.h snac.h http_codes.h
15+ xs_match.h xs_fcgi.h xs_html.h xs_po.h xs_webmention.h snac.h \
16+ http_codes.h
17 upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h http_codes.h
18 utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
19  xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h
M snac.c
+1, -0
1@@ -25,6 +25,7 @@
2 #include "xs_fcgi.h"
3 #include "xs_html.h"
4 #include "xs_po.h"
5+#include "xs_webmention.h"
6 
7 #include "snac.h"
8 
+2, -2
 1@@ -225,7 +225,7 @@ int xs_smtp_request(const char *url, const char *user, const char *pass,
 2 
 3     if (use_ssl)
 4         curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
 5-    
 6+
 7     curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from);
 8 
 9     rcpt = curl_slist_append(rcpt, to);
10@@ -236,7 +236,7 @@ int xs_smtp_request(const char *url, const char *user, const char *pass,
11     curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
12 
13     res = curl_easy_perform(curl);
14-    
15+
16     curl_easy_cleanup(curl);
17     curl_slist_free_all(rcpt);
18 
+1, -1
1@@ -1 +1 @@
2-/* d467dc71e518603250a55c8a67e26cf40e1710e9 2025-02-14T10:21:15+01:00 */
3+/* 871d420cef893b6efe32869407294baf084ce3ab 2025-05-04T11:01:01+02:00 */
+118, -0
  1@@ -0,0 +1,118 @@
  2+/* copyright (c) 2025 grunfink et al. / MIT license */
  3+
  4+#ifndef _XS_WEBMENTION_H
  5+
  6+#define _XS_WEBMENTION_H
  7+
  8+int xs_webmention_send(const char *source, const char *target, const char *user_agent);
  9+
 10+
 11+#ifdef XS_IMPLEMENTATION
 12+
 13+int xs_webmention_send(const char *source, const char *target, const char *user_agent)
 14+/* sends a Webmention to target.
 15+   Returns: < 0, error; 0, no Webmention endpoint; > 0, Webmention sent */
 16+{
 17+    int status = 0;
 18+    xs *endpoint = NULL;
 19+
 20+    xs *ua = xs_fmt("%s (Webmention)", user_agent ? user_agent : "xs_webmention");
 21+    xs *headers = xs_dict_new();
 22+    headers = xs_dict_set(headers, "accept", "text/html");
 23+    headers = xs_dict_set(headers, "user-agent", ua);
 24+
 25+    xs *h_req = NULL;
 26+    int p_size = 0;
 27+
 28+    /* try first a HEAD, to see if there is a Webmention Link header */
 29+    h_req = xs_http_request("HEAD", target, headers, NULL, 0, &status, NULL, &p_size, 0);
 30+
 31+    /* return immediate failures */
 32+    if (status < 200 || status > 299)
 33+        return -1;
 34+
 35+    const char *link = xs_dict_get(h_req, "link");
 36+
 37+    if (xs_is_string(link) && xs_regex_match(link, "rel *= *(\"|')?webmention")) {
 38+        /* endpoint is between < and > */
 39+        xs *r = xs_regex_select_n(link, "<[^>]+>", 1);
 40+
 41+        if (xs_list_len(r) == 1) {
 42+            endpoint = xs_dup(xs_list_get(r, 0));
 43+            endpoint = xs_strip_chars_i(endpoint, "<>");
 44+        }
 45+    }
 46+
 47+    if (endpoint == NULL) {
 48+        /* no Link header; get the content */
 49+        xs *g_req = NULL;
 50+        xs *payload = NULL;
 51+
 52+        g_req = xs_http_request("GET", target, headers, NULL, 0, &status, &payload, &p_size, 0);
 53+
 54+        if (status < 200 || status > 299)
 55+            return -1;
 56+
 57+        const char *ctype = xs_dict_get(g_req, "content-type");
 58+
 59+        /* not HTML? no point in looking inside */
 60+        if (!xs_is_string(ctype) || xs_str_in(ctype, "text/html") == -1)
 61+            return -2;
 62+
 63+        if (!xs_is_string(payload))
 64+            return -3;
 65+
 66+        xs *links = xs_regex_select(payload, "<(a +|link +)[^>]+>");
 67+        const char *link;
 68+
 69+        xs_list_foreach(links, link) {
 70+            if (xs_regex_match(link, "rel *= *(\"|')?webmention")) {
 71+                /* found; extract the href */
 72+                xs *r = xs_regex_select_n(link, "href *= *(\"|')?[^\"]+(\"|')", 1);
 73+
 74+                if (xs_list_len(r) == 1) {
 75+                    xs *l = xs_split_n(xs_list_get(r, 0), "=", 1);
 76+
 77+                    if (xs_list_len(l) == 2) {
 78+                        endpoint = xs_dup(xs_list_get(l, 1));
 79+                        endpoint = xs_strip_chars_i(endpoint, " \"'");
 80+
 81+                        break;
 82+                    }
 83+                }
 84+            }
 85+        }
 86+    }
 87+
 88+    /* is it a relative endpoint? */
 89+    if (xs_is_string(endpoint)) {
 90+        if (!xs_startswith(endpoint, "https://") && !xs_startswith(endpoint, "http://")) {
 91+            xs *l = xs_split(target, "/");
 92+
 93+            if (xs_list_len(l) < 3)
 94+                endpoint = xs_free(endpoint);
 95+            else {
 96+                xs *s = xs_fmt("%s/" "/%s", xs_list_get(l, 0), xs_list_get(l, 2));
 97+                endpoint = xs_str_wrap_i(s, endpoint, NULL);
 98+            }
 99+        }
100+    }
101+
102+    if (xs_is_string(endpoint)) {
103+        /* got it! */
104+        headers = xs_dict_set(headers, "content-type", "application/x-www-form-urlencoded");
105+
106+        xs *body = xs_fmt("source=%s&target=%s", source, target);
107+
108+        xs *rsp = xs_http_request("POST", endpoint, headers, body, strlen(body), &status, NULL, 0, 0);
109+    }
110+    else
111+        status = 0;
112+
113+    return status >= 200 && status <= 299;
114+}
115+
116+
117+#endif /* XS_IMPLEMENTATION */
118+
119+#endif /* _XS_WEBMENTION_H */