From f88bdd796045dd206b38e07adb13b5af4489b4c5 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 24 Jun 2025 14:06:41 +0200 Subject: Added a webmention hook. --- html.c | 20 ++++++++++++++----- httpd.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ xs_webmention.h | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/html.c b/html.c index c472e3b..672ea44 100644 --- a/html.c +++ b/html.c @@ -868,6 +868,14 @@ xs_html *html_user_head(snac *user, const char *desc, const char *url) xs_html_attr("type", "application/activity+json"), xs_html_attr("href", url ? url : user->actor))); + /* webmention hook */ + xs *wbh = xs_fmt("%s/webmention-hook", srv_baseurl); + + xs_html_add(head, + xs_html_sctag("link", + xs_html_attr("rel", "webmention"), + xs_html_attr("href", wbh))); + return head; } @@ -3407,13 +3415,15 @@ xs_str *html_notifications(snac *user, int skip, int show) const char *actor_id = xs_dict_get(noti, "actor"); xs *actor = NULL; + xs *a_name = NULL; - if (!valid_status(actor_get(actor_id, &actor))) - continue; + if (valid_status(actor_get(actor_id, &actor))) + a_name = actor_name(actor, proxy); + else + a_name = xs_dup(actor_id); - xs *a_name = actor_name(actor, proxy); - xs *label_sanatized = sanitize(type); - const char *label = label_sanatized; + xs *label_sanitized = sanitize(type); + const char *label = label_sanitized; if (strcmp(type, "Create") == 0) label = L("Mention"); diff --git a/httpd.c b/httpd.c index 15634d1..af78e09 100644 --- a/httpd.c +++ b/httpd.c @@ -12,6 +12,7 @@ #include "xs_openssl.h" #include "xs_fcgi.h" #include "xs_html.h" +#include "xs_webmention.h" #include "snac.h" @@ -373,6 +374,63 @@ int server_get_handler(xs_dict *req, const char *q_path, } +int server_post_handler(const xs_dict *req, const char *q_path, + char *payload, int p_size, + char **body, int *b_size, char **ctype) +{ + int status = 0; + + if (strcmp(q_path, "/webmention-hook") == 0) { + status = HTTP_STATUS_BAD_REQUEST; + + const xs_dict *p_vars = xs_dict_get(req, "p_vars"); + + if (!xs_is_dict(p_vars)) + return status; + + const char *source = xs_dict_get(p_vars, "source"); + const char *target = xs_dict_get(p_vars, "target"); + + if (!xs_is_string(source) || !xs_is_string(target)) { + srv_debug(1, xs_fmt("webmention-hook bad source or target")); + return status; + } + + if (!xs_startswith(target, srv_baseurl)) { + srv_debug(1, xs_fmt("webmention-hook unknown target %s", target)); + return status; + } + + if (!object_here(target)) { + srv_debug(0, xs_fmt("webmention-hook target %s not / no longer here", target)); + return status; + } + + /* get the user */ + xs *s1 = xs_replace(target, srv_baseurl, ""); + + xs *l1 = xs_split(s1, "/"); + const char *uid = xs_list_get(l1, 1); + snac user; + + if (!xs_is_string(uid) || !user_open(&user, uid)) + return status; + + int r = xs_webmention_hook(source, target, USER_AGENT); + + if (r > 0) + notify_add(&user, "Webmention", NULL, source, target, xs_stock(XSTYPE_DICT)); + + srv_log(xs_fmt("webmention-hook source=%s target=%s %d", source, target, r)); + + user_free(&user); + status = HTTP_STATUS_OK; + } + + return status; +} + + void httpd_connection(FILE *f) /* the connection processor */ { @@ -444,6 +502,10 @@ void httpd_connection(FILE *f) else if (strcmp(method, "POST") == 0) { + if (status == 0) + status = server_post_handler(req, q_path, + payload, p_size, &body, &b_size, &ctype); + #ifndef NO_MASTODON_API if (status == 0) status = oauth_post_handler(req, q_path, diff --git a/xs_webmention.h b/xs_webmention.h index 8415629..e177573 100644 --- a/xs_webmention.h +++ b/xs_webmention.h @@ -5,6 +5,7 @@ #define _XS_WEBMENTION_H int xs_webmention_send(const char *source, const char *target, const char *user_agent); +int xs_webmention_hook(const char *source, const char *target, const char *user_agent); #ifdef XS_IMPLEMENTATION @@ -118,6 +119,47 @@ int xs_webmention_send(const char *source, const char *target, const char *user_ } +int xs_webmention_hook(const char *source, const char *target, const char *user_agent) +/* a Webmention has been received for a target that is ours; check if the source + really contains a link to our target */ +{ + int status = 0; + + 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 *g_req = NULL; + xs *payload = NULL; + int p_size = 0; + + g_req = xs_http_request("GET", source, headers, NULL, 0, &status, &payload, &p_size, 0); + + if (status < 200 || status > 299) + return -1; + + if (!xs_is_string(payload)) + return -2; + + /* note: a "rogue" webmention can include a link to our target in commented-out HTML code */ + + xs *links = xs_regex_select(payload, "<(a +|link +)[^>]+>"); + const char *link; + + status = 0; + xs_list_foreach(links, link) { + /* if the link contains our target, it's valid */ + if (xs_str_in(link, target) != -1) { + status = 1; + break; + } + } + + return status; +} + + #endif /* XS_IMPLEMENTATION */ #endif /* _XS_WEBMENTION_H */ -- cgit v1.2.3