grunfink пре 1 година
родитељ
комит
64d99af19c
6 измењених фајлова са 128 додато и 8 уклоњено
  1. 3 3
      Makefile
  2. 3 2
      Makefile.NetBSD
  3. 1 0
      snac.c
  4. 2 2
      xs_curl.h
  5. 1 1
      xs_version.h
  6. 118 0
      xs_webmention.h

+ 3 - 3
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
  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 \
 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 \
  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 \
 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
  snac.h http_codes.h
 httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_unix_socket.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 \
 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_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_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
 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 \
 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
  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 \
 webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \
  http_codes.h
  http_codes.h
-tests/smtp.o: tests/smtp.c xs.h xs_curl.h

+ 3 - 2
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
  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 \
 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 \
  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 \
 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
  snac.h http_codes.h
 httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_unix_socket.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 \
 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_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_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
 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 \
 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
  xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h

+ 1 - 0
snac.c

@@ -25,6 +25,7 @@
 #include "xs_fcgi.h"
 #include "xs_fcgi.h"
 #include "xs_html.h"
 #include "xs_html.h"
 #include "xs_po.h"
 #include "xs_po.h"
+#include "xs_webmention.h"
 
 
 #include "snac.h"
 #include "snac.h"
 
 

+ 2 - 2
xs_curl.h

@@ -225,7 +225,7 @@ int xs_smtp_request(const char *url, const char *user, const char *pass,
 
 
     if (use_ssl)
     if (use_ssl)
         curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
         curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
-    
+
     curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from);
     curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from);
 
 
     rcpt = curl_slist_append(rcpt, to);
     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);
     curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
 
 
     res = curl_easy_perform(curl);
     res = curl_easy_perform(curl);
-    
+
     curl_easy_cleanup(curl);
     curl_easy_cleanup(curl);
     curl_slist_free_all(rcpt);
     curl_slist_free_all(rcpt);
 
 

+ 1 - 1
xs_version.h

@@ -1 +1 @@
-/* d467dc71e518603250a55c8a67e26cf40e1710e9 2025-02-14T10:21:15+01:00 */
+/* 871d420cef893b6efe32869407294baf084ce3ab 2025-05-04T11:01:01+02:00 */

+ 118 - 0
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 */