commit 64d99af
grunfink
·
2025-05-04 09:05:47 +0000 UTC
parent bca51ef
xs_webmention.h new file.
6 files changed,
+128,
-8
M
Makefile
+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 */