snac2

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

commit 64d99af19ce864ffefec50fcf0d19d2d65411c35
parent bca51ef5ba10fc38d804d5d83f26575e02190663
Author: grunfink <grunfink@comam.es>
Date:   Sun,  4 May 2025 11:05:47 +0200

xs_webmention.h new file.

Diffstat:
MMakefile | 6+++---
MMakefile.NetBSD | 5+++--
Msnac.c | 1+
Mxs_curl.h | 4++--
Mxs_version.h | 2+-
Axs_webmention.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 128 insertions(+), 8 deletions(-)

diff --git a/Makefile b/Makefile @@ -56,7 +56,7 @@ format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ xs_time.h xs_match.h snac.h http_codes.h html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ xs_time.h xs_mime.h xs_match.h xs_html.h xs_curl.h xs_unicode.h xs_url.h \ - snac.h http_codes.h + xs_random.h snac.h http_codes.h http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ snac.h http_codes.h httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_unix_socket.h \ @@ -71,10 +71,10 @@ sandbox.o: sandbox.c xs.h snac.h http_codes.h snac.o: snac.c xs.h xs_hex.h xs_io.h xs_unicode_tbl.h xs_unicode.h \ xs_json.h xs_curl.h xs_openssl.h xs_socket.h xs_unix_socket.h xs_url.h \ xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h xs_random.h \ - xs_match.h xs_fcgi.h xs_html.h xs_po.h snac.h http_codes.h + xs_match.h xs_fcgi.h xs_html.h xs_po.h xs_webmention.h snac.h \ + http_codes.h upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h http_codes.h utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \ xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \ http_codes.h -tests/smtp.o: tests/smtp.c xs.h xs_curl.h diff --git a/Makefile.NetBSD b/Makefile.NetBSD @@ -45,7 +45,7 @@ format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ xs_time.h xs_match.h snac.h http_codes.h html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ xs_time.h xs_mime.h xs_match.h xs_html.h xs_curl.h xs_unicode.h xs_url.h \ - snac.h http_codes.h + xs_random.h snac.h http_codes.h http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ snac.h http_codes.h httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_unix_socket.h \ @@ -60,7 +60,8 @@ sandbox.o: sandbox.c xs.h snac.h http_codes.h snac.o: snac.c xs.h xs_hex.h xs_io.h xs_unicode_tbl.h xs_unicode.h \ xs_json.h xs_curl.h xs_openssl.h xs_socket.h xs_unix_socket.h xs_url.h \ xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h xs_random.h \ - xs_match.h xs_fcgi.h xs_html.h xs_po.h snac.h http_codes.h + xs_match.h xs_fcgi.h xs_html.h xs_po.h xs_webmention.h snac.h \ + http_codes.h upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h http_codes.h utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \ xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h diff --git a/snac.c b/snac.c @@ -25,6 +25,7 @@ #include "xs_fcgi.h" #include "xs_html.h" #include "xs_po.h" +#include "xs_webmention.h" #include "snac.h" diff --git a/xs_curl.h b/xs_curl.h @@ -225,7 +225,7 @@ int xs_smtp_request(const char *url, const char *user, const char *pass, if (use_ssl) curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL); - + curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from); rcpt = curl_slist_append(rcpt, to); @@ -236,7 +236,7 @@ int xs_smtp_request(const char *url, const char *user, const char *pass, curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); res = curl_easy_perform(curl); - + curl_easy_cleanup(curl); curl_slist_free_all(rcpt); diff --git a/xs_version.h b/xs_version.h @@ -1 +1 @@ -/* d467dc71e518603250a55c8a67e26cf40e1710e9 2025-02-14T10:21:15+01:00 */ +/* 871d420cef893b6efe32869407294baf084ce3ab 2025-05-04T11:01:01+02:00 */ diff --git a/xs_webmention.h b/xs_webmention.h @@ -0,0 +1,118 @@ +/* copyright (c) 2025 grunfink et al. / MIT license */ + +#ifndef _XS_WEBMENTION_H + +#define _XS_WEBMENTION_H + +int xs_webmention_send(const char *source, const char *target, const char *user_agent); + + +#ifdef XS_IMPLEMENTATION + +int xs_webmention_send(const char *source, const char *target, const char *user_agent) +/* sends a Webmention to target. + Returns: < 0, error; 0, no Webmention endpoint; > 0, Webmention sent */ +{ + int status = 0; + xs *endpoint = NULL; + + xs *ua = xs_fmt("%s (Webmention)", user_agent ? user_agent : "xs_webmention"); + xs *headers = xs_dict_new(); + headers = xs_dict_set(headers, "accept", "text/html"); + headers = xs_dict_set(headers, "user-agent", ua); + + xs *h_req = NULL; + int p_size = 0; + + /* try first a HEAD, to see if there is a Webmention Link header */ + h_req = xs_http_request("HEAD", target, headers, NULL, 0, &status, NULL, &p_size, 0); + + /* return immediate failures */ + if (status < 200 || status > 299) + return -1; + + const char *link = xs_dict_get(h_req, "link"); + + if (xs_is_string(link) && xs_regex_match(link, "rel *= *(\"|')?webmention")) { + /* endpoint is between < and > */ + xs *r = xs_regex_select_n(link, "<[^>]+>", 1); + + if (xs_list_len(r) == 1) { + endpoint = xs_dup(xs_list_get(r, 0)); + endpoint = xs_strip_chars_i(endpoint, "<>"); + } + } + + if (endpoint == NULL) { + /* no Link header; get the content */ + xs *g_req = NULL; + xs *payload = NULL; + + g_req = xs_http_request("GET", target, headers, NULL, 0, &status, &payload, &p_size, 0); + + if (status < 200 || status > 299) + return -1; + + const char *ctype = xs_dict_get(g_req, "content-type"); + + /* not HTML? no point in looking inside */ + if (!xs_is_string(ctype) || xs_str_in(ctype, "text/html") == -1) + return -2; + + if (!xs_is_string(payload)) + return -3; + + xs *links = xs_regex_select(payload, "<(a +|link +)[^>]+>"); + const char *link; + + xs_list_foreach(links, link) { + if (xs_regex_match(link, "rel *= *(\"|')?webmention")) { + /* found; extract the href */ + xs *r = xs_regex_select_n(link, "href *= *(\"|')?[^\"]+(\"|')", 1); + + if (xs_list_len(r) == 1) { + xs *l = xs_split_n(xs_list_get(r, 0), "=", 1); + + if (xs_list_len(l) == 2) { + endpoint = xs_dup(xs_list_get(l, 1)); + endpoint = xs_strip_chars_i(endpoint, " \"'"); + + break; + } + } + } + } + } + + /* is it a relative endpoint? */ + if (xs_is_string(endpoint)) { + if (!xs_startswith(endpoint, "https://") && !xs_startswith(endpoint, "http://")) { + xs *l = xs_split(target, "/"); + + if (xs_list_len(l) < 3) + endpoint = xs_free(endpoint); + else { + xs *s = xs_fmt("%s/" "/%s", xs_list_get(l, 0), xs_list_get(l, 2)); + endpoint = xs_str_wrap_i(s, endpoint, NULL); + } + } + } + + if (xs_is_string(endpoint)) { + /* got it! */ + headers = xs_dict_set(headers, "content-type", "application/x-www-form-urlencoded"); + + xs *body = xs_fmt("source=%s&target=%s", source, target); + + xs *rsp = xs_http_request("POST", endpoint, headers, body, strlen(body), &status, NULL, 0, 0); + } + else + status = 0; + + return status >= 200 && status <= 299; +} + + +#endif /* XS_IMPLEMENTATION */ + +#endif /* _XS_WEBMENTION_H */