summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--Makefile.NetBSD2
-rw-r--r--README.md7
-rw-r--r--RELEASE_NOTES.md20
-rw-r--r--TODO.md24
-rw-r--r--activitypub.c420
-rw-r--r--data.c586
-rw-r--r--doc/snac.815
-rw-r--r--format.c19
-rw-r--r--html.c703
-rw-r--r--http.c36
-rw-r--r--httpd.c66
-rw-r--r--main.c26
-rw-r--r--mastoapi.c495
-rw-r--r--snac.h69
-rw-r--r--upgrade.c73
-rw-r--r--utils.c17
-rw-r--r--webfinger.c49
-rw-r--r--xs.h254
-rw-r--r--xs_curl.h6
-rw-r--r--xs_fcgi.h4
-rw-r--r--xs_html.h34
-rw-r--r--xs_httpd.h9
-rw-r--r--xs_json.h23
-rw-r--r--xs_mime.h22
-rw-r--r--xs_regex.h43
-rw-r--r--xs_set.h8
-rw-r--r--xs_unicode.h22
-rw-r--r--xs_url.h13
-rw-r--r--xs_version.h2
30 files changed, 2075 insertions, 994 deletions
diff --git a/Makefile b/Makefile
index 39b44ef..1edf99e 100644
--- a/Makefile
+++ b/Makefile
@@ -36,7 +36,7 @@ uninstall:
36activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ 36activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \
37 xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h 37 xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h
38data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ 38data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \
39 xs_set.h xs_time.h xs_regex.h snac.h 39 xs_set.h xs_time.h xs_regex.h xs_match.h snac.h
40format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ 40format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \
41 xs_time.h snac.h 41 xs_time.h snac.h
42html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ 42html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \
diff --git a/Makefile.NetBSD b/Makefile.NetBSD
index 67c77a5..bb2f1bc 100644
--- a/Makefile.NetBSD
+++ b/Makefile.NetBSD
@@ -38,7 +38,7 @@ uninstall:
38activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ 38activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \
39 xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h 39 xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h
40data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ 40data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \
41 xs_set.h xs_time.h xs_regex.h snac.h 41 xs_set.h xs_time.h xs_regex.h xs_match.h snac.h
42format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ 42format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \
43 xs_time.h snac.h 43 xs_time.h snac.h
44html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ 44html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \
diff --git a/README.md b/README.md
index 1e5c2e8..4cd3777 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,6 @@ Run `make` and then `make install` as root.
58 58
59If you're compiling on NetBSD, you should use the specific provided Makefile and run `make -f Makefile.NetBSD` and then `make -f Makefile.NetBSD install` as root. 59If you're compiling on NetBSD, you should use the specific provided Makefile and run `make -f Makefile.NetBSD` and then `make -f Makefile.NetBSD install` as root.
60 60
61
62From version 2.27, `snac` includes support for the Mastodon API; if you are not interested on it, you can compile it out by running 61From version 2.27, `snac` includes support for the Mastodon API; if you are not interested on it, you can compile it out by running
63 62
64```sh 63```sh
@@ -71,6 +70,12 @@ If your compilation process complains about undefined references to `shm_open()`
71make LDFLAGS=-lrt 70make LDFLAGS=-lrt
72``` 71```
73 72
73If it still gives compilation errors (because your system does not implement the shared memory functions), you can fix it with
74
75```sh
76make CFLAGS=-DWITHOUT_SHM
77```
78
74See the administrator manual on how to proceed from here. 79See the administrator manual on how to proceed from here.
75 80
76## Testing via Docker 81## Testing via Docker
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index f82e722..cea217a 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,9 +1,29 @@
1# Release Notes 1# Release Notes
2 2
3## 2.53
4
5New user feature to search by post content (using regular expressions) or tag.
6
7Added some (partial) support for `Event` object types.
8
9Minor fixes: Allow unboosting your own posts (contributed by khm), CSS fixes for the Dillo browser (contributed by kvibber).
10
3## 2.52 11## 2.52
4 12
5Posts that were liked or boosted can now be unliked and unboosted. 13Posts that were liked or boosted can now be unliked and unboosted.
6 14
15Outgoing message timeouts are no longer hardcoded and can be configured (see `snac(8)` for more information).
16
17Fixed a bug that caused some incorrect unfollows under special conditions (with shared inboxes enabled and users from the same instance that follow each other, the internal message distributor was confused).
18
19Mastodon API: Added support for lists.
20
21Added a header to avoid over-zealous caching in some browsers (contributed by louis77).
22
23Added support for running and federating inside hidden networks like Tor, I2P or Loki (contributed by iwojima).
24
25Fixed an error processing polls coming from Pleroma instances.
26
7## 2.51 27## 2.51
8 28
9Support for custom Emojis has been added; they are no longer hardcoded, but read from the `emojis.json` file at the server base directory. Also, they are no longer limited to string substitutions, but images as external URLs are also supported (see `snac(8)` for more information). 29Support for custom Emojis has been added; they are no longer hardcoded, but read from the `emojis.json` file at the server base directory. Also, they are no longer limited to string substitutions, but images as external URLs are also supported (see `snac(8)` for more information).
diff --git a/TODO.md b/TODO.md
index bdb4f23..db19a14 100644
--- a/TODO.md
+++ b/TODO.md
@@ -10,36 +10,34 @@ Mastodon API: fix whatever the fuck is making the official app and Megalodon to
10 10
11Important: deleting a follower should do more that just delete the object, see https://codeberg.org/grunfink/snac2/issues/43#issuecomment-956721 11Important: deleting a follower should do more that just delete the object, see https://codeberg.org/grunfink/snac2/issues/43#issuecomment-956721
12 12
13Editing / Updating a post does not index newly added hashtags.
14
13## Wishlist 15## Wishlist
14 16
15Implement `Group`-like accounts (i.e. an actor that boosts to their followers all posts that mention it). 17Track 'Event' data types standardization; how to add plan-to-attend and similar activities (more info: https://event-federation.eu/)
16 18
17Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 19Implement "FEP-3b86: Activity Intents" https://codeberg.org/fediverse/fep/src/branch/main/fep/3b86/fep-3b86.md
20
21Track "FEP-ef61: Portable Objects" https://codeberg.org/fediverse/fep/src/branch/main/fep/ef61/fep-ef61.md
22
23Implement `Group`-like accounts (i.e. an actor that boosts to their followers all posts that mention it).
18 24
19Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 25Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104
20 26
21Consider adding Mastodon import functionality (for following_accounts.csv and outbox.json). 27Consider adding Mastodon import functionality (for following_accounts.csv and outbox.json).
22 28
23Consider adding milter-like support to reject posts to mitigate spam.
24
25Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`). 29Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`).
26 30
27Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050
28
29Add support for /share?text=tt&website=url (whatever it is, see https://mastodonshare.com/ for details). 31Add support for /share?text=tt&website=url (whatever it is, see https://mastodonshare.com/ for details).
30 32
31Add support for /authorize_interaction (whatever it is). 33Add support for /authorize_interaction (whatever it is).
32 34
33Add a list of hashtags to drop. 35Add a list of hashtags to drop.
34 36
35Add domain/subdomain flexibility according to https://codeberg.org/grunfink/snac2/issues/3
36
37The 'history' pages are just monthly HTML snapshots of the local timeline. This is ok and cheap and easy, but is problematic if you e.g. intentionally delete a post because it will remain there in the history forever. If you activate local timeline purging, purged entries will remain in the history as 'ghosts', which may or may not be what the user wants. 37The 'history' pages are just monthly HTML snapshots of the local timeline. This is ok and cheap and easy, but is problematic if you e.g. intentionally delete a post because it will remain there in the history forever. If you activate local timeline purging, purged entries will remain in the history as 'ghosts', which may or may not be what the user wants.
38 38
39Implement bulleted lists. Mastodon is crap and won't show them, but other implementations (Friendica, Pleroma) will do. 39Implement bulleted lists. Mastodon is crap and won't show them, but other implementations (Friendica, Pleroma) will do.
40 40
41User request: "will it be possible to click on a link and instead of opening the original instance, we'll be able only to see a list of the posts of this person here in comam?. Something like Mastodon does."
42
43The actual storage system wastes too much disk space (lots of small files that really consume 4k of storage). Consider alternatives. 41The actual storage system wastes too much disk space (lots of small files that really consume 4k of storage). Consider alternatives.
44 42
45## Closed 43## Closed
@@ -311,3 +309,9 @@ Consider implementing the rejection of activities from recently-created accounts
311Consider discarding posts by content using string or regex to mitigate spam (2024-03-14T10:40:14+0100). 309Consider discarding posts by content using string or regex to mitigate spam (2024-03-14T10:40:14+0100).
312 310
313Post edits should preserve the image and the image description somewhat (2024-03-22T09:57:18+0100). 311Post edits should preserve the image and the image description somewhat (2024-03-22T09:57:18+0100).
312
313Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93
314
315Consider adding milter-like support to reject posts to mitigate spam (discarded; 2024-04-20T22:46:35+0200).
316
317Implement support for 'Event' data types. Example: https://fediversity.site/item/e9bdb383-eeb9-4d7d-b2f7-c6401267cae0 (2024-05-12T08:56:27+0200)
diff --git a/activitypub.c b/activitypub.c
index 9a23e14..6e40a88 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -67,7 +67,7 @@ int activitypub_request(snac *user, const char *url, xs_dict **data)
67 xs *response = NULL; 67 xs *response = NULL;
68 xs *payload = NULL; 68 xs *payload = NULL;
69 int p_size; 69 int p_size;
70 char *ctype; 70 const char *ctype;
71 71
72 *data = NULL; 72 *data = NULL;
73 73
@@ -154,20 +154,21 @@ int actor_request(snac *user, const char *actor, xs_dict **data)
154} 154}
155 155
156 156
157char *get_atto(const xs_dict *msg) 157const char *get_atto(const xs_dict *msg)
158/* gets the attributedTo field (an actor) */ 158/* gets the attributedTo field (an actor) */
159{ 159{
160 char *actor = xs_dict_get(msg, "attributedTo"); 160 const xs_val *actor = xs_dict_get(msg, "attributedTo");
161 161
162 /* if the actor is a list of objects (like on Peertube videos), pick the Person */ 162 /* if the actor is a list of objects (like on Peertube videos), pick the Person */
163 if (xs_type(actor) == XSTYPE_LIST) { 163 if (xs_type(actor) == XSTYPE_LIST) {
164 xs_list *p = actor; 164 const xs_list *p = actor;
165 xs_dict *v; 165 int c = 0;
166 const xs_dict *v;
166 actor = NULL; 167 actor = NULL;
167 168
168 while (actor == NULL && xs_list_iter(&p, &v)) { 169 while (actor == NULL && xs_list_next(p, &v, &c)) {
169 if (xs_type(v) == XSTYPE_DICT) { 170 if (xs_type(v) == XSTYPE_DICT) {
170 char *type = xs_dict_get(v, "type"); 171 const char *type = xs_dict_get(v, "type");
171 if (xs_type(type) == XSTYPE_STRING && strcmp(type, "Person") == 0) { 172 if (xs_type(type) == XSTYPE_STRING && strcmp(type, "Person") == 0) {
172 actor = xs_dict_get(v, "id"); 173 actor = xs_dict_get(v, "id");
173 174
@@ -186,12 +187,12 @@ xs_list *get_attachments(const xs_dict *msg)
186/* unify the garbage fire that are the attachments */ 187/* unify the garbage fire that are the attachments */
187{ 188{
188 xs_list *l = xs_list_new(); 189 xs_list *l = xs_list_new();
189 xs_list *p; 190 const xs_list *p;
190 191
191 /* try first the attachments list */ 192 /* try first the attachments list */
192 if (!xs_is_null(p = xs_dict_get(msg, "attachment"))) { 193 if (!xs_is_null(p = xs_dict_get(msg, "attachment"))) {
193 xs *attach = NULL; 194 xs *attach = NULL;
194 xs_val *v; 195 const xs_val *v;
195 196
196 /* ensure it's a list */ 197 /* ensure it's a list */
197 if (xs_type(p) == XSTYPE_DICT) { 198 if (xs_type(p) == XSTYPE_DICT) {
@@ -203,23 +204,24 @@ xs_list *get_attachments(const xs_dict *msg)
203 204
204 if (xs_type(attach) == XSTYPE_LIST) { 205 if (xs_type(attach) == XSTYPE_LIST) {
205 /* does the message have an image? */ 206 /* does the message have an image? */
206 if (xs_type(v = xs_dict_get(msg, "image")) == XSTYPE_DICT) { 207 const xs_dict *d = xs_dict_get(msg, "image");
208 if (xs_type(d) == XSTYPE_DICT) {
207 /* add it to the attachment list */ 209 /* add it to the attachment list */
208 attach = xs_list_append(attach, v); 210 attach = xs_list_append(attach, d);
209 } 211 }
210 } 212 }
211 213
212 /* now iterate the list */ 214 /* now iterate the list */
213 p = attach; 215 int c = 0;
214 while (xs_list_iter(&p, &v)) { 216 while (xs_list_next(attach, &v, &c)) {
215 char *type = xs_dict_get(v, "mediaType"); 217 const char *type = xs_dict_get(v, "mediaType");
216 if (xs_is_null(type)) 218 if (xs_is_null(type))
217 type = xs_dict_get(v, "type"); 219 type = xs_dict_get(v, "type");
218 220
219 if (xs_is_null(type)) 221 if (xs_is_null(type))
220 continue; 222 continue;
221 223
222 char *href = xs_dict_get(v, "url"); 224 const char *href = xs_dict_get(v, "url");
223 if (xs_is_null(href)) 225 if (xs_is_null(href))
224 href = xs_dict_get(v, "href"); 226 href = xs_dict_get(v, "href");
225 if (xs_is_null(href)) 227 if (xs_is_null(href))
@@ -233,7 +235,7 @@ xs_list *get_attachments(const xs_dict *msg)
233 type = mt; 235 type = mt;
234 } 236 }
235 237
236 char *name = xs_dict_get(v, "name"); 238 const char *name = xs_dict_get(v, "name");
237 if (xs_is_null(name)) 239 if (xs_is_null(name))
238 name = xs_dict_get(msg, "name"); 240 name = xs_dict_get(msg, "name");
239 if (xs_is_null(name)) 241 if (xs_is_null(name))
@@ -252,29 +254,31 @@ xs_list *get_attachments(const xs_dict *msg)
252 p = xs_dict_get(msg, "url"); 254 p = xs_dict_get(msg, "url");
253 255
254 if (xs_type(p) == XSTYPE_LIST) { 256 if (xs_type(p) == XSTYPE_LIST) {
255 char *href = NULL; 257 const char *href = NULL;
256 char *type = NULL; 258 const char *type = NULL;
257 xs_val *v; 259 int c = 0;
260 const xs_val *v;
258 261
259 while (href == NULL && xs_list_iter(&p, &v)) { 262 while (href == NULL && xs_list_next(p, &v, &c)) {
260 if (xs_type(v) == XSTYPE_DICT) { 263 if (xs_type(v) == XSTYPE_DICT) {
261 char *mtype = xs_dict_get(v, "type"); 264 const char *mtype = xs_dict_get(v, "type");
262 265
263 if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "Link") == 0) { 266 if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "Link") == 0) {
264 mtype = xs_dict_get(v, "mediaType"); 267 mtype = xs_dict_get(v, "mediaType");
265 xs_list *tag = xs_dict_get(v, "tag"); 268 const xs_list *tag = xs_dict_get(v, "tag");
266 269
267 if (xs_type(mtype) == XSTYPE_STRING && 270 if (xs_type(mtype) == XSTYPE_STRING &&
268 strcmp(mtype, "application/x-mpegURL") == 0 && 271 strcmp(mtype, "application/x-mpegURL") == 0 &&
269 xs_type(tag) == XSTYPE_LIST) { 272 xs_type(tag) == XSTYPE_LIST) {
270 /* now iterate the tag list, looking for a video URL */ 273 /* now iterate the tag list, looking for a video URL */
271 xs_dict *d; 274 const xs_dict *d;
275 int c = 0;
272 276
273 while (href == NULL && xs_list_iter(&tag, &d)) { 277 while (href == NULL && xs_list_next(tag, &d, &c)) {
274 if (xs_type(d) == XSTYPE_DICT) { 278 if (xs_type(d) == XSTYPE_DICT) {
275 if (xs_type(mtype = xs_dict_get(d, "mediaType")) == XSTYPE_STRING && 279 if (xs_type(mtype = xs_dict_get(d, "mediaType")) == XSTYPE_STRING &&
276 xs_startswith(mtype, "video/")) { 280 xs_startswith(mtype, "video/")) {
277 char *h = xs_dict_get(d, "href"); 281 const char *h = xs_dict_get(d, "href");
278 282
279 /* this is probably it */ 283 /* this is probably it */
280 if (xs_type(h) == XSTYPE_STRING) { 284 if (xs_type(h) == XSTYPE_STRING) {
@@ -303,7 +307,7 @@ xs_list *get_attachments(const xs_dict *msg)
303} 307}
304 308
305 309
306int timeline_request(snac *snac, char **id, xs_str **wrk, int level) 310int timeline_request(snac *snac, const char **id, xs_str **wrk, int level)
307/* ensures that an entry and its ancestors are in the timeline */ 311/* ensures that an entry and its ancestors are in the timeline */
308{ 312{
309 int status = 0; 313 int status = 0;
@@ -323,7 +327,7 @@ int timeline_request(snac *snac, char **id, xs_str **wrk, int level)
323 status = activitypub_request(snac, *id, &msg); 327 status = activitypub_request(snac, *id, &msg);
324 328
325 if (valid_status(status)) { 329 if (valid_status(status)) {
326 xs_dict *object = msg; 330 const xs_dict *object = msg;
327 const char *type = xs_dict_get(object, "type"); 331 const char *type = xs_dict_get(object, "type");
328 332
329 /* get the id again from the object, as it may be different */ 333 /* get the id again from the object, as it may be different */
@@ -355,102 +359,35 @@ int timeline_request(snac *snac, char **id, xs_str **wrk, int level)
355 type = "(null)"; 359 type = "(null)";
356 } 360 }
357 361
358 if (xs_match(type, "Note|Page|Article|Video")) { 362 if (xs_match(type, POSTLIKE_OBJECT_TYPE)) {
359 const char *actor = get_atto(object); 363 if (content_match("filter_reject.txt", object))
360
361 if (content_check("filter_reject.txt", object))
362 snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid)); 364 snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid));
363 else { 365 else {
364 /* request (and drop) the actor for this entry */ 366 const char *actor = get_atto(object);
365 if (!xs_is_null(actor))
366 actor_request(snac, actor, NULL);
367
368 /* does it have an ancestor? */
369 char *in_reply_to = xs_dict_get(object, "inReplyTo");
370
371 /* store */
372 timeline_add(snac, nid, object);
373
374 /* recurse! */
375 timeline_request(snac, &in_reply_to, NULL, level + 1);
376 }
377 }
378 }
379 }
380
381 enqueue_request_replies(snac, *id);
382 }
383
384 return status;
385}
386 367
368 if (!xs_is_null(actor)) {
369 /* request (and drop) the actor for this entry */
370 if (!valid_status(actor_request(snac, actor, NULL))) {
371 /* failed? retry later */
372 enqueue_actor_refresh(snac, actor, 60);
373 }
387 374
388void timeline_request_replies(snac *user, const char *id) 375 /* does it have an ancestor? */
389/* requests all replies of a message */ 376 const char *in_reply_to = xs_dict_get(object, "inReplyTo");
390/* FIXME: experimental -- needs more testing */
391{
392 /* FIXME: TEMPORARILY DISABLED */
393 /* Reason: I've found that many of the posts in the 'replies' Collection
394 do not have an inReplyTo field (why??? aren't they 'replies'???).
395 For this reason, these requested objects are not stored as children
396 of the original post and they are shown as out-of-context, top level posts.
397 This process is disabled until I find an elegant way of providing a parent
398 for these 'stray' children. */
399 return;
400
401 xs *msg = NULL;
402
403 if (!valid_status(object_get(id, &msg)))
404 return;
405
406 /* does it have a replies collection? */
407 const xs_dict *replies = xs_dict_get(msg, "replies");
408
409 if (!xs_is_null(replies)) {
410 const char *type = xs_dict_get(replies, "type");
411 const char *first = xs_dict_get(replies, "first");
412
413 if (!xs_is_null(type) && !xs_is_null(first) && strcmp(type, "Collection") == 0) {
414 const char *next = xs_dict_get(first, "next");
415
416 if (!xs_is_null(next)) {
417 xs *rpls = NULL;
418 int status = activitypub_request(user, next, &rpls);
419
420 /* request the Collection of replies */
421 if (valid_status(status)) {
422 xs_list *items = xs_dict_get(rpls, "items");
423
424 if (xs_type(items) == XSTYPE_LIST) {
425 xs_val *v;
426
427 /* request them all */
428 while (xs_list_iter(&items, &v)) {
429 if (xs_type(v) == XSTYPE_DICT) {
430 /* not an id, but the object itself (!) */
431 const char *c_id = xs_dict_get(v, "id");
432
433 if (!xs_is_null(id)) {
434 snac_debug(user, 0, xs_fmt("embedded reply %s", c_id));
435 377
436 object_add(c_id, v); 378 /* store */
379 timeline_add(snac, nid, object);
437 380
438 /* get its own children */ 381 /* recurse! */
439 timeline_request_replies(user, v); 382 timeline_request(snac, &in_reply_to, NULL, level + 1);
440 }
441 }
442 else {
443 snac_debug(user, 0, xs_fmt("request reply %s", v));
444 timeline_request(user, &v, NULL, 0);
445 }
446 } 383 }
447 } 384 }
448 } 385 }
449 else
450 snac_debug(user, 0, xs_fmt("replies request error %s %d", next, status));
451 } 386 }
452 } 387 }
453 } 388 }
389
390 return status;
454} 391}
455 392
456 393
@@ -476,7 +413,7 @@ int send_to_inbox(snac *snac, const xs_str *inbox, const xs_dict *msg,
476 xs_val **payload, int *p_size, int timeout) 413 xs_val **payload, int *p_size, int timeout)
477/* sends a message to an Inbox */ 414/* sends a message to an Inbox */
478{ 415{
479 char *seckey = xs_dict_get(snac->key, "secret"); 416 const char *seckey = xs_dict_get(snac->key, "secret");
480 417
481 return send_to_inbox_raw(snac->actor, seckey, inbox, msg, payload, p_size, timeout); 418 return send_to_inbox_raw(snac->actor, seckey, inbox, msg, payload, p_size, timeout);
482} 419}
@@ -486,7 +423,7 @@ xs_str *get_actor_inbox(const char *actor)
486/* gets an actor's inbox */ 423/* gets an actor's inbox */
487{ 424{
488 xs *data = NULL; 425 xs *data = NULL;
489 char *v = NULL; 426 const char *v = NULL;
490 427
491 if (valid_status(actor_request(NULL, actor, &data))) { 428 if (valid_status(actor_request(NULL, actor, &data))) {
492 /* try first endpoints/sharedInbox */ 429 /* try first endpoints/sharedInbox */
@@ -535,17 +472,17 @@ void post_message(snac *snac, const char *actor, const xs_dict *msg)
535xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public) 472xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public)
536/* returns the list of recipients for a message */ 473/* returns the list of recipients for a message */
537{ 474{
538 char *to = xs_dict_get(msg, "to"); 475 const xs_val *to = xs_dict_get(msg, "to");
539 char *cc = xs_dict_get(msg, "cc"); 476 const xs_val *cc = xs_dict_get(msg, "cc");
540 xs_set rcpts; 477 xs_set rcpts;
541 int n; 478 int n;
542 479
543 xs_set_init(&rcpts); 480 xs_set_init(&rcpts);
544 481
545 char *lists[] = { to, cc, NULL }; 482 const xs_list *lists[] = { to, cc, NULL };
546 for (n = 0; lists[n]; n++) { 483 for (n = 0; lists[n]; n++) {
547 char *l = lists[n]; 484 xs_list *l = (xs_list *)lists[n];
548 char *v; 485 const char *v;
549 xs *tl = NULL; 486 xs *tl = NULL;
550 487
551 /* if it's a string, create a list with only one element */ 488 /* if it's a string, create a list with only one element */
@@ -560,7 +497,7 @@ xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public)
560 if (expand_public && strcmp(v, public_address) == 0) { 497 if (expand_public && strcmp(v, public_address) == 0) {
561 /* iterate the followers and add them */ 498 /* iterate the followers and add them */
562 xs *fwers = follower_list(snac); 499 xs *fwers = follower_list(snac);
563 char *actor; 500 const char *actor;
564 501
565 char *p = fwers; 502 char *p = fwers;
566 while (xs_list_iter(&p, &actor)) 503 while (xs_list_iter(&p, &actor))
@@ -667,13 +604,13 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg)
667 604
668 /* if it's a Follow, it must be explicitly for us */ 605 /* if it's a Follow, it must be explicitly for us */
669 if (xs_match(type, "Follow")) { 606 if (xs_match(type, "Follow")) {
670 char *object = xs_dict_get(c_msg, "object"); 607 const char *object = xs_dict_get(c_msg, "object");
671 return !xs_is_null(object) && strcmp(snac->actor, object) == 0; 608 return !xs_is_null(object) && strcmp(snac->actor, object) == 0;
672 } 609 }
673 610
674 /* only accept Ping directed to us */ 611 /* only accept Ping directed to us */
675 if (xs_match(type, "Ping")) { 612 if (xs_match(type, "Ping")) {
676 char *dest = xs_dict_get(c_msg, "to"); 613 const char *dest = xs_dict_get(c_msg, "to");
677 return !xs_is_null(dest) && strcmp(snac->actor, dest) == 0; 614 return !xs_is_null(dest) && strcmp(snac->actor, dest) == 0;
678 } 615 }
679 616
@@ -688,10 +625,10 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg)
688 if (pub_msg && following_check(snac, actor)) 625 if (pub_msg && following_check(snac, actor))
689 return 1; 626 return 1;
690 627
691 xs_dict *msg = xs_dict_get(c_msg, "object"); 628 const xs_dict *msg = xs_dict_get(c_msg, "object");
692 xs *rcpts = recipient_list(snac, msg, 0); 629 xs *rcpts = recipient_list(snac, msg, 0);
693 xs_list *p = rcpts; 630 xs_list *p = rcpts;
694 xs_str *v; 631 const xs_str *v;
695 632
696 xs *actor_followers = NULL; 633 xs *actor_followers = NULL;
697 634
@@ -700,8 +637,9 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg)
700 xs *actor_obj = NULL; 637 xs *actor_obj = NULL;
701 638
702 if (valid_status(object_get(actor, &actor_obj))) { 639 if (valid_status(object_get(actor, &actor_obj))) {
703 if ((v = xs_dict_get(actor_obj, "followers"))) 640 const xs_val *fw = xs_dict_get(actor_obj, "followers");
704 actor_followers = xs_dup(v); 641 if (fw)
642 actor_followers = xs_dup(fw);
705 } 643 }
706 } 644 }
707 645
@@ -724,13 +662,13 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg)
724 } 662 }
725 663
726 /* accept if it's by someone we follow */ 664 /* accept if it's by someone we follow */
727 char *atto = get_atto(msg); 665 const char *atto = get_atto(msg);
728 666
729 if (pub_msg && !xs_is_null(atto) && following_check(snac, atto)) 667 if (pub_msg && !xs_is_null(atto) && following_check(snac, atto))
730 return 3; 668 return 3;
731 669
732 /* is this message a reply to another? */ 670 /* is this message a reply to another? */
733 char *irt = xs_dict_get(msg, "inReplyTo"); 671 const char *irt = xs_dict_get(msg, "inReplyTo");
734 if (!xs_is_null(irt)) { 672 if (!xs_is_null(irt)) {
735 xs *r_msg = NULL; 673 xs *r_msg = NULL;
736 674
@@ -755,7 +693,7 @@ xs_str *process_tags(snac *snac, const char *content, xs_list **tag)
755 xs_list *tl = *tag; 693 xs_list *tl = *tag;
756 xs *split; 694 xs *split;
757 xs_list *p; 695 xs_list *p;
758 xs_val *v; 696 const xs_val *v;
759 int n = 0; 697 int n = 0;
760 698
761 /* create a default server for incomplete mentions */ 699 /* create a default server for incomplete mentions */
@@ -983,8 +921,8 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor,
983 921
984 /* telegram */ 922 /* telegram */
985 923
986 char *bot = xs_dict_get(snac->config, "telegram_bot"); 924 const char *bot = xs_dict_get(snac->config, "telegram_bot");
987 char *chat_id = xs_dict_get(snac->config, "telegram_chat_id"); 925 const char *chat_id = xs_dict_get(snac->config, "telegram_chat_id");
988 926
989 if (!xs_is_null(bot) && !xs_is_null(chat_id) && *bot && *chat_id) 927 if (!xs_is_null(bot) && !xs_is_null(chat_id) && *bot && *chat_id)
990 enqueue_telegram(body, bot, chat_id); 928 enqueue_telegram(body, bot, chat_id);
@@ -997,8 +935,8 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor,
997 objid = actor; 935 objid = actor;
998 936
999 /* ntfy */ 937 /* ntfy */
1000 char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); 938 const char *ntfy_server = xs_dict_get(snac->config, "ntfy_server");
1001 char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); 939 const char *ntfy_token = xs_dict_get(snac->config, "ntfy_token");
1002 940
1003 if (!xs_is_null(ntfy_server) && *ntfy_server) 941 if (!xs_is_null(ntfy_server) && *ntfy_server)
1004 enqueue_ntfy(body, ntfy_server, ntfy_token); 942 enqueue_ntfy(body, ntfy_server, ntfy_token);
@@ -1084,7 +1022,7 @@ xs_dict *msg_base(snac *snac, const char *type, const char *id,
1084} 1022}
1085 1023
1086 1024
1087xs_dict *msg_collection(snac *snac, char *id) 1025xs_dict *msg_collection(snac *snac, const char *id)
1088/* creates an empty OrderedCollection message */ 1026/* creates an empty OrderedCollection message */
1089{ 1027{
1090 xs_dict *msg = msg_base(snac, "OrderedCollection", id, NULL, NULL, NULL); 1028 xs_dict *msg = msg_base(snac, "OrderedCollection", id, NULL, NULL, NULL);
@@ -1098,7 +1036,7 @@ xs_dict *msg_collection(snac *snac, char *id)
1098} 1036}
1099 1037
1100 1038
1101xs_dict *msg_accept(snac *snac, char *object, char *to) 1039xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to)
1102/* creates an Accept message (as a response to a Follow) */ 1040/* creates an Accept message (as a response to a Follow) */
1103{ 1041{
1104 xs_dict *msg = msg_base(snac, "Accept", "@dummy", snac->actor, NULL, object); 1042 xs_dict *msg = msg_base(snac, "Accept", "@dummy", snac->actor, NULL, object);
@@ -1109,12 +1047,12 @@ xs_dict *msg_accept(snac *snac, char *object, char *to)
1109} 1047}
1110 1048
1111 1049
1112xs_dict *msg_update(snac *snac, xs_dict *object) 1050xs_dict *msg_update(snac *snac, const xs_dict *object)
1113/* creates an Update message */ 1051/* creates an Update message */
1114{ 1052{
1115 xs_dict *msg = msg_base(snac, "Update", "@object", snac->actor, "@now", object); 1053 xs_dict *msg = msg_base(snac, "Update", "@object", snac->actor, "@now", object);
1116 1054
1117 char *type = xs_dict_get(object, "type"); 1055 const char *type = xs_dict_get(object, "type");
1118 1056
1119 if (strcmp(type, "Note") == 0) { 1057 if (strcmp(type, "Note") == 0) {
1120 msg = xs_dict_append(msg, "to", xs_dict_get(object, "to")); 1058 msg = xs_dict_append(msg, "to", xs_dict_get(object, "to"));
@@ -1137,7 +1075,7 @@ xs_dict *msg_update(snac *snac, xs_dict *object)
1137} 1075}
1138 1076
1139 1077
1140xs_dict *msg_admiration(snac *snac, char *object, char *type) 1078xs_dict *msg_admiration(snac *snac, const char *object, const char *type)
1141/* creates a Like or Announce message */ 1079/* creates a Like or Announce message */
1142{ 1080{
1143 xs *a_msg = NULL; 1081 xs *a_msg = NULL;
@@ -1168,7 +1106,7 @@ xs_dict *msg_admiration(snac *snac, char *object, char *type)
1168} 1106}
1169 1107
1170 1108
1171xs_dict *msg_repulsion(snac *user, char *id, char *type) 1109xs_dict *msg_repulsion(snac *user, const char *id, const char *type)
1172/* creates an Undo + admiration message */ 1110/* creates an Undo + admiration message */
1173{ 1111{
1174 xs *a_msg = NULL; 1112 xs *a_msg = NULL;
@@ -1206,7 +1144,7 @@ xs_dict *msg_actor(snac *snac)
1206 xs *kid = NULL; 1144 xs *kid = NULL;
1207 xs *f_bio = NULL; 1145 xs *f_bio = NULL;
1208 xs_dict *msg = msg_base(snac, "Person", snac->actor, NULL, NULL, NULL); 1146 xs_dict *msg = msg_base(snac, "Person", snac->actor, NULL, NULL, NULL);
1209 char *p; 1147 const char *p;
1210 int n; 1148 int n;
1211 1149
1212 /* change the @context (is this really necessary?) */ 1150 /* change the @context (is this really necessary?) */
@@ -1264,11 +1202,11 @@ xs_dict *msg_actor(snac *snac)
1264 } 1202 }
1265 1203
1266 /* add the metadata as attachments of PropertyValue */ 1204 /* add the metadata as attachments of PropertyValue */
1267 xs_dict *metadata = xs_dict_get(snac->config, "metadata"); 1205 const xs_dict *metadata = xs_dict_get(snac->config, "metadata");
1268 if (xs_type(metadata) == XSTYPE_DICT) { 1206 if (xs_type(metadata) == XSTYPE_DICT) {
1269 xs *attach = xs_list_new(); 1207 xs *attach = xs_list_new();
1270 xs_str *k; 1208 const xs_str *k;
1271 xs_str *v; 1209 const xs_str *v;
1272 1210
1273 int c = 0; 1211 int c = 0;
1274 while (xs_dict_next(metadata, &k, &v, &c)) { 1212 while (xs_dict_next(metadata, &k, &v, &c)) {
@@ -1277,7 +1215,7 @@ xs_dict *msg_actor(snac *snac)
1277 xs *k2 = encode_html(k); 1215 xs *k2 = encode_html(k);
1278 xs *v2 = NULL; 1216 xs *v2 = NULL;
1279 1217
1280 if (xs_startswith(v, "https:")) { 1218 if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/")) {
1281 xs *t = encode_html(v); 1219 xs *t = encode_html(v);
1282 v2 = xs_fmt("<a href=\"%s\" rel=\"me\">%s</a>", t, t); 1220 v2 = xs_fmt("<a href=\"%s\" rel=\"me\">%s</a>", t, t);
1283 } 1221 }
@@ -1310,7 +1248,7 @@ xs_dict *msg_create(snac *snac, const xs_dict *object)
1310/* creates a 'Create' message */ 1248/* creates a 'Create' message */
1311{ 1249{
1312 xs_dict *msg = msg_base(snac, "Create", "@wrapper", snac->actor, NULL, object); 1250 xs_dict *msg = msg_base(snac, "Create", "@wrapper", snac->actor, NULL, object);
1313 xs_val *v; 1251 const xs_val *v;
1314 1252
1315 if ((v = get_atto(object))) 1253 if ((v = get_atto(object)))
1316 msg = xs_dict_append(msg, "attributedTo", v); 1254 msg = xs_dict_append(msg, "attributedTo", v);
@@ -1327,7 +1265,7 @@ xs_dict *msg_create(snac *snac, const xs_dict *object)
1327} 1265}
1328 1266
1329 1267
1330xs_dict *msg_undo(snac *snac, char *object) 1268xs_dict *msg_undo(snac *snac, const xs_val *object)
1331/* creates an 'Undo' message */ 1269/* creates an 'Undo' message */
1332{ 1270{
1333 xs_dict *msg = msg_base(snac, "Undo", "@object", snac->actor, "@now", object); 1271 xs_dict *msg = msg_base(snac, "Undo", "@object", snac->actor, "@now", object);
@@ -1340,7 +1278,7 @@ xs_dict *msg_undo(snac *snac, char *object)
1340} 1278}
1341 1279
1342 1280
1343xs_dict *msg_delete(snac *snac, char *id) 1281xs_dict *msg_delete(snac *snac, const char *id)
1344/* creates a 'Delete' + 'Tombstone' for a local entry */ 1282/* creates a 'Delete' + 'Tombstone' for a local entry */
1345{ 1283{
1346 xs *tomb = xs_dict_new(); 1284 xs *tomb = xs_dict_new();
@@ -1369,7 +1307,7 @@ xs_dict *msg_follow(snac *snac, const char *q)
1369 1307
1370 xs *url_or_uid = xs_strip_i(xs_str_new(q)); 1308 xs *url_or_uid = xs_strip_i(xs_str_new(q));
1371 1309
1372 if (xs_startswith(url_or_uid, "https:/")) 1310 if (xs_startswith(url_or_uid, "https:/") || xs_startswith(url_or_uid, "http:/"))
1373 actor = xs_dup(url_or_uid); 1311 actor = xs_dup(url_or_uid);
1374 else 1312 else
1375 if (!valid_status(webfinger_request(url_or_uid, &actor, NULL)) || actor == NULL) { 1313 if (!valid_status(webfinger_request(url_or_uid, &actor, NULL)) || actor == NULL) {
@@ -1382,7 +1320,7 @@ xs_dict *msg_follow(snac *snac, const char *q)
1382 1320
1383 if (valid_status(status)) { 1321 if (valid_status(status)) {
1384 /* check if the actor is an alias */ 1322 /* check if the actor is an alias */
1385 char *r_actor = xs_dict_get(actor_o, "id"); 1323 const char *r_actor = xs_dict_get(actor_o, "id");
1386 1324
1387 if (r_actor && strcmp(actor, r_actor) != 0) { 1325 if (r_actor && strcmp(actor, r_actor) != 0) {
1388 snac_log(snac, xs_fmt("actor to follow is an alias %s -> %s", actor, r_actor)); 1326 snac_log(snac, xs_fmt("actor to follow is an alias %s -> %s", actor, r_actor));
@@ -1398,7 +1336,7 @@ xs_dict *msg_follow(snac *snac, const char *q)
1398 1336
1399 1337
1400xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, 1338xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1401 xs_str *in_reply_to, xs_list *attach, int priv) 1339 const xs_str *in_reply_to, const xs_list *attach, int priv)
1402/* creates a 'Note' message */ 1340/* creates a 'Note' message */
1403{ 1341{
1404 xs *ntid = tid(0); 1342 xs *ntid = tid(0);
@@ -1413,7 +1351,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1413 xs *atls = xs_list_new(); 1351 xs *atls = xs_list_new();
1414 xs_dict *msg = msg_base(snac, "Note", id, NULL, "@now", NULL); 1352 xs_dict *msg = msg_base(snac, "Note", id, NULL, "@now", NULL);
1415 xs_list *p; 1353 xs_list *p;
1416 xs_val *v; 1354 const xs_val *v;
1417 1355
1418 if (rcpts == NULL) 1356 if (rcpts == NULL)
1419 to = xs_list_new(); 1357 to = xs_list_new();
@@ -1438,7 +1376,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1438 1376
1439 if (valid_status(object_get(in_reply_to, &p_msg))) { 1377 if (valid_status(object_get(in_reply_to, &p_msg))) {
1440 /* add this author as recipient */ 1378 /* add this author as recipient */
1441 char *a, *v; 1379 const char *a, *v;
1442 1380
1443 if ((a = get_atto(p_msg)) && xs_list_in(to, a) == -1) 1381 if ((a = get_atto(p_msg)) && xs_list_in(to, a) == -1)
1444 to = xs_list_append(to, a); 1382 to = xs_list_append(to, a);
@@ -1449,7 +1387,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1449 xs *actor_o = NULL; 1387 xs *actor_o = NULL;
1450 1388
1451 if (xs_list_len(l) > 3 && valid_status(object_get(a, &actor_o))) { 1389 if (xs_list_len(l) > 3 && valid_status(object_get(a, &actor_o))) {
1452 char *uname = xs_dict_get(actor_o, "preferredUsername"); 1390 const char *uname = xs_dict_get(actor_o, "preferredUsername");
1453 1391
1454 if (!xs_is_null(uname) && *uname) { 1392 if (!xs_is_null(uname) && *uname) {
1455 xs *handle = xs_fmt("@%s@%s", uname, xs_list_get(l, 2)); 1393 xs *handle = xs_fmt("@%s@%s", uname, xs_list_get(l, 2));
@@ -1488,7 +1426,8 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1488 1426
1489 /* create the attachment list, if there are any */ 1427 /* create the attachment list, if there are any */
1490 if (!xs_is_null(attach)) { 1428 if (!xs_is_null(attach)) {
1491 while (xs_list_iter(&attach, &v)) { 1429 int c = 0;
1430 while (xs_list_next(attach, &v, &c)) {
1492 xs *d = xs_dict_new(); 1431 xs *d = xs_dict_new();
1493 const char *url = xs_list_get(v, 0); 1432 const char *url = xs_list_get(v, 0);
1494 const char *alt = xs_list_get(v, 1); 1433 const char *alt = xs_list_get(v, 1);
@@ -1511,7 +1450,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1511 p = tag; 1450 p = tag;
1512 while (xs_list_iter(&p, &v)) { 1451 while (xs_list_iter(&p, &v)) {
1513 if (xs_type(v) == XSTYPE_DICT) { 1452 if (xs_type(v) == XSTYPE_DICT) {
1514 char *t; 1453 const char *t;
1515 1454
1516 if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { 1455 if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) {
1517 if (!xs_is_null(t = xs_dict_get(v, "href"))) 1456 if (!xs_is_null(t = xs_dict_get(v, "href")))
@@ -1589,7 +1528,7 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
1589 1528
1590 xs *o = xs_list_new(); 1529 xs *o = xs_list_new();
1591 xs_list *p = (xs_list *)opts; 1530 xs_list *p = (xs_list *)opts;
1592 xs_str *v; 1531 const xs_str *v;
1593 xs *replies = xs_json_loads("{\"type\":\"Collection\",\"totalItems\":0}"); 1532 xs *replies = xs_json_loads("{\"type\":\"Collection\",\"totalItems\":0}");
1594 1533
1595 xs_set_init(&seen); 1534 xs_set_init(&seen);
@@ -1635,9 +1574,9 @@ int update_question(snac *user, const char *id)
1635 xs *msg = NULL; 1574 xs *msg = NULL;
1636 xs *rcnt = xs_dict_new(); 1575 xs *rcnt = xs_dict_new();
1637 xs *lopts = xs_list_new(); 1576 xs *lopts = xs_list_new();
1638 xs_list *opts; 1577 const xs_list *opts;
1639 xs_list *p; 1578 xs_list *p;
1640 xs_val *v; 1579 const xs_val *v;
1641 1580
1642 /* get the object */ 1581 /* get the object */
1643 if (!valid_status(object_get(id, &msg))) 1582 if (!valid_status(object_get(id, &msg)))
@@ -1653,8 +1592,8 @@ int update_question(snac *user, const char *id)
1653 return -3; 1592 return -3;
1654 1593
1655 /* fill the initial count */ 1594 /* fill the initial count */
1656 p = opts; 1595 int c = 0;
1657 while (xs_list_iter(&p, &v)) { 1596 while (xs_list_next(opts, &v, &c)) {
1658 const char *name = xs_dict_get(v, "name"); 1597 const char *name = xs_dict_get(v, "name");
1659 if (name) { 1598 if (name) {
1660 lopts = xs_list_append(lopts, name); 1599 lopts = xs_list_append(lopts, name);
@@ -1760,13 +1699,13 @@ int update_question(snac *user, const char *id)
1760 1699
1761/** queues **/ 1700/** queues **/
1762 1701
1763int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) 1702int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
1764/* processes an ActivityPub message from the input queue */ 1703/* processes an ActivityPub message from the input queue */
1765/* return values: -1, fatal error; 0, transient error, retry; 1704/* return values: -1, fatal error; 0, transient error, retry;
1766 1, processed and done; 2, propagate to users (only when no user is set) */ 1705 1, processed and done; 2, propagate to users (only when no user is set) */
1767{ 1706{
1768 char *actor = xs_dict_get(msg, "actor"); 1707 const char *actor = xs_dict_get(msg, "actor");
1769 char *type = xs_dict_get(msg, "type"); 1708 const char *type = xs_dict_get(msg, "type");
1770 xs *actor_o = NULL; 1709 xs *actor_o = NULL;
1771 int a_status; 1710 int a_status;
1772 int do_notify = 0; 1711 int do_notify = 0;
@@ -1786,7 +1725,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1786 return -1; 1725 return -1;
1787 } 1726 }
1788 1727
1789 char *object, *utype; 1728 const char *object, *utype;
1790 1729
1791 object = xs_dict_get(msg, "object"); 1730 object = xs_dict_get(msg, "object");
1792 if (object != NULL && xs_type(object) == XSTYPE_DICT) 1731 if (object != NULL && xs_type(object) == XSTYPE_DICT)
@@ -1809,7 +1748,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1809 } 1748 }
1810 1749
1811 /* also discard if the object to be deleted is not here */ 1750 /* also discard if the object to be deleted is not here */
1812 char *obj_id = object; 1751 const char *obj_id = object;
1813 if (xs_type(obj_id) == XSTYPE_DICT) 1752 if (xs_type(obj_id) == XSTYPE_DICT)
1814 obj_id = xs_dict_get(obj_id, "id"); 1753 obj_id = xs_dict_get(obj_id, "id");
1815 1754
@@ -1881,7 +1820,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1881 int min_account_age = xs_number_get(xs_dict_get(srv_config, "min_account_age")); 1820 int min_account_age = xs_number_get(xs_dict_get(srv_config, "min_account_age"));
1882 1821
1883 if (min_account_age > 0) { 1822 if (min_account_age > 0) {
1884 char *actor_date = xs_dict_get(actor_o, "published"); 1823 const char *actor_date = xs_dict_get(actor_o, "published");
1885 if (!xs_is_null(actor_date)) { 1824 if (!xs_is_null(actor_date)) {
1886 time_t actor_t = xs_parse_iso_date(actor_date, 0); 1825 time_t actor_t = xs_parse_iso_date(actor_date, 0);
1887 1826
@@ -1941,16 +1880,39 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1941 } 1880 }
1942 else 1881 else
1943 if (strcmp(type, "Undo") == 0) { /** **/ 1882 if (strcmp(type, "Undo") == 0) { /** **/
1883 const char *id = xs_dict_get(object, "object");
1884
1944 if (xs_type(object) != XSTYPE_DICT) 1885 if (xs_type(object) != XSTYPE_DICT)
1945 utype = "Follow"; 1886 utype = "Follow";
1946 1887
1947 if (strcmp(utype, "Follow") == 0) { /** **/ 1888 if (strcmp(utype, "Follow") == 0) { /** **/
1948 if (valid_status(follower_del(snac, actor))) { 1889 if (id && strcmp(id, snac->actor) != 0)
1949 snac_log(snac, xs_fmt("no longer following us %s", actor)); 1890 snac_debug(snac, 1, xs_fmt("Undo + Follow from %s not for us (%s)", actor, id));
1950 do_notify = 1; 1891 else {
1892 if (valid_status(follower_del(snac, actor))) {
1893 snac_log(snac, xs_fmt("no longer following us %s", actor));
1894 do_notify = 1;
1895 }
1896 else
1897 snac_log(snac, xs_fmt("error deleting follower %s", actor));
1951 } 1898 }
1952 else 1899 }
1953 snac_log(snac, xs_fmt("error deleting follower %s", actor)); 1900 else
1901 if (strcmp(utype, "Like") == 0) { /** **/
1902 int status = object_unadmire(id, actor, 1);
1903
1904 snac_log(snac, xs_fmt("Unlike for %s %d", id, status));
1905 }
1906 else
1907 if (strcmp(utype, "Announce") == 0) { /** **/
1908 int status = 200;
1909
1910 /* commented out: if a followed user boosts something that
1911 is requested and then unboosts, the post remains here,
1912 but with no apparent reason, and that is confusing */
1913 //status = object_unadmire(id, actor, 0);
1914
1915 snac_log(snac, xs_fmt("Unboost for %s %d", id, status));
1954 } 1916 }
1955 else 1917 else
1956 snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype)); 1918 snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype));
@@ -1963,19 +1925,29 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1963 } 1925 }
1964 1926
1965 if (xs_match(utype, "Note|Article")) { /** **/ 1927 if (xs_match(utype, "Note|Article")) { /** **/
1966 char *id = xs_dict_get(object, "id"); 1928 const char *id = xs_dict_get(object, "id");
1967 char *in_reply_to = xs_dict_get(object, "inReplyTo"); 1929 const char *in_reply_to = xs_dict_get(object, "inReplyTo");
1930 const char *atto = get_atto(object);
1968 xs *wrk = NULL; 1931 xs *wrk = NULL;
1969 1932
1933 if (xs_is_null(id))
1934 snac_log(snac, xs_fmt("malformed message: no 'id' field"));
1935 else
1936 if (xs_is_null(atto))
1937 snac_log(snac, xs_fmt("malformed message: no 'attributedTo' field"));
1938 else
1970 if (!xs_is_null(in_reply_to) && is_hidden(snac, in_reply_to)) { 1939 if (!xs_is_null(in_reply_to) && is_hidden(snac, in_reply_to)) {
1971 snac_debug(snac, 0, xs_fmt("dropped reply %s to hidden post %s", id, in_reply_to)); 1940 snac_debug(snac, 0, xs_fmt("dropped reply %s to hidden post %s", id, in_reply_to));
1972 } 1941 }
1973 else { 1942 else {
1974 if (content_check("filter_reject.txt", object)) { 1943 if (content_match("filter_reject.txt", object)) {
1975 snac_log(snac, xs_fmt("rejected by content %s", id)); 1944 snac_log(snac, xs_fmt("rejected by content %s", id));
1976 return 1; 1945 return 1;
1977 } 1946 }
1978 1947
1948 if (strcmp(actor, atto) != 0)
1949 snac_log(snac, xs_fmt("SUSPICIOUS: actor != atto (%s != %s)", actor, atto));
1950
1979 timeline_request(snac, &in_reply_to, &wrk, 0); 1951 timeline_request(snac, &in_reply_to, &wrk, 0);
1980 1952
1981 if (timeline_add(snac, id, object)) { 1953 if (timeline_add(snac, id, object)) {
@@ -1992,14 +1964,14 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1992 } 1964 }
1993 else 1965 else
1994 if (strcmp(utype, "Question") == 0) { /** **/ 1966 if (strcmp(utype, "Question") == 0) { /** **/
1995 char *id = xs_dict_get(object, "id"); 1967 const char *id = xs_dict_get(object, "id");
1996 1968
1997 if (timeline_add(snac, id, object)) 1969 if (timeline_add(snac, id, object))
1998 snac_log(snac, xs_fmt("new 'Question' %s %s", actor, id)); 1970 snac_log(snac, xs_fmt("new 'Question' %s %s", actor, id));
1999 } 1971 }
2000 else 1972 else
2001 if (strcmp(utype, "Video") == 0) { /** **/ 1973 if (strcmp(utype, "Video") == 0) { /** **/
2002 char *id = xs_dict_get(object, "id"); 1974 const char *id = xs_dict_get(object, "id");
2003 1975
2004 if (timeline_add(snac, id, object)) 1976 if (timeline_add(snac, id, object))
2005 snac_log(snac, xs_fmt("new 'Video' %s %s", actor, id)); 1977 snac_log(snac, xs_fmt("new 'Video' %s %s", actor, id));
@@ -2080,6 +2052,9 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
2080 snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", 2052 snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s",
2081 actor, object)); 2053 actor, object));
2082 2054
2055 /* distribute the post with the actor as 'proxy' */
2056 list_distribute(snac, actor, a_msg);
2057
2083 do_notify = 1; 2058 do_notify = 1;
2084 } 2059 }
2085 else 2060 else
@@ -2172,7 +2147,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
2172} 2147}
2173 2148
2174 2149
2175int send_email(char *msg) 2150int send_email(const char *msg)
2176/* invoke sendmail with email headers and body in msg */ 2151/* invoke sendmail with email headers and body in msg */
2177{ 2152{
2178 FILE *f; 2153 FILE *f;
@@ -2204,18 +2179,18 @@ int send_email(char *msg)
2204void process_user_queue_item(snac *snac, xs_dict *q_item) 2179void process_user_queue_item(snac *snac, xs_dict *q_item)
2205/* processes an item from the user queue */ 2180/* processes an item from the user queue */
2206{ 2181{
2207 char *type; 2182 const char *type;
2208 int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); 2183 int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max"));
2209 2184
2210 if ((type = xs_dict_get(q_item, "type")) == NULL) 2185 if ((type = xs_dict_get(q_item, "type")) == NULL)
2211 type = "output"; 2186 type = "output";
2212 2187
2213 if (strcmp(type, "message") == 0) { 2188 if (strcmp(type, "message") == 0) {
2214 xs_dict *msg = xs_dict_get(q_item, "message"); 2189 const xs_dict *msg = xs_dict_get(q_item, "message");
2215 xs *rcpts = recipient_list(snac, msg, 1); 2190 xs *rcpts = recipient_list(snac, msg, 1);
2216 xs_set inboxes; 2191 xs_set inboxes;
2217 xs_list *p; 2192 xs_list *p;
2218 xs_str *actor; 2193 const xs_str *actor;
2219 2194
2220 xs_set_init(&inboxes); 2195 xs_set_init(&inboxes);
2221 2196
@@ -2237,7 +2212,7 @@ void process_user_queue_item(snac *snac, xs_dict *q_item)
2237 if (is_msg_public(msg)) { 2212 if (is_msg_public(msg)) {
2238 if (xs_type(xs_dict_get(srv_config, "disable_inbox_collection")) != XSTYPE_TRUE) { 2213 if (xs_type(xs_dict_get(srv_config, "disable_inbox_collection")) != XSTYPE_TRUE) {
2239 xs *shibx = inbox_list(); 2214 xs *shibx = inbox_list();
2240 xs_str *inbox; 2215 const xs_str *inbox;
2241 2216
2242 p = shibx; 2217 p = shibx;
2243 while (xs_list_iter(&p, &inbox)) { 2218 while (xs_list_iter(&p, &inbox)) {
@@ -2252,8 +2227,8 @@ void process_user_queue_item(snac *snac, xs_dict *q_item)
2252 else 2227 else
2253 if (strcmp(type, "input") == 0) { 2228 if (strcmp(type, "input") == 0) {
2254 /* process the message */ 2229 /* process the message */
2255 xs_dict *msg = xs_dict_get(q_item, "message"); 2230 const xs_dict *msg = xs_dict_get(q_item, "message");
2256 xs_dict *req = xs_dict_get(q_item, "req"); 2231 const xs_dict *req = xs_dict_get(q_item, "req");
2257 int retries = xs_number_get(xs_dict_get(q_item, "retries")); 2232 int retries = xs_number_get(xs_dict_get(q_item, "retries"));
2258 2233
2259 if (xs_is_null(msg)) 2234 if (xs_is_null(msg))
@@ -2280,11 +2255,20 @@ void process_user_queue_item(snac *snac, xs_dict *q_item)
2280 update_question(snac, id); 2255 update_question(snac, id);
2281 } 2256 }
2282 else 2257 else
2283 if (strcmp(type, "request_replies") == 0) { 2258 if (strcmp(type, "object_request") == 0) {
2284 const char *id = xs_dict_get(q_item, "message"); 2259 const char *id = xs_dict_get(q_item, "message");
2285 2260
2286 if (!xs_is_null(id)) 2261 if (!xs_is_null(id)) {
2287 timeline_request_replies(snac, id); 2262 int status;
2263 xs *data = NULL;
2264
2265 status = activitypub_request(snac, id, &data);
2266
2267 if (valid_status(status))
2268 object_add_ow(id, data);
2269
2270 snac_debug(snac, 1, xs_fmt("object_request %s %d", id, status));
2271 }
2288 } 2272 }
2289 else 2273 else
2290 if (strcmp(type, "verify_links") == 0) { 2274 if (strcmp(type, "verify_links") == 0) {
@@ -2320,7 +2304,7 @@ int process_user_queue(snac *snac)
2320 xs *list = user_queue(snac); 2304 xs *list = user_queue(snac);
2321 2305
2322 xs_list *p = list; 2306 xs_list *p = list;
2323 xs_str *fn; 2307 const xs_str *fn;
2324 2308
2325 while (xs_list_iter(&p, &fn)) { 2309 while (xs_list_iter(&p, &fn)) {
2326 xs *q_item = dequeue(fn); 2310 xs *q_item = dequeue(fn);
@@ -2339,19 +2323,20 @@ int process_user_queue(snac *snac)
2339void process_queue_item(xs_dict *q_item) 2323void process_queue_item(xs_dict *q_item)
2340/* processes an item from the global queue */ 2324/* processes an item from the global queue */
2341{ 2325{
2342 char *type = xs_dict_get(q_item, "type"); 2326 const char *type = xs_dict_get(q_item, "type");
2343 int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); 2327 int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max"));
2344 2328
2345 if (strcmp(type, "output") == 0) { 2329 if (strcmp(type, "output") == 0) {
2346 int status; 2330 int status;
2347 xs_str *inbox = xs_dict_get(q_item, "inbox"); 2331 const xs_str *inbox = xs_dict_get(q_item, "inbox");
2348 xs_str *keyid = xs_dict_get(q_item, "keyid"); 2332 const xs_str *keyid = xs_dict_get(q_item, "keyid");
2349 xs_str *seckey = xs_dict_get(q_item, "seckey"); 2333 const xs_str *seckey = xs_dict_get(q_item, "seckey");
2350 xs_dict *msg = xs_dict_get(q_item, "message"); 2334 const xs_dict *msg = xs_dict_get(q_item, "message");
2351 int retries = xs_number_get(xs_dict_get(q_item, "retries")); 2335 int retries = xs_number_get(xs_dict_get(q_item, "retries"));
2352 int p_status = xs_number_get(xs_dict_get(q_item, "p_status")); 2336 int p_status = xs_number_get(xs_dict_get(q_item, "p_status"));
2353 xs *payload = NULL; 2337 xs *payload = NULL;
2354 int p_size = 0; 2338 int p_size = 0;
2339 int timeout = 0;
2355 2340
2356 if (xs_is_null(inbox) || xs_is_null(msg) || xs_is_null(keyid) || xs_is_null(seckey)) { 2341 if (xs_is_null(inbox) || xs_is_null(msg) || xs_is_null(keyid) || xs_is_null(seckey)) {
2357 srv_log(xs_fmt("output message error: missing fields")); 2342 srv_log(xs_fmt("output message error: missing fields"));
@@ -2364,8 +2349,15 @@ void process_queue_item(xs_dict *q_item)
2364 } 2349 }
2365 2350
2366 /* deliver (if previous error status was a timeout, try now longer) */ 2351 /* deliver (if previous error status was a timeout, try now longer) */
2367 status = send_to_inbox_raw(keyid, seckey, inbox, msg, 2352 if (p_status == 599)
2368 &payload, &p_size, p_status == 599 ? 8 : 6); 2353 timeout = xs_number_get(xs_dict_get_def(srv_config, "queue_timeout_2", "8"));
2354 else
2355 timeout = xs_number_get(xs_dict_get_def(srv_config, "queue_timeout", "6"));
2356
2357 if (timeout == 0)
2358 timeout = 6;
2359
2360 status = send_to_inbox_raw(keyid, seckey, inbox, msg, &payload, &p_size, timeout);
2369 2361
2370 if (payload) { 2362 if (payload) {
2371 if (p_size > 64) { 2363 if (p_size > 64) {
@@ -2411,7 +2403,7 @@ void process_queue_item(xs_dict *q_item)
2411 else 2403 else
2412 if (strcmp(type, "email") == 0) { 2404 if (strcmp(type, "email") == 0) {
2413 /* send this email */ 2405 /* send this email */
2414 xs_str *msg = xs_dict_get(q_item, "message"); 2406 const xs_str *msg = xs_dict_get(q_item, "message");
2415 int retries = xs_number_get(xs_dict_get(q_item, "retries")); 2407 int retries = xs_number_get(xs_dict_get(q_item, "retries"));
2416 2408
2417 if (!send_email(msg)) 2409 if (!send_email(msg))
@@ -2433,8 +2425,8 @@ void process_queue_item(xs_dict *q_item)
2433 else 2425 else
2434 if (strcmp(type, "telegram") == 0) { 2426 if (strcmp(type, "telegram") == 0) {
2435 /* send this via telegram */ 2427 /* send this via telegram */
2436 char *bot = xs_dict_get(q_item, "bot"); 2428 const char *bot = xs_dict_get(q_item, "bot");
2437 char *msg = xs_dict_get(q_item, "message"); 2429 const char *msg = xs_dict_get(q_item, "message");
2438 xs *chat_id = xs_dup(xs_dict_get(q_item, "chat_id")); 2430 xs *chat_id = xs_dup(xs_dict_get(q_item, "chat_id"));
2439 int status = 0; 2431 int status = 0;
2440 2432
@@ -2457,9 +2449,9 @@ void process_queue_item(xs_dict *q_item)
2457 else 2449 else
2458 if (strcmp(type, "ntfy") == 0) { 2450 if (strcmp(type, "ntfy") == 0) {
2459 /* send this via ntfy */ 2451 /* send this via ntfy */
2460 char *ntfy_server = xs_dict_get(q_item, "ntfy_server"); 2452 const char *ntfy_server = xs_dict_get(q_item, "ntfy_server");
2461 char *msg = xs_dict_get(q_item, "message"); 2453 const char *msg = xs_dict_get(q_item, "message");
2462 char *ntfy_token = xs_dict_get(q_item, "ntfy_token"); 2454 const char *ntfy_token = xs_dict_get(q_item, "ntfy_token");
2463 int status = 0; 2455 int status = 0;
2464 2456
2465 xs *url = xs_fmt("%s", ntfy_server); 2457 xs *url = xs_fmt("%s", ntfy_server);
@@ -2488,8 +2480,8 @@ void process_queue_item(xs_dict *q_item)
2488 } 2480 }
2489 else 2481 else
2490 if (strcmp(type, "input") == 0) { 2482 if (strcmp(type, "input") == 0) {
2491 xs_dict *msg = xs_dict_get(q_item, "message"); 2483 const xs_dict *msg = xs_dict_get(q_item, "message");
2492 xs_dict *req = xs_dict_get(q_item, "req"); 2484 const xs_dict *req = xs_dict_get(q_item, "req");
2493 int retries = xs_number_get(xs_dict_get(q_item, "retries")); 2485 int retries = xs_number_get(xs_dict_get(q_item, "retries"));
2494 2486
2495 /* do some instance-level checks */ 2487 /* do some instance-level checks */
@@ -2497,8 +2489,6 @@ void process_queue_item(xs_dict *q_item)
2497 2489
2498 if (r == 0) { 2490 if (r == 0) {
2499 /* transient error? retry */ 2491 /* transient error? retry */
2500 int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max"));
2501
2502 if (retries > queue_retry_max) 2492 if (retries > queue_retry_max)
2503 srv_log(xs_fmt("shared input giving up")); 2493 srv_log(xs_fmt("shared input giving up"));
2504 else { 2494 else {
@@ -2510,7 +2500,7 @@ void process_queue_item(xs_dict *q_item)
2510 else 2500 else
2511 if (r == 2) { 2501 if (r == 2) {
2512 /* redistribute the input message to all users */ 2502 /* redistribute the input message to all users */
2513 char *ntid = xs_dict_get(q_item, "ntid"); 2503 const char *ntid = xs_dict_get(q_item, "ntid");
2514 xs *tmpfn = xs_fmt("%s/tmp/%s.json", srv_basedir, ntid); 2504 xs *tmpfn = xs_fmt("%s/tmp/%s.json", srv_basedir, ntid);
2515 FILE *f; 2505 FILE *f;
2516 2506
@@ -2521,7 +2511,7 @@ void process_queue_item(xs_dict *q_item)
2521 2511
2522 xs *users = user_list(); 2512 xs *users = user_list();
2523 xs_list *p = users; 2513 xs_list *p = users;
2524 char *v; 2514 const char *v;
2525 int cnt = 0; 2515 int cnt = 0;
2526 2516
2527 while (xs_list_iter(&p, &v)) { 2517 while (xs_list_iter(&p, &v)) {
@@ -2564,7 +2554,7 @@ int process_queue(void)
2564 xs *list = queue(); 2554 xs *list = queue();
2565 2555
2566 xs_list *p = list; 2556 xs_list *p = list;
2567 xs_str *fn; 2557 const xs_str *fn;
2568 2558
2569 while (xs_list_iter(&p, &fn)) { 2559 while (xs_list_iter(&p, &fn)) {
2570 xs *q_item = dequeue(fn); 2560 xs *q_item = dequeue(fn);
@@ -2585,7 +2575,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path,
2585 char **body, int *b_size, char **ctype) 2575 char **body, int *b_size, char **ctype)
2586{ 2576{
2587 int status = 200; 2577 int status = 200;
2588 char *accept = xs_dict_get(req, "accept"); 2578 const char *accept = xs_dict_get(req, "accept");
2589 snac snac; 2579 snac snac;
2590 xs *msg = NULL; 2580 xs *msg = NULL;
2591 2581
@@ -2597,7 +2587,8 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path,
2597 return 0; 2587 return 0;
2598 2588
2599 xs *l = xs_split_n(q_path, "/", 2); 2589 xs *l = xs_split_n(q_path, "/", 2);
2600 char *uid, *p_path; 2590 const char *uid;
2591 const char *p_path;
2601 2592
2602 uid = xs_list_get(l, 1); 2593 uid = xs_list_get(l, 1);
2603 if (!user_open(&snac, uid)) { 2594 if (!user_open(&snac, uid)) {
@@ -2615,7 +2606,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path,
2615 msg = msg_actor(&snac); 2606 msg = msg_actor(&snac);
2616 *ctype = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; 2607 *ctype = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
2617 2608
2618 char *ua = xs_dict_get(req, "user-agent"); 2609 const char *ua = xs_dict_get(req, "user-agent");
2619 2610
2620 snac_debug(&snac, 0, xs_fmt("serving actor [%s]", ua ? ua : "No UA")); 2611 snac_debug(&snac, 0, xs_fmt("serving actor [%s]", ua ? ua : "No UA"));
2621 } 2612 }
@@ -2625,15 +2616,16 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path,
2625 xs *elems = timeline_simple_list(&snac, "public", 0, 20); 2616 xs *elems = timeline_simple_list(&snac, "public", 0, 20);
2626 xs *list = xs_list_new(); 2617 xs *list = xs_list_new();
2627 msg = msg_collection(&snac, id); 2618 msg = msg_collection(&snac, id);
2628 char *p, *v; 2619 char *p;
2620 const char *v;
2629 2621
2630 p = elems; 2622 p = elems;
2631 while (xs_list_iter(&p, &v)) { 2623 while (xs_list_iter(&p, &v)) {
2632 xs *i = NULL; 2624 xs *i = NULL;
2633 2625
2634 if (valid_status(object_get_by_md5(v, &i))) { 2626 if (valid_status(object_get_by_md5(v, &i))) {
2635 char *type = xs_dict_get(i, "type"); 2627 const char *type = xs_dict_get(i, "type");
2636 char *id = xs_dict_get(i, "id"); 2628 const char *id = xs_dict_get(i, "id");
2637 2629
2638 if (type && id && strcmp(type, "Note") == 0 && xs_startswith(id, snac.actor)) { 2630 if (type && id && strcmp(type, "Note") == 0 && xs_startswith(id, snac.actor)) {
2639 xs *c_msg = msg_create(&snac, i); 2631 xs *c_msg = msg_create(&snac, i);
@@ -2686,9 +2678,9 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2686 (void)b_size; 2678 (void)b_size;
2687 2679
2688 int status = 202; /* accepted */ 2680 int status = 202; /* accepted */
2689 char *i_ctype = xs_dict_get(req, "content-type"); 2681 const char *i_ctype = xs_dict_get(req, "content-type");
2690 snac snac; 2682 snac snac;
2691 char *v; 2683 const char *v;
2692 2684
2693 if (i_ctype == NULL) { 2685 if (i_ctype == NULL) {
2694 *body = xs_str_new("no content-type"); 2686 *body = xs_str_new("no content-type");
diff --git a/data.c b/data.c
index 7dd7d19..edbc64f 100644
--- a/data.c
+++ b/data.c
@@ -10,6 +10,7 @@
10#include "xs_set.h" 10#include "xs_set.h"
11#include "xs_time.h" 11#include "xs_time.h"
12#include "xs_regex.h" 12#include "xs_regex.h"
13#include "xs_match.h"
13 14
14#include "snac.h" 15#include "snac.h"
15 16
@@ -28,7 +29,7 @@ pthread_mutex_t data_mutex = {0};
28int snac_upgrade(xs_str **error); 29int snac_upgrade(xs_str **error);
29 30
30 31
31int srv_open(char *basedir, int auto_upgrade) 32int srv_open(const char *basedir, int auto_upgrade)
32/* opens a server */ 33/* opens a server */
33{ 34{
34 int ret = 0; 35 int ret = 0;
@@ -57,18 +58,20 @@ int srv_open(char *basedir, int auto_upgrade)
57 if (srv_config == NULL) 58 if (srv_config == NULL)
58 error = xs_fmt("ERROR: cannot parse '%s'", cfg_file); 59 error = xs_fmt("ERROR: cannot parse '%s'", cfg_file);
59 else { 60 else {
60 char *host; 61 const char *host;
61 char *prefix; 62 const char *prefix;
62 char *dbglvl; 63 const char *dbglvl;
64 const char *proto;
63 65
64 host = xs_dict_get(srv_config, "host"); 66 host = xs_dict_get(srv_config, "host");
65 prefix = xs_dict_get(srv_config, "prefix"); 67 prefix = xs_dict_get(srv_config, "prefix");
66 dbglvl = xs_dict_get(srv_config, "dbglevel"); 68 dbglvl = xs_dict_get(srv_config, "dbglevel");
69 proto = xs_dict_get_def(srv_config, "protocol", "https");
67 70
68 if (host == NULL || prefix == NULL) 71 if (host == NULL || prefix == NULL)
69 error = xs_str_new("ERROR: cannot get server data"); 72 error = xs_str_new("ERROR: cannot get server data");
70 else { 73 else {
71 srv_baseurl = xs_fmt("https://%s%s", host, prefix); 74 srv_baseurl = xs_fmt("%s:/" "/%s%s", proto, host, prefix);
72 75
73 dbglevel = (int) xs_number_get(dbglvl); 76 dbglevel = (int) xs_number_get(dbglvl);
74 77
@@ -111,7 +114,7 @@ int srv_open(char *basedir, int auto_upgrade)
111#endif 114#endif
112 115
113#ifdef __OpenBSD__ 116#ifdef __OpenBSD__
114 char *v = xs_dict_get(srv_config, "disable_openbsd_security"); 117 const char *v = xs_dict_get(srv_config, "disable_openbsd_security");
115 118
116 if (v && xs_type(v) == XSTYPE_TRUE) { 119 if (v && xs_type(v) == XSTYPE_TRUE) {
117 srv_debug(1, xs_dup("OpenBSD security disabled by admin")); 120 srv_debug(1, xs_dup("OpenBSD security disabled by admin"));
@@ -190,7 +193,7 @@ int user_open(snac *user, const char *uid)
190 xs *lcuid = xs_tolower_i(xs_dup(uid)); 193 xs *lcuid = xs_tolower_i(xs_dup(uid));
191 xs *ulist = user_list(); 194 xs *ulist = user_list();
192 xs_list *p = ulist; 195 xs_list *p = ulist;
193 xs_str *v; 196 const xs_str *v;
194 197
195 while (xs_list_iter(&p, &v)) { 198 while (xs_list_iter(&p, &v)) {
196 xs *v2 = xs_tolower_i(xs_dup(v)); 199 xs *v2 = xs_tolower_i(xs_dup(v));
@@ -286,7 +289,7 @@ int user_open_by_md5(snac *snac, const char *md5)
286{ 289{
287 xs *ulist = user_list(); 290 xs *ulist = user_list();
288 xs_list *p = ulist; 291 xs_list *p = ulist;
289 xs_str *v; 292 const xs_str *v;
290 293
291 while (xs_list_iter(&p, &v)) { 294 while (xs_list_iter(&p, &v)) {
292 user_open(snac, v); 295 user_open(snac, v);
@@ -338,6 +341,12 @@ double f_ctime(const char *fn)
338} 341}
339 342
340 343
344int is_md5_hex(const char *md5)
345{
346 return xs_is_hex(md5) && strlen(md5) == 32;
347}
348
349
341/** database 2.1+ **/ 350/** database 2.1+ **/
342 351
343/** indexes **/ 352/** indexes **/
@@ -349,6 +358,11 @@ int index_add_md5(const char *fn, const char *md5)
349 int status = 201; /* Created */ 358 int status = 201; /* Created */
350 FILE *f; 359 FILE *f;
351 360
361 if (!is_md5_hex(md5)) {
362 srv_log(xs_fmt("index_add_md5: bad md5 %s %s", fn, md5));
363 return 400;
364 }
365
352 pthread_mutex_lock(&data_mutex); 366 pthread_mutex_lock(&data_mutex);
353 367
354 if ((f = fopen(fn, "a")) != NULL) { 368 if ((f = fopen(fn, "a")) != NULL) {
@@ -406,7 +420,7 @@ int index_del_md5(const char *fn, const char *md5)
406 fclose(f); 420 fclose(f);
407 } 421 }
408 else 422 else
409 status = 500; 423 status = 410;
410 424
411 pthread_mutex_unlock(&data_mutex); 425 pthread_mutex_unlock(&data_mutex);
412 426
@@ -604,7 +618,7 @@ static xs_str *_object_fn_by_md5(const char *md5, const char *func)
604 if (md5[0] == '-') 618 if (md5[0] == '-')
605 ok = 0; 619 ok = 0;
606 else 620 else
607 if (!xs_is_hex(md5) || strlen(md5) != 32) { 621 if (!is_md5_hex(md5)) {
608 srv_log(xs_fmt("_object_fn_by_md5() [from %s()]: bad md5 '%s'", func, md5)); 622 srv_log(xs_fmt("_object_fn_by_md5() [from %s()]: bad md5 '%s'", func, md5));
609 ok = 0; 623 ok = 0;
610 } 624 }
@@ -696,7 +710,7 @@ int _object_add(const char *id, const xs_dict *obj, int ow)
696 fclose(f); 710 fclose(f);
697 711
698 /* does this object has a parent? */ 712 /* does this object has a parent? */
699 char *in_reply_to = xs_dict_get(obj, "inReplyTo"); 713 const char *in_reply_to = xs_dict_get(obj, "inReplyTo");
700 714
701 if (!xs_is_null(in_reply_to) && *in_reply_to) { 715 if (!xs_is_null(in_reply_to) && *in_reply_to) {
702 /* update the children index of the parent */ 716 /* update the children index of the parent */
@@ -758,7 +772,8 @@ int object_del_by_md5(const char *md5)
758 xs *spec = xs_dup(fn); 772 xs *spec = xs_dup(fn);
759 spec = xs_replace_i(spec, ".json", "*.idx"); 773 spec = xs_replace_i(spec, ".json", "*.idx");
760 xs *files = xs_glob(spec, 0, 0); 774 xs *files = xs_glob(spec, 0, 0);
761 char *p, *v; 775 char *p;
776 const char *v;
762 777
763 p = files; 778 p = files;
764 while (xs_list_iter(&p, &v)) { 779 while (xs_list_iter(&p, &v)) {
@@ -917,6 +932,9 @@ int object_unadmire(const char *id, const char *actor, int like)
917 932
918 status = index_del(fn, actor); 933 status = index_del(fn, actor);
919 934
935 if (valid_status(status))
936 index_gc(fn);
937
920 srv_debug(0, 938 srv_debug(0,
921 xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); 939 xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status));
922 940
@@ -1016,7 +1034,8 @@ xs_list *follower_list(snac *snac)
1016{ 1034{
1017 xs *list = object_user_cache_list(snac, "followers", XS_ALL, 0); 1035 xs *list = object_user_cache_list(snac, "followers", XS_ALL, 0);
1018 xs_list *fwers = xs_list_new(); 1036 xs_list *fwers = xs_list_new();
1019 char *p, *v; 1037 char *p;
1038 const char *v;
1020 1039
1021 /* resolve the list of md5 to be a list of actors */ 1040 /* resolve the list of md5 to be a list of actors */
1022 p = list; 1041 p = list;
@@ -1060,14 +1079,18 @@ int timeline_touch(snac *snac)
1060xs_str *timeline_fn_by_md5(snac *snac, const char *md5) 1079xs_str *timeline_fn_by_md5(snac *snac, const char *md5)
1061/* get the filename of an entry by md5 from any timeline */ 1080/* get the filename of an entry by md5 from any timeline */
1062{ 1081{
1063 xs_str *fn = xs_fmt("%s/private/%s.json", snac->basedir, md5); 1082 xs_str *fn = NULL;
1064 1083
1065 if (mtime(fn) == 0.0) { 1084 if (xs_is_hex(md5) && strlen(md5) == 32) {
1066 fn = xs_free(fn); 1085 fn = xs_fmt("%s/private/%s.json", snac->basedir, md5);
1067 fn = xs_fmt("%s/public/%s.json", snac->basedir, md5);
1068 1086
1069 if (mtime(fn) == 0.0) 1087 if (mtime(fn) == 0.0) {
1070 fn = xs_free(fn); 1088 fn = xs_free(fn);
1089 fn = xs_fmt("%s/public/%s.json", snac->basedir, md5);
1090
1091 if (mtime(fn) == 0.0)
1092 fn = xs_free(fn);
1093 }
1071 } 1094 }
1072 1095
1073 return fn; 1096 return fn;
@@ -1103,7 +1126,7 @@ int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg)
1103} 1126}
1104 1127
1105 1128
1106int timeline_del(snac *snac, char *id) 1129int timeline_del(snac *snac, const char *id)
1107/* deletes a message from the timeline */ 1130/* deletes a message from the timeline */
1108{ 1131{
1109 /* delete from the user's caches */ 1132 /* delete from the user's caches */
@@ -1145,6 +1168,8 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg)
1145 1168
1146 tag_index(id, o_msg); 1169 tag_index(id, o_msg);
1147 1170
1171 list_distribute(snac, NULL, o_msg);
1172
1148 snac_debug(snac, 1, xs_fmt("timeline_add %s", id)); 1173 snac_debug(snac, 1, xs_fmt("timeline_add %s", id));
1149 1174
1150 return ret; 1175 return ret;
@@ -1169,17 +1194,16 @@ int timeline_admire(snac *snac, const char *id, const char *admirer, int like)
1169} 1194}
1170 1195
1171 1196
1172xs_list *timeline_top_level(snac *snac, xs_list *list) 1197xs_list *timeline_top_level(snac *snac, const xs_list *list)
1173/* returns the top level md5 entries from this index */ 1198/* returns the top level md5 entries from this index */
1174{ 1199{
1175 xs_set seen; 1200 xs_set seen;
1176 xs_list *p; 1201 const xs_str *v;
1177 xs_str *v;
1178 1202
1179 xs_set_init(&seen); 1203 xs_set_init(&seen);
1180 1204
1181 p = list; 1205 int c = 0;
1182 while (xs_list_iter(&p, &v)) { 1206 while (xs_list_next(list, &v, &c)) {
1183 char line[256] = ""; 1207 char line[256] = "";
1184 1208
1185 strncpy(line, v, sizeof(line)); 1209 strncpy(line, v, sizeof(line));
@@ -1267,7 +1291,7 @@ int following_add(snac *snac, const char *actor, const xs_dict *msg)
1267 /* object already exists; if it's of type Accept, 1291 /* object already exists; if it's of type Accept,
1268 the actor is already being followed and confirmed, 1292 the actor is already being followed and confirmed,
1269 so do nothing */ 1293 so do nothing */
1270 char *type = xs_dict_get(p_object, "type"); 1294 const char *type = xs_dict_get(p_object, "type");
1271 1295
1272 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) { 1296 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) {
1273 snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor)); 1297 snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor));
@@ -1345,7 +1369,7 @@ xs_list *following_list(snac *snac)
1345 xs *spec = xs_fmt("%s/following/" "*.json", snac->basedir); 1369 xs *spec = xs_fmt("%s/following/" "*.json", snac->basedir);
1346 xs *glist = xs_glob(spec, 0, 0); 1370 xs *glist = xs_glob(spec, 0, 0);
1347 xs_list *p; 1371 xs_list *p;
1348 xs_str *v; 1372 const xs_str *v;
1349 xs_list *list = xs_list_new(); 1373 xs_list *list = xs_list_new();
1350 1374
1351 /* iterate the list of files */ 1375 /* iterate the list of files */
@@ -1515,7 +1539,8 @@ void hide(snac *snac, const char *id)
1515 1539
1516 /* hide all the children */ 1540 /* hide all the children */
1517 xs *chld = object_children(id); 1541 xs *chld = object_children(id);
1518 char *p, *v; 1542 char *p;
1543 const char *v;
1519 1544
1520 p = chld; 1545 p = chld;
1521 while (xs_list_iter(&p, &v)) { 1546 while (xs_list_iter(&p, &v)) {
@@ -1523,8 +1548,9 @@ void hide(snac *snac, const char *id)
1523 1548
1524 /* resolve to get the id */ 1549 /* resolve to get the id */
1525 if (valid_status(object_get_by_md5(v, &co))) { 1550 if (valid_status(object_get_by_md5(v, &co))) {
1526 if ((v = xs_dict_get(co, "id")) != NULL) 1551 const char *id = xs_dict_get(co, "id");
1527 hide(snac, v); 1552 if (id != NULL)
1553 hide(snac, id);
1528 } 1554 }
1529 } 1555 }
1530 } 1556 }
@@ -1540,7 +1566,7 @@ int is_hidden(snac *snac, const char *id)
1540} 1566}
1541 1567
1542 1568
1543int actor_add(const char *actor, xs_dict *msg) 1569int actor_add(const char *actor, const xs_dict *msg)
1544/* adds an actor */ 1570/* adds an actor */
1545{ 1571{
1546 return object_add_ow(actor, msg); 1572 return object_add_ow(actor, msg);
@@ -1609,7 +1635,7 @@ int actor_get_refresh(snac *user, const char *actor, xs_dict **data)
1609 int status = actor_get(actor, data); 1635 int status = actor_get(actor, data);
1610 1636
1611 if (status == 205 && user && !xs_startswith(actor, srv_baseurl)) 1637 if (status == 205 && user && !xs_startswith(actor, srv_baseurl))
1612 enqueue_actor_refresh(user, actor); 1638 enqueue_actor_refresh(user, actor, 0);
1613 1639
1614 return status; 1640 return status;
1615} 1641}
@@ -1664,17 +1690,18 @@ int limited(snac *user, const char *id, int cmd)
1664void tag_index(const char *id, const xs_dict *obj) 1690void tag_index(const char *id, const xs_dict *obj)
1665/* update the tag indexes for this object */ 1691/* update the tag indexes for this object */
1666{ 1692{
1667 xs_list *tags = xs_dict_get(obj, "tag"); 1693 const xs_list *tags = xs_dict_get(obj, "tag");
1668 1694
1669 if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { 1695 if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) {
1670 xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir); 1696 xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir);
1671 1697
1672 mkdirx(g_tag_dir); 1698 mkdirx(g_tag_dir);
1673 1699
1674 xs_dict *v; 1700 const xs_dict *v;
1675 while (xs_list_iter(&tags, &v)) { 1701 int ct = 0;
1676 char *type = xs_dict_get(v, "type"); 1702 while (xs_list_next(tags, &v, &ct)) {
1677 char *name = xs_dict_get(v, "name"); 1703 const char *type = xs_dict_get(v, "type");
1704 const char *name = xs_dict_get(v, "name");
1678 1705
1679 if (!xs_is_null(type) && !xs_is_null(name) && strcmp(type, "Hashtag") == 0) { 1706 if (!xs_is_null(type) && !xs_is_null(name) && strcmp(type, "Hashtag") == 0) {
1680 while (*name == '#' || *name == '@') 1707 while (*name == '#' || *name == '@')
@@ -1683,7 +1710,7 @@ void tag_index(const char *id, const xs_dict *obj)
1683 if (*name == '\0') 1710 if (*name == '\0')
1684 continue; 1711 continue;
1685 1712
1686 name = xs_tolower_i(name); 1713 name = xs_tolower_i((xs_str *)name);
1687 1714
1688 xs *md5_tag = xs_md5_hex(name, strlen(name)); 1715 xs *md5_tag = xs_md5_hex(name, strlen(name));
1689 xs *tag_dir = xs_fmt("%s/%c%c", g_tag_dir, md5_tag[0], md5_tag[1]); 1716 xs *tag_dir = xs_fmt("%s/%c%c", g_tag_dir, md5_tag[0], md5_tag[1]);
@@ -1706,7 +1733,7 @@ void tag_index(const char *id, const xs_dict *obj)
1706} 1733}
1707 1734
1708 1735
1709xs_list *tag_search(char *tag, int skip, int show) 1736xs_list *tag_search(const char *tag, int skip, int show)
1710/* returns the list of posts tagged with tag */ 1737/* returns the list of posts tagged with tag */
1711{ 1738{
1712 if (*tag == '#') 1739 if (*tag == '#')
@@ -1720,6 +1747,206 @@ xs_list *tag_search(char *tag, int skip, int show)
1720} 1747}
1721 1748
1722 1749
1750/** lists **/
1751
1752xs_val *list_maint(snac *user, const char *list, int op)
1753/* list maintenance */
1754{
1755 xs_val *l = NULL;
1756
1757 switch (op) {
1758 case 0: /** list of lists **/
1759 {
1760 FILE *f;
1761 xs *spec = xs_fmt("%s/list/" "*.id", user->basedir);
1762 xs *ls = xs_glob(spec, 0, 0);
1763 int c = 0;
1764 const char *v;
1765
1766 l = xs_list_new();
1767
1768 while (xs_list_next(ls, &v, &c)) {
1769 if ((f = fopen(v, "r")) != NULL) {
1770 xs *title = xs_readline(f);
1771 fclose(f);
1772
1773 title = xs_strip_i(title);
1774
1775 xs *v2 = xs_replace(v, ".id", "");
1776 xs *l2 = xs_split(v2, "/");
1777
1778 /* return [ list_id, list_title ] */
1779 l = xs_list_append(l, xs_list_append(xs_list_new(), xs_list_get(l2, -1), title));
1780 }
1781 }
1782 }
1783
1784 break;
1785
1786 case 1: /** create new list (list is the name) **/
1787 {
1788 xs *lol = list_maint(user, NULL, 0);
1789 int c = 0;
1790 const xs_list *v;
1791 int add = 1;
1792
1793 /* check if this list name already exists */
1794 while (xs_list_next(lol, &v, &c)) {
1795 if (strcmp(xs_list_get(v, 1), list) == 0) {
1796 add = 0;
1797 break;
1798 }
1799 }
1800
1801 if (add) {
1802 FILE *f;
1803 xs *dir = xs_fmt("%s/list/", user->basedir);
1804 xs *id = xs_fmt("%010x", time(NULL));
1805
1806 mkdirx(dir);
1807
1808 xs *fn = xs_fmt("%s%s.id", dir, id);
1809
1810 if ((f = fopen(fn, "w")) != NULL) {
1811 fprintf(f, "%s\n", list);
1812 fclose(f);
1813 }
1814
1815 l = xs_stock(XSTYPE_TRUE);
1816 }
1817 else
1818 l = xs_stock(XSTYPE_FALSE);
1819 }
1820
1821 break;
1822
1823 case 2: /** delete list (list is the id) **/
1824 {
1825 if (xs_is_hex(list)) {
1826 xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list);
1827 unlink(fn);
1828
1829 fn = xs_replace_i(fn, ".id", ".lst");
1830 unlink(fn);
1831
1832 fn = xs_replace_i(fn, ".lst", ".idx");
1833 unlink(fn);
1834
1835 fn = xs_str_cat(fn, ".bak");
1836 unlink(fn);
1837 }
1838 }
1839
1840 break;
1841
1842 case 3: /** get list name **/
1843 if (xs_is_hex(list)) {
1844 FILE *f;
1845 xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list);
1846
1847 if ((f = fopen(fn, "r")) != NULL) {
1848 l = xs_strip_i(xs_readline(f));
1849 fclose(f);
1850 }
1851 }
1852
1853 break;
1854 }
1855
1856 return l;
1857}
1858
1859
1860xs_list *list_timeline(snac *user, const char *list, int skip, int show)
1861/* returns the timeline of a list */
1862{
1863 xs_list *l = NULL;
1864
1865 if (!xs_is_hex(list))
1866 return NULL;
1867
1868 xs *fn = xs_fmt("%s/list/%s.idx", user->basedir, list);
1869
1870 if (mtime(fn) > 0.0)
1871 l = index_list_desc(fn, skip, show);
1872
1873 return l;
1874}
1875
1876
1877xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op)
1878/* list content management */
1879{
1880 xs_val *l = NULL;
1881
1882 if (!xs_is_hex(list))
1883 return NULL;
1884
1885 if (actor_md5 != NULL && !xs_is_hex(actor_md5))
1886 return NULL;
1887
1888 xs *fn = xs_fmt("%s/list/%s.lst", user->basedir, list);
1889
1890 switch (op) {
1891 case 0: /** list content **/
1892 l = index_list(fn, XS_ALL);
1893
1894 break;
1895
1896 case 1: /** append actor to list **/
1897 if (actor_md5 != NULL) {
1898 if (!index_in(fn, actor_md5))
1899 index_add_md5(fn, actor_md5);
1900 }
1901
1902 break;
1903
1904 case 2: /** delete actor from list **/
1905 if (actor_md5 != NULL)
1906 index_del_md5(fn, actor_md5);
1907
1908 break;
1909
1910 default:
1911 srv_log(xs_fmt("ERROR: list_content: bad op %d", op));
1912 break;
1913 }
1914
1915 return l;
1916}
1917
1918
1919void list_distribute(snac *user, const char *who, const xs_dict *post)
1920/* distributes the post to all appropriate lists */
1921{
1922 const char *id = xs_dict_get(post, "id");
1923
1924 /* if who is not set, use the attributedTo in the message */
1925 if (xs_is_null(who))
1926 who = get_atto(post);
1927
1928 if (xs_type(who) == XSTYPE_STRING && xs_type(id) == XSTYPE_STRING) {
1929 xs *a_md5 = xs_md5_hex(who, strlen(who));
1930 xs *i_md5 = xs_md5_hex(id, strlen(id));
1931 xs *spec = xs_fmt("%s/list/" "*.lst", user->basedir);
1932 xs *ls = xs_glob(spec, 0, 0);
1933 int c = 0;
1934 const char *v;
1935
1936 while (xs_list_next(ls, &v, &c)) {
1937 /* is the actor in this list? */
1938 if (index_in_md5(v, a_md5)) {
1939 /* it is; add post md5 to its timeline */
1940 xs *idx = xs_replace(v, ".lst", ".idx");
1941 index_add_md5(idx, i_md5);
1942
1943 snac_debug(user, 1, xs_fmt("listed post %s in %s", id, idx));
1944 }
1945 }
1946 }
1947}
1948
1949
1723/** static data **/ 1950/** static data **/
1724 1951
1725static int _load_raw_file(const char *fn, xs_val **data, int *size, 1952static int _load_raw_file(const char *fn, xs_val **data, int *size,
@@ -1944,7 +2171,7 @@ void inbox_add(const char *inbox)
1944void inbox_add_by_actor(const xs_dict *actor) 2171void inbox_add_by_actor(const xs_dict *actor)
1945/* collects an actor's shared inbox, if it has one */ 2172/* collects an actor's shared inbox, if it has one */
1946{ 2173{
1947 char *v; 2174 const char *v;
1948 2175
1949 if (!xs_is_null(v = xs_dict_get(actor, "endpoints")) && 2176 if (!xs_is_null(v = xs_dict_get(actor, "endpoints")) &&
1950 !xs_is_null(v = xs_dict_get(v, "sharedInbox"))) { 2177 !xs_is_null(v = xs_dict_get(v, "sharedInbox"))) {
@@ -1962,7 +2189,7 @@ xs_list *inbox_list(void)
1962 xs *spec = xs_fmt("%s/inbox/" "*", srv_basedir); 2189 xs *spec = xs_fmt("%s/inbox/" "*", srv_basedir);
1963 xs *files = xs_glob(spec, 0, 0); 2190 xs *files = xs_glob(spec, 0, 0);
1964 xs_list *p = files; 2191 xs_list *p = files;
1965 xs_val *v; 2192 const xs_val *v;
1966 2193
1967 while (xs_list_iter(&p, &v)) { 2194 while (xs_list_iter(&p, &v)) {
1968 FILE *f; 2195 FILE *f;
@@ -1987,9 +2214,10 @@ xs_list *inbox_list(void)
1987 2214
1988xs_str *_instance_block_fn(const char *instance) 2215xs_str *_instance_block_fn(const char *instance)
1989{ 2216{
1990 xs *s1 = xs_replace(instance, "https:/" "/", ""); 2217 xs *s = xs_replace(instance, "http:/" "/", "");
2218 xs *s1 = xs_replace(s, "https:/" "/", "");
1991 xs *l = xs_split(s1, "/"); 2219 xs *l = xs_split(s1, "/");
1992 char *p = xs_list_get(l, 0); 2220 const char *p = xs_list_get(l, 0);
1993 xs *md5 = xs_md5_hex(p, strlen(p)); 2221 xs *md5 = xs_md5_hex(p, strlen(p));
1994 2222
1995 return xs_fmt("%s/block/%s", srv_basedir, md5); 2223 return xs_fmt("%s/block/%s", srv_basedir, md5);
@@ -2049,20 +2277,20 @@ int instance_unblock(const char *instance)
2049} 2277}
2050 2278
2051 2279
2052/** content filtering **/ 2280/** operations by content **/
2053 2281
2054int content_check(const char *file, const xs_dict *msg) 2282int content_match(const char *file, const xs_dict *msg)
2055/* checks if a message's content matches any of the regexes in file */ 2283/* checks if a message's content matches any of the regexes in file */
2056/* file format: one regex per line */ 2284/* file format: one regex per line */
2057{ 2285{
2058 xs *fn = xs_fmt("%s/%s", srv_basedir, file); 2286 xs *fn = xs_fmt("%s/%s", srv_basedir, file);
2059 FILE *f; 2287 FILE *f;
2060 int r = 0; 2288 int r = 0;
2061 char *v = xs_dict_get(msg, "content"); 2289 const char *v = xs_dict_get(msg, "content");
2062 2290
2063 if (xs_type(v) == XSTYPE_STRING && *v) { 2291 if (xs_type(v) == XSTYPE_STRING && *v) {
2064 if ((f = fopen(fn, "r")) != NULL) { 2292 if ((f = fopen(fn, "r")) != NULL) {
2065 srv_debug(1, xs_fmt("content_check: loading regexes from %s", fn)); 2293 srv_debug(1, xs_fmt("content_match: loading regexes from %s", fn));
2066 2294
2067 /* massage content (strip HTML tags, etc.) */ 2295 /* massage content (strip HTML tags, etc.) */
2068 xs *c = xs_regex_replace(v, "<[^>]+>", " "); 2296 xs *c = xs_regex_replace(v, "<[^>]+>", " ");
@@ -2072,13 +2300,9 @@ int content_check(const char *file, const xs_dict *msg)
2072 while (!r && !feof(f)) { 2300 while (!r && !feof(f)) {
2073 xs *rx = xs_strip_i(xs_readline(f)); 2301 xs *rx = xs_strip_i(xs_readline(f));
2074 2302
2075 if (*rx) { 2303 if (*rx && xs_regex_match(c, rx)) {
2076 xs *l = xs_regex_select_n(c, rx, 1); 2304 srv_debug(1, xs_fmt("content_match: match for '%s'", rx));
2077 2305 r = 1;
2078 if (xs_list_len(l)) {
2079 srv_debug(1, xs_fmt("content_check: match for '%s'", rx));
2080 r = 1;
2081 }
2082 } 2306 }
2083 } 2307 }
2084 2308
@@ -2090,6 +2314,119 @@ int content_check(const char *file, const xs_dict *msg)
2090} 2314}
2091 2315
2092 2316
2317xs_list *content_search(snac *user, const char *regex,
2318 int priv, int skip, int show, int max_secs, int *timeout)
2319/* returns a list of posts which content matches the regex */
2320{
2321 if (regex == NULL || *regex == '\0')
2322 return xs_list_new();
2323
2324 xs *i_regex = xs_tolower_i(xs_dup(regex));
2325
2326 xs_set seen;
2327
2328 xs_set_init(&seen);
2329
2330 if (max_secs == 0)
2331 max_secs = 3;
2332
2333 time_t t = time(NULL) + max_secs;
2334 *timeout = 0;
2335
2336 /* iterate all timelines simultaneously */
2337 xs_list *tls[3] = {0};
2338 const char *md5s[3] = {0};
2339 int c[3] = {0};
2340
2341 tls[0] = timeline_simple_list(user, "public", 0, XS_ALL); /* public */
2342 tls[1] = timeline_instance_list(0, XS_ALL); /* instance */
2343 tls[2] = priv ? timeline_simple_list(user, "private", 0, XS_ALL) : xs_list_new(); /* private or none */
2344
2345 /* first positioning */
2346 for (int n = 0; n < 3; n++)
2347 xs_list_next(tls[n], &md5s[n], &c[n]);
2348
2349 show += skip;
2350
2351 while (show > 0) {
2352 /* timeout? */
2353 if (time(NULL) > t) {
2354 *timeout = 1;
2355 break;
2356 }
2357
2358 /* find the newest post */
2359 int newest = -1;
2360 double mtime = 0.0;
2361
2362 for (int n = 0; n < 3; n++) {
2363 if (md5s[n] != NULL) {
2364 xs *fn = _object_fn_by_md5(md5s[n], "content_search");
2365 double mt = mtime(fn);
2366
2367 if (mt > mtime) {
2368 newest = n;
2369 mtime = mt;
2370 }
2371 }
2372 }
2373
2374 if (newest == -1)
2375 break;
2376
2377 const char *md5 = md5s[newest];
2378
2379 /* advance the chosen timeline */
2380 if (!xs_list_next(tls[newest], &md5s[newest], &c[newest]))
2381 md5s[newest] = NULL;
2382
2383 xs *post = NULL;
2384
2385 if (!valid_status(object_get_by_md5(md5, &post)))
2386 continue;
2387
2388 if (!xs_match(xs_dict_get_def(post, "type", "-"), POSTLIKE_OBJECT_TYPE))
2389 continue;
2390
2391 const char *id = xs_dict_get(post, "id");
2392
2393 if (id == NULL || is_hidden(user, id))
2394 continue;
2395
2396 const char *content = xs_dict_get(post, "content");
2397
2398 if (xs_is_null(content))
2399 continue;
2400
2401 /* strip HTML */
2402 xs *c = xs_regex_replace(content, "<[^>]+>", " ");
2403 c = xs_regex_replace_i(c, " {2,}", " ");
2404 c = xs_tolower_i(c);
2405
2406 /* apply regex */
2407 if (xs_regex_match(c, i_regex)) {
2408 if (xs_set_add(&seen, md5) == 1)
2409 show--;
2410 }
2411 }
2412
2413 xs_list *r = xs_set_result(&seen);
2414
2415 if (skip) {
2416 /* BAD */
2417 while (skip--) {
2418 r = xs_list_del(r, 0);
2419 }
2420 }
2421
2422 xs_free(tls[0]);
2423 xs_free(tls[1]);
2424 xs_free(tls[2]);
2425
2426 return r;
2427}
2428
2429
2093/** notifications **/ 2430/** notifications **/
2094 2431
2095xs_str *notify_check_time(snac *snac, int reset) 2432xs_str *notify_check_time(snac *snac, int reset)
@@ -2203,7 +2540,7 @@ xs_list *notify_list(snac *snac, int skip, int show)
2203 xs *spec = xs_fmt("%s/notify/" "*.json", snac->basedir); 2540 xs *spec = xs_fmt("%s/notify/" "*.json", snac->basedir);
2204 xs *lst = xs_glob(spec, 1, 0); 2541 xs *lst = xs_glob(spec, 1, 0);
2205 xs_list *p = lst; 2542 xs_list *p = lst;
2206 char *v; 2543 const char *v;
2207 2544
2208 while (xs_list_iter(&p, &v)) { 2545 while (xs_list_iter(&p, &v)) {
2209 char *p = strrchr(v, '.'); 2546 char *p = strrchr(v, '.');
@@ -2231,7 +2568,7 @@ int notify_new_num(snac *snac)
2231 int cnt = 0; 2568 int cnt = 0;
2232 2569
2233 xs_list *p = lst; 2570 xs_list *p = lst;
2234 xs_str *v; 2571 const xs_str *v;
2235 2572
2236 while (xs_list_iter(&p, &v)) { 2573 while (xs_list_iter(&p, &v)) {
2237 xs *id = xs_strip_i(xs_dup(v)); 2574 xs *id = xs_strip_i(xs_dup(v));
@@ -2253,7 +2590,7 @@ void notify_clear(snac *snac)
2253 xs *spec = xs_fmt("%s/notify/" "*", snac->basedir); 2590 xs *spec = xs_fmt("%s/notify/" "*", snac->basedir);
2254 xs *lst = xs_glob(spec, 0, 0); 2591 xs *lst = xs_glob(spec, 0, 0);
2255 xs_list *p = lst; 2592 xs_list *p = lst;
2256 xs_str *v; 2593 const xs_str *v;
2257 2594
2258 while (xs_list_iter(&p, &v)) 2595 while (xs_list_iter(&p, &v))
2259 unlink(v); 2596 unlink(v);
@@ -2309,7 +2646,7 @@ void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retri
2309/* enqueues an input message */ 2646/* enqueues an input message */
2310{ 2647{
2311 xs *qmsg = _new_qmsg("input", msg, retries); 2648 xs *qmsg = _new_qmsg("input", msg, retries);
2312 char *ntid = xs_dict_get(qmsg, "ntid"); 2649 const char *ntid = xs_dict_get(qmsg, "ntid");
2313 xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); 2650 xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid);
2314 2651
2315 qmsg = xs_dict_append(qmsg, "req", req); 2652 qmsg = xs_dict_append(qmsg, "req", req);
@@ -2324,7 +2661,7 @@ void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries)
2324/* enqueues an input message from the shared input */ 2661/* enqueues an input message from the shared input */
2325{ 2662{
2326 xs *qmsg = _new_qmsg("input", msg, retries); 2663 xs *qmsg = _new_qmsg("input", msg, retries);
2327 char *ntid = xs_dict_get(qmsg, "ntid"); 2664 const char *ntid = xs_dict_get(qmsg, "ntid");
2328 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 2665 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid);
2329 2666
2330 qmsg = xs_dict_append(qmsg, "req", req); 2667 qmsg = xs_dict_append(qmsg, "req", req);
@@ -2336,11 +2673,12 @@ void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries)
2336 2673
2337 2674
2338void enqueue_output_raw(const char *keyid, const char *seckey, 2675void enqueue_output_raw(const char *keyid, const char *seckey,
2339 xs_dict *msg, xs_str *inbox, int retries, int p_status) 2676 const xs_dict *msg, const xs_str *inbox,
2677 int retries, int p_status)
2340/* enqueues an output message to an inbox */ 2678/* enqueues an output message to an inbox */
2341{ 2679{
2342 xs *qmsg = _new_qmsg("output", msg, retries); 2680 xs *qmsg = _new_qmsg("output", msg, retries);
2343 char *ntid = xs_dict_get(qmsg, "ntid"); 2681 const char *ntid = xs_dict_get(qmsg, "ntid");
2344 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 2682 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid);
2345 2683
2346 xs *ns = xs_number_new(p_status); 2684 xs *ns = xs_number_new(p_status);
@@ -2360,7 +2698,8 @@ void enqueue_output_raw(const char *keyid, const char *seckey,
2360} 2698}
2361 2699
2362 2700
2363void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries, int p_status) 2701void enqueue_output(snac *snac, const xs_dict *msg,
2702 const xs_str *inbox, int retries, int p_status)
2364/* enqueues an output message to an inbox */ 2703/* enqueues an output message to an inbox */
2365{ 2704{
2366 if (xs_startswith(inbox, snac->actor)) { 2705 if (xs_startswith(inbox, snac->actor)) {
@@ -2368,13 +2707,14 @@ void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries, int p_
2368 return; 2707 return;
2369 } 2708 }
2370 2709
2371 char *seckey = xs_dict_get(snac->key, "secret"); 2710 const char *seckey = xs_dict_get(snac->key, "secret");
2372 2711
2373 enqueue_output_raw(snac->actor, seckey, msg, inbox, retries, p_status); 2712 enqueue_output_raw(snac->actor, seckey, msg, inbox, retries, p_status);
2374} 2713}
2375 2714
2376 2715
2377void enqueue_output_by_actor(snac *snac, xs_dict *msg, const xs_str *actor, int retries) 2716void enqueue_output_by_actor(snac *snac, const xs_dict *msg,
2717 const xs_str *actor, int retries)
2378/* enqueues an output message for an actor */ 2718/* enqueues an output message for an actor */
2379{ 2719{
2380 xs *inbox = get_actor_inbox(actor); 2720 xs *inbox = get_actor_inbox(actor);
@@ -2386,11 +2726,11 @@ void enqueue_output_by_actor(snac *snac, xs_dict *msg, const xs_str *actor, int
2386} 2726}
2387 2727
2388 2728
2389void enqueue_email(xs_str *msg, int retries) 2729void enqueue_email(const xs_str *msg, int retries)
2390/* enqueues an email message to be sent */ 2730/* enqueues an email message to be sent */
2391{ 2731{
2392 xs *qmsg = _new_qmsg("email", msg, retries); 2732 xs *qmsg = _new_qmsg("email", msg, retries);
2393 char *ntid = xs_dict_get(qmsg, "ntid"); 2733 const char *ntid = xs_dict_get(qmsg, "ntid");
2394 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 2734 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid);
2395 2735
2396 qmsg = _enqueue_put(fn, qmsg); 2736 qmsg = _enqueue_put(fn, qmsg);
@@ -2403,7 +2743,7 @@ void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id)
2403/* enqueues a message to be sent via Telegram */ 2743/* enqueues a message to be sent via Telegram */
2404{ 2744{
2405 xs *qmsg = _new_qmsg("telegram", msg, 0); 2745 xs *qmsg = _new_qmsg("telegram", msg, 0);
2406 char *ntid = xs_dict_get(qmsg, "ntid"); 2746 const char *ntid = xs_dict_get(qmsg, "ntid");
2407 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 2747 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid);
2408 2748
2409 qmsg = xs_dict_append(qmsg, "bot", bot); 2749 qmsg = xs_dict_append(qmsg, "bot", bot);
@@ -2418,7 +2758,7 @@ void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_t
2418/* enqueues a message to be sent via ntfy */ 2758/* enqueues a message to be sent via ntfy */
2419{ 2759{
2420 xs *qmsg = _new_qmsg("ntfy", msg, 0); 2760 xs *qmsg = _new_qmsg("ntfy", msg, 0);
2421 char *ntid = xs_dict_get(qmsg, "ntid"); 2761 const char *ntid = xs_dict_get(qmsg, "ntid");
2422 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); 2762 xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid);
2423 2763
2424 qmsg = xs_dict_append(qmsg, "ntfy_server", ntfy_server); 2764 qmsg = xs_dict_append(qmsg, "ntfy_server", ntfy_server);
@@ -2434,7 +2774,7 @@ void enqueue_message(snac *snac, const xs_dict *msg)
2434/* enqueues an output message */ 2774/* enqueues an output message */
2435{ 2775{
2436 xs *qmsg = _new_qmsg("message", msg, 0); 2776 xs *qmsg = _new_qmsg("message", msg, 0);
2437 char *ntid = xs_dict_get(qmsg, "ntid"); 2777 const char *ntid = xs_dict_get(qmsg, "ntid");
2438 xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); 2778 xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid);
2439 2779
2440 qmsg = _enqueue_put(fn, qmsg); 2780 qmsg = _enqueue_put(fn, qmsg);
@@ -2458,67 +2798,47 @@ void enqueue_close_question(snac *user, const char *id, int end_secs)
2458} 2798}
2459 2799
2460 2800
2461void enqueue_verify_links(snac *user) 2801void enqueue_object_request(snac *user, const char *id, int forward_secs)
2462/* enqueues a link verification */ 2802/* enqueues the request of an object in the future */
2463{ 2803{
2464 xs *qmsg = _new_qmsg("verify_links", "", 0); 2804 xs *qmsg = _new_qmsg("object_request", id, 0);
2465 char *ntid = xs_dict_get(qmsg, "ntid"); 2805 xs *ntid = tid(forward_secs);
2466 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 2806 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid);
2807
2808 qmsg = xs_dict_set(qmsg, "ntid", ntid);
2467 2809
2468 qmsg = _enqueue_put(fn, qmsg); 2810 qmsg = _enqueue_put(fn, qmsg);
2469 2811
2470 snac_debug(user, 1, xs_fmt("enqueue_verify_links %s", user->actor)); 2812 snac_debug(user, 0, xs_fmt("enqueue_object_request %s %d", id, forward_secs));
2471} 2813}
2472 2814
2473 2815
2474void enqueue_actor_refresh(snac *user, const char *actor) 2816void enqueue_verify_links(snac *user)
2475/* enqueues an actor refresh */ 2817/* enqueues a link verification */
2476{ 2818{
2477 xs *qmsg = _new_qmsg("actor_refresh", "", 0); 2819 xs *qmsg = _new_qmsg("verify_links", "", 0);
2478 char *ntid = xs_dict_get(qmsg, "ntid"); 2820 const char *ntid = xs_dict_get(qmsg, "ntid");
2479 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 2821 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid);
2480 2822
2481 qmsg = xs_dict_append(qmsg, "actor", actor);
2482
2483 qmsg = _enqueue_put(fn, qmsg); 2823 qmsg = _enqueue_put(fn, qmsg);
2484 2824
2485 snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor)); 2825 snac_debug(user, 1, xs_fmt("enqueue_verify_links %s", user->actor));
2486} 2826}
2487 2827
2488 2828
2489void enqueue_request_replies(snac *user, const char *id) 2829void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs)
2490/* enqueues a request for the replies of a message */ 2830/* enqueues an actor refresh */
2491{ 2831{
2492 /* test first if this precise request is already in the queue */ 2832 xs *qmsg = _new_qmsg("actor_refresh", "", 0);
2493 xs *queue = user_queue(user); 2833 xs *ntid = tid(forward_secs);
2494 xs_list *p = queue;
2495 xs_str *v;
2496
2497 while (xs_list_iter(&p, &v)) {
2498 xs *q_item = queue_get(v);
2499
2500 if (q_item != NULL) {
2501 const char *type = xs_dict_get(q_item, "type");
2502 const char *msg = xs_dict_get(q_item, "message");
2503
2504 if (type && msg && strcmp(type, "request_replies") == 0 && strcmp(msg, id) == 0) {
2505 /* don't requeue */
2506 snac_debug(user, 1, xs_fmt("enqueue_request_replies already here %s", id));
2507 return;
2508 }
2509 }
2510 }
2511
2512 /* not there; enqueue the request with a small delay */
2513 xs *qmsg = _new_qmsg("request_replies", id, 0);
2514 xs *ntid = tid(10);
2515 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); 2834 xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid);
2516 2835
2517 qmsg = xs_dict_set(qmsg, "ntid", ntid); 2836 qmsg = xs_dict_set(qmsg, "ntid", ntid);
2837 qmsg = xs_dict_append(qmsg, "actor", actor);
2518 2838
2519 qmsg = _enqueue_put(fn, qmsg); 2839 qmsg = _enqueue_put(fn, qmsg);
2520 2840
2521 snac_debug(user, 2, xs_fmt("enqueue_request_replies %s", id)); 2841 snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor));
2522} 2842}
2523 2843
2524 2844
@@ -2528,14 +2848,14 @@ int was_question_voted(snac *user, const char *id)
2528 xs *children = object_children(id); 2848 xs *children = object_children(id);
2529 int voted = 0; 2849 int voted = 0;
2530 xs_list *p; 2850 xs_list *p;
2531 xs_str *md5; 2851 const xs_str *md5;
2532 2852
2533 p = children; 2853 p = children;
2534 while (xs_list_iter(&p, &md5)) { 2854 while (xs_list_iter(&p, &md5)) {
2535 xs *obj = NULL; 2855 xs *obj = NULL;
2536 2856
2537 if (valid_status(object_get_by_md5(md5, &obj))) { 2857 if (valid_status(object_get_by_md5(md5, &obj))) {
2538 char *atto = get_atto(obj); 2858 const char *atto = get_atto(obj);
2539 if (atto && strcmp(atto, user->actor) == 0 && 2859 if (atto && strcmp(atto, user->actor) == 0 &&
2540 !xs_is_null(xs_dict_get(obj, "name"))) { 2860 !xs_is_null(xs_dict_get(obj, "name"))) {
2541 voted = 1; 2861 voted = 1;
@@ -2555,7 +2875,7 @@ xs_list *user_queue(snac *snac)
2555 xs_list *list = xs_list_new(); 2875 xs_list *list = xs_list_new();
2556 time_t t = time(NULL); 2876 time_t t = time(NULL);
2557 xs_list *p; 2877 xs_list *p;
2558 xs_val *v; 2878 const xs_val *v;
2559 2879
2560 xs *fns = xs_glob(spec, 0, 0); 2880 xs *fns = xs_glob(spec, 0, 0);
2561 2881
@@ -2584,7 +2904,7 @@ xs_list *queue(void)
2584 xs_list *list = xs_list_new(); 2904 xs_list *list = xs_list_new();
2585 time_t t = time(NULL); 2905 time_t t = time(NULL);
2586 xs_list *p; 2906 xs_list *p;
2587 xs_val *v; 2907 const xs_val *v;
2588 2908
2589 xs *fns = xs_glob(spec, 0, 0); 2909 xs *fns = xs_glob(spec, 0, 0);
2590 2910
@@ -2660,7 +2980,7 @@ static void _purge_dir(const char *dir, int days)
2660 xs *spec = xs_fmt("%s/" "*", dir); 2980 xs *spec = xs_fmt("%s/" "*", dir);
2661 xs *list = xs_glob(spec, 0, 0); 2981 xs *list = xs_glob(spec, 0, 0);
2662 xs_list *p; 2982 xs_list *p;
2663 xs_str *v; 2983 const xs_str *v;
2664 2984
2665 p = list; 2985 p = list;
2666 while (xs_list_iter(&p, &v)) 2986 while (xs_list_iter(&p, &v))
@@ -2686,7 +3006,7 @@ void purge_server(void)
2686 xs *spec = xs_fmt("%s/object/??", srv_basedir); 3006 xs *spec = xs_fmt("%s/object/??", srv_basedir);
2687 xs *dirs = xs_glob(spec, 0, 0); 3007 xs *dirs = xs_glob(spec, 0, 0);
2688 xs_list *p; 3008 xs_list *p;
2689 xs_str *v; 3009 const xs_str *v;
2690 int cnt = 0; 3010 int cnt = 0;
2691 int icnt = 0; 3011 int icnt = 0;
2692 3012
@@ -2695,7 +3015,7 @@ void purge_server(void)
2695 p = dirs; 3015 p = dirs;
2696 while (xs_list_iter(&p, &v)) { 3016 while (xs_list_iter(&p, &v)) {
2697 xs_list *p2; 3017 xs_list *p2;
2698 xs_str *v2; 3018 const xs_str *v2;
2699 3019
2700 { 3020 {
2701 xs *spec2 = xs_fmt("%s/" "*.json", v); 3021 xs *spec2 = xs_fmt("%s/" "*.json", v);
@@ -2709,7 +3029,7 @@ void purge_server(void)
2709 if (mtime_nl(v2, &n_link) < mt && n_link < 2) { 3029 if (mtime_nl(v2, &n_link) < mt && n_link < 2) {
2710 xs *s1 = xs_replace(v2, ".json", ""); 3030 xs *s1 = xs_replace(v2, ".json", "");
2711 xs *l = xs_split(s1, "/"); 3031 xs *l = xs_split(s1, "/");
2712 char *md5 = xs_list_get(l, -1); 3032 const char *md5 = xs_list_get(l, -1);
2713 3033
2714 object_del_by_md5(md5); 3034 object_del_by_md5(md5);
2715 cnt++; 3035 cnt++;
@@ -2743,6 +3063,16 @@ void purge_server(void)
2743 } 3063 }
2744 } 3064 }
2745 } 3065 }
3066
3067 /* delete index backups */
3068 xs *specb = xs_fmt("%s/" "*.bak", v);
3069 xs *bakfs = xs_glob(specb, 0, 0);
3070
3071 p2 = bakfs;
3072 while (xs_list_iter(&p2, &v2)) {
3073 unlink(v2);
3074 srv_debug(1, xs_fmt("purged %s", v2));
3075 }
2746 } 3076 }
2747 } 3077 }
2748 3078
@@ -2764,7 +3094,7 @@ void purge_server(void)
2764 xs *spec2 = xs_fmt("%s/" "*.idx", v); 3094 xs *spec2 = xs_fmt("%s/" "*.idx", v);
2765 xs *files = xs_glob(spec2, 0, 0); 3095 xs *files = xs_glob(spec2, 0, 0);
2766 xs_list *p2; 3096 xs_list *p2;
2767 xs_str *v2; 3097 const xs_str *v2;
2768 3098
2769 p2 = files; 3099 p2 = files;
2770 while (xs_list_iter(&p2, &v2)) { 3100 while (xs_list_iter(&p2, &v2)) {
@@ -2791,7 +3121,7 @@ void purge_user(snac *snac)
2791/* do the purge for this user */ 3121/* do the purge for this user */
2792{ 3122{
2793 int priv_days, pub_days, user_days = 0; 3123 int priv_days, pub_days, user_days = 0;
2794 char *v; 3124 const char *v;
2795 int n; 3125 int n;
2796 3126
2797 priv_days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days")); 3127 priv_days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days"));
@@ -2823,6 +3153,19 @@ void purge_user(snac *snac)
2823 srv_debug(1, xs_fmt("purge: %s %d", idx, gc)); 3153 srv_debug(1, xs_fmt("purge: %s %d", idx, gc));
2824 } 3154 }
2825 3155
3156 /* purge lists */
3157 {
3158 xs *spec = xs_fmt("%s/list/" "*.idx", snac->basedir);
3159 xs *lol = xs_glob(spec, 0, 0);
3160 int c = 0;
3161 const char *v;
3162
3163 while (xs_list_next(lol, &v, &c)) {
3164 int gc = index_gc(v);
3165 srv_debug(1, xs_fmt("purge: %s %d", v, gc));
3166 }
3167 }
3168
2826 /* unrelated to purging, but it's a janitorial process, so what the hell */ 3169 /* unrelated to purging, but it's a janitorial process, so what the hell */
2827 verify_links(snac); 3170 verify_links(snac);
2828} 3171}
@@ -2833,7 +3176,8 @@ void purge_all(void)
2833{ 3176{
2834 snac snac; 3177 snac snac;
2835 xs *list = user_list(); 3178 xs *list = user_list();
2836 char *p, *uid; 3179 char *p;
3180 const char *uid;
2837 3181
2838 p = list; 3182 p = list;
2839 while (xs_list_iter(&p, &uid)) { 3183 while (xs_list_iter(&p, &uid)) {
@@ -2887,7 +3231,7 @@ void srv_archive(const char *direction, const char *url, xs_dict *req,
2887 if (p_size && payload) { 3231 if (p_size && payload) {
2888 xs *payload_fn = NULL; 3232 xs *payload_fn = NULL;
2889 xs *payload_fn_raw = NULL; 3233 xs *payload_fn_raw = NULL;
2890 char *v = xs_dict_get(req, "content-type"); 3234 const char *v = xs_dict_get(req, "content-type");
2891 3235
2892 if (v && xs_str_in(v, "json") != -1) { 3236 if (v && xs_str_in(v, "json") != -1) {
2893 payload_fn = xs_fmt("%s/payload.json", dir); 3237 payload_fn = xs_fmt("%s/payload.json", dir);
@@ -2918,7 +3262,7 @@ void srv_archive(const char *direction, const char *url, xs_dict *req,
2918 3262
2919 if (b_size && body) { 3263 if (b_size && body) {
2920 xs *body_fn = NULL; 3264 xs *body_fn = NULL;
2921 char *v = xs_dict_get(headers, "content-type"); 3265 const char *v = xs_dict_get(headers, "content-type");
2922 3266
2923 if (v && xs_str_in(v, "json") != -1) { 3267 if (v && xs_str_in(v, "json") != -1) {
2924 body_fn = xs_fmt("%s/body.json", dir); 3268 body_fn = xs_fmt("%s/body.json", dir);
@@ -2987,7 +3331,7 @@ void srv_archive_error(const char *prefix, const xs_str *err,
2987} 3331}
2988 3332
2989 3333
2990void srv_archive_qitem(char *prefix, xs_dict *q_item) 3334void srv_archive_qitem(const char *prefix, xs_dict *q_item)
2991/* archives a q_item in the error folder */ 3335/* archives a q_item in the error folder */
2992{ 3336{
2993 xs *ntid = tid(0); 3337 xs *ntid = tid(0);
diff --git a/doc/snac.8 b/doc/snac.8
index 4929a52..7c35aeb 100644
--- a/doc/snac.8
+++ b/doc/snac.8
@@ -143,6 +143,14 @@ times the sending will be retried.
143The number of minutes to wait before the failed posting of a message is 143The number of minutes to wait before the failed posting of a message is
144retried. This is not linear, but multipled by the number of retries 144retried. This is not linear, but multipled by the number of retries
145already done. 145already done.
146.It Ic queue_timeout
147The maximum number of seconds to wait when sending a message from the queue.
148.It Ic queue_timeout_2
149The maximum number of seconds to wait when sending a message from the queue
150to those servers that went timeout in the previous retry. If you want to
151give slow servers a chance to receive your messages, you can increase this
152value (but also take into account that processing the queue will take longer
153while waiting for these molasses to respond).
146.It Ic max_timeline_entries 154.It Ic max_timeline_entries
147This is the maximum timeline entries shown in the web interface. 155This is the maximum timeline entries shown in the web interface.
148.It Ic timeline_purge_days 156.It Ic timeline_purge_days
@@ -209,6 +217,13 @@ with a large number of users.
209If this numeric value (in seconds) is set, any activity coming from an account 217If this numeric value (in seconds) is set, any activity coming from an account
210that was created more recently than that will be rejected. This may be used 218that was created more recently than that will be rejected. This may be used
211to mitigate spam from automatically created accounts. 219to mitigate spam from automatically created accounts.
220.It Ic protocol
221This string value contains the protocol (schema) to be used in URLs. If not
222set, it defaults to "https". If you run
223.Nm
224as part of a hidden network like Tor or I2P that doesn't have a TLS /
225Certificate infrastructure, you need to set it to "http". Don't change it
226unless you know what you are doing.
212.El 227.El
213.Pp 228.Pp
214You must restart the server to make effective these changes. 229You must restart the server to make effective these changes.
diff --git a/format.c b/format.c
index 92901bb..b021f55 100644
--- a/format.c
+++ b/format.c
@@ -82,7 +82,8 @@ static xs_str *format_line(const char *line, xs_list **attach)
82/* formats a line */ 82/* formats a line */
83{ 83{
84 xs_str *s = xs_str_new(NULL); 84 xs_str *s = xs_str_new(NULL);
85 char *p, *v; 85 char *p;
86 const char *v;
86 87
87 /* split by markup */ 88 /* split by markup */
88 xs *sm = xs_regex_split(line, 89 xs *sm = xs_regex_split(line,
@@ -155,7 +156,8 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag
155 int in_pre = 0; 156 int in_pre = 0;
156 int in_blq = 0; 157 int in_blq = 0;
157 xs *list; 158 xs *list;
158 char *p, *v; 159 char *p;
160 const char *v;
159 161
160 /* work by lines */ 162 /* work by lines */
161 list = xs_split(content, "\n"); 163 list = xs_split(content, "\n");
@@ -234,14 +236,14 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag
234 /* traditional emoticons */ 236 /* traditional emoticons */
235 xs *d = emojis(); 237 xs *d = emojis();
236 int c = 0; 238 int c = 0;
237 char *k, *v; 239 const char *k, *v;
238 240
239 while (xs_dict_next(d, &k, &v, &c)) { 241 while (xs_dict_next(d, &k, &v, &c)) {
240 const char *t = NULL; 242 const char *t = NULL;
241 243
242 /* is it an URL to an image? */ 244 /* is it an URL to an image? */
243 if (xs_startswith(v, "https:/" "/") && xs_startswith((t = xs_mime_by_ext(v)), "image/")) { 245 if (xs_startswith(v, "https:/" "/") && xs_startswith((t = xs_mime_by_ext(v)), "image/")) {
244 if (tag) { 246 if (tag && xs_str_in(s, k) != -1) {
245 /* add the emoji to the tag list */ 247 /* add the emoji to the tag list */
246 xs *e = xs_dict_new(); 248 xs *e = xs_dict_new();
247 xs *i = xs_dict_new(); 249 xs *i = xs_dict_new();
@@ -280,7 +282,8 @@ xs_str *sanitize(const char *content)
280 xs_str *s = xs_str_new(NULL); 282 xs_str *s = xs_str_new(NULL);
281 xs *sl; 283 xs *sl;
282 int n = 0; 284 int n = 0;
283 char *p, *v; 285 char *p;
286 const char *v;
284 287
285 sl = xs_regex_split(content, "</?[^>]+>"); 288 sl = xs_regex_split(content, "</?[^>]+>");
286 289
@@ -311,9 +314,9 @@ xs_str *sanitize(const char *content)
311 314
312 s = xs_str_cat(s, s2); 315 s = xs_str_cat(s, s2);
313 } else { 316 } else {
314 /* else? just show it with encoded code.. that's it. */ 317 /* treat end of divs as paragraph breaks */
315 xs *el = encode_html(v); 318 if (strcmp(v, "</div>"))
316 s = xs_str_cat(s, el); 319 s = xs_str_cat(s, "<p>");
317 } 320 }
318 } 321 }
319 else { 322 else {
diff --git a/html.c b/html.c
index f50fb7d..f97c45d 100644
--- a/html.c
+++ b/html.c
@@ -41,7 +41,7 @@ int login(snac *snac, const xs_dict *headers)
41} 41}
42 42
43 43
44xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems) 44xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems)
45/* replaces all the :shortnames: with the emojis in tag */ 45/* replaces all the :shortnames: with the emojis in tag */
46{ 46{
47 if (!xs_is_null(tag)) { 47 if (!xs_is_null(tag)) {
@@ -55,20 +55,20 @@ xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems)
55 tag_list = xs_dup(tag); 55 tag_list = xs_dup(tag);
56 } 56 }
57 57
58 xs *style = xs_fmt("height: %dem; vertical-align: middle;", ems); 58 xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems);
59 59
60 xs_list *p = tag_list; 60 const char *v;
61 char *v; 61 int c = 0;
62 62
63 while (xs_list_iter(&p, &v)) { 63 while (xs_list_next(tag_list, &v, &c)) {
64 char *t = xs_dict_get(v, "type"); 64 const char *t = xs_dict_get(v, "type");
65 65
66 if (t && strcmp(t, "Emoji") == 0) { 66 if (t && strcmp(t, "Emoji") == 0) {
67 char *n = xs_dict_get(v, "name"); 67 const char *n = xs_dict_get(v, "name");
68 char *i = xs_dict_get(v, "icon"); 68 const char *i = xs_dict_get(v, "icon");
69 69
70 if (n && i) { 70 if (n && i) {
71 char *u = xs_dict_get(i, "url"); 71 const char *u = xs_dict_get(i, "url");
72 xs_html *img = xs_html_sctag("img", 72 xs_html *img = xs_html_sctag("img",
73 xs_html_attr("loading", "lazy"), 73 xs_html_attr("loading", "lazy"),
74 xs_html_attr("src", u), 74 xs_html_attr("src", u),
@@ -88,7 +88,7 @@ xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems)
88xs_str *actor_name(xs_dict *actor) 88xs_str *actor_name(xs_dict *actor)
89/* gets the actor name */ 89/* gets the actor name */
90{ 90{
91 char *v; 91 const char *v;
92 92
93 if (xs_is_null((v = xs_dict_get(actor, "name"))) || *v == '\0') { 93 if (xs_is_null((v = xs_dict_get(actor, "name"))) || *v == '\0') {
94 if (xs_is_null(v = xs_dict_get(actor, "preferredUsername")) || *v == '\0') { 94 if (xs_is_null(v = xs_dict_get(actor, "preferredUsername")) || *v == '\0') {
@@ -106,7 +106,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
106 xs_html *actor_icon = xs_html_tag("p", NULL); 106 xs_html *actor_icon = xs_html_tag("p", NULL);
107 107
108 xs *avatar = NULL; 108 xs *avatar = NULL;
109 char *v; 109 const char *v;
110 int fwing = 0; 110 int fwing = 0;
111 int fwer = 0; 111 int fwer = 0;
112 112
@@ -125,7 +125,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
125 if (avatar == NULL) 125 if (avatar == NULL)
126 avatar = xs_fmt("data:image/png;base64, %s", default_avatar_base64()); 126 avatar = xs_fmt("data:image/png;base64, %s", default_avatar_base64());
127 127
128 char *actor_id = xs_dict_get(actor, "id"); 128 const char *actor_id = xs_dict_get(actor, "id");
129 xs *href = NULL; 129 xs *href = NULL;
130 130
131 if (user) { 131 if (user) {
@@ -216,7 +216,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
216 } 216 }
217 217
218 { 218 {
219 char *username, *id; 219 const char *username, *id;
220 220
221 if (xs_is_null(username = xs_dict_get(actor, "preferredUsername")) || *username == '\0') { 221 if (xs_is_null(username = xs_dict_get(actor, "preferredUsername")) || *username == '\0') {
222 /* This should never be reached */ 222 /* This should never be reached */
@@ -244,19 +244,19 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
244} 244}
245 245
246 246
247xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg) 247xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg)
248{ 248{
249 xs *actor = NULL; 249 xs *actor = NULL;
250 xs_html *actor_icon = NULL; 250 xs_html *actor_icon = NULL;
251 251
252 if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) { 252 if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) {
253 char *date = NULL; 253 const char *date = NULL;
254 char *udate = NULL; 254 const char *udate = NULL;
255 char *url = NULL; 255 const char *url = NULL;
256 int priv = 0; 256 int priv = 0;
257 const char *type = xs_dict_get(msg, "type"); 257 const char *type = xs_dict_get(msg, "type");
258 258
259 if (xs_match(type, "Note|Question|Page|Article|Video")) 259 if (xs_match(type, POSTLIKE_OBJECT_TYPE))
260 url = xs_dict_get(msg, "id"); 260 url = xs_dict_get(msg, "id");
261 261
262 priv = !is_msg_public(msg); 262 priv = !is_msg_public(msg);
@@ -271,14 +271,14 @@ xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg)
271} 271}
272 272
273 273
274xs_html *html_note(snac *user, char *summary, 274xs_html *html_note(snac *user, const char *summary,
275 char *div_id, char *form_id, 275 const char *div_id, const char *form_id,
276 char *ta_plh, char *ta_content, 276 const char *ta_plh, const char *ta_content,
277 char *edit_id, char *actor_id, 277 const char *edit_id, const char *actor_id,
278 xs_val *cw_yn, char *cw_text, 278 const xs_val *cw_yn, const char *cw_text,
279 xs_val *mnt_only, char *redir, 279 const xs_val *mnt_only, const char *redir,
280 char *in_reply_to, int poll, 280 const char *in_reply_to, int poll,
281 char *att_file, char *att_alt_text) 281 const char *att_file, const char *att_alt_text)
282{ 282{
283 xs *action = xs_fmt("%s/admin/note", user->actor); 283 xs *action = xs_fmt("%s/admin/note", user->actor);
284 284
@@ -460,9 +460,11 @@ static xs_html *html_base_head(void)
460 /* add server CSS and favicon */ 460 /* add server CSS and favicon */
461 xs *f; 461 xs *f;
462 f = xs_fmt("%s/favicon.ico", srv_baseurl); 462 f = xs_fmt("%s/favicon.ico", srv_baseurl);
463 xs_list *p = xs_dict_get(srv_config, "cssurls"); 463 const xs_list *p = xs_dict_get(srv_config, "cssurls");
464 char *v; 464 const char *v;
465 while (xs_list_iter(&p, &v)) { 465 int c = 0;
466
467 while (xs_list_next(p, &v, &c)) {
466 xs_html_add(head, 468 xs_html_add(head,
467 xs_html_sctag("link", 469 xs_html_sctag("link",
468 xs_html_attr("rel", "stylesheet"), 470 xs_html_attr("rel", "stylesheet"),
@@ -498,8 +500,8 @@ xs_html *html_instance_head(void)
498 } 500 }
499 } 501 }
500 502
501 char *host = xs_dict_get(srv_config, "host"); 503 const char *host = xs_dict_get(srv_config, "host");
502 char *title = xs_dict_get(srv_config, "title"); 504 const char *title = xs_dict_get(srv_config, "title");
503 505
504 xs_html_add(head, 506 xs_html_add(head,
505 xs_html_tag("title", 507 xs_html_tag("title",
@@ -509,12 +511,12 @@ xs_html *html_instance_head(void)
509} 511}
510 512
511 513
512static xs_html *html_instance_body(char *tag) 514static xs_html *html_instance_body(void)
513{ 515{
514 char *host = xs_dict_get(srv_config, "host"); 516 const char *host = xs_dict_get(srv_config, "host");
515 char *sdesc = xs_dict_get(srv_config, "short_description"); 517 const char *sdesc = xs_dict_get(srv_config, "short_description");
516 char *email = xs_dict_get(srv_config, "admin_email"); 518 const char *email = xs_dict_get(srv_config, "admin_email");
517 char *acct = xs_dict_get(srv_config, "admin_account"); 519 const char *acct = xs_dict_get(srv_config, "admin_account");
518 520
519 xs *blurb = xs_replace(snac_blurb, "%host%", host); 521 xs *blurb = xs_replace(snac_blurb, "%host%", host);
520 522
@@ -560,16 +562,6 @@ static xs_html *html_instance_body(char *tag)
560 xs_html_text(handle))))); 562 xs_html_text(handle)))));
561 } 563 }
562 564
563 {
564 xs *l = tag ? xs_fmt(L("Search results for #%s"), tag) :
565 xs_dup(L("Recent posts by users in this instance"));
566
567 xs_html_add(body,
568 xs_html_tag("h2",
569 xs_html_attr("class", "snac-header"),
570 xs_html_text(l)));
571 }
572
573 return body; 565 return body;
574} 566}
575 567
@@ -749,7 +741,17 @@ static xs_html *html_user_body(snac *user, int read_only)
749 xs_html_text(" - "), 741 xs_html_text(" - "),
750 xs_html_tag("a", 742 xs_html_tag("a",
751 xs_html_attr("href", instance_url), 743 xs_html_attr("href", instance_url),
752 xs_html_text(L("instance")))); 744 xs_html_text(L("instance"))),
745 xs_html_text(" "),
746 xs_html_tag("form",
747 xs_html_attr("style", "display: inline!important"),
748 xs_html_attr("class", "snac-search-box"),
749 xs_html_attr("action", admin_url),
750 xs_html_sctag("input",
751 xs_html_attr("type", "text"),
752 xs_html_attr("name", "q"),
753 xs_html_attr("title", L("Search posts by content (regular expression) or #tag")),
754 xs_html_attr("placeholder", L("Content search")))));
753 } 755 }
754 756
755 xs_html_add(body, 757 xs_html_add(body,
@@ -760,7 +762,7 @@ static xs_html *html_user_body(snac *user, int read_only)
760 xs_html_attr("class", "h-card snac-top-user")); 762 xs_html_attr("class", "h-card snac-top-user"));
761 763
762 if (read_only) { 764 if (read_only) {
763 char *header = xs_dict_get(user->config, "header"); 765 const char *header = xs_dict_get(user->config, "header");
764 if (header && *header) { 766 if (header && *header) {
765 xs_html_add(top_user, 767 xs_html_add(top_user,
766 xs_html_tag("div", 768 xs_html_tag("div",
@@ -797,10 +799,10 @@ static xs_html *html_user_body(snac *user, int read_only)
797 xs_html_add(top_user, 799 xs_html_add(top_user,
798 top_user_bio); 800 top_user_bio);
799 801
800 xs_dict *metadata = xs_dict_get(user->config, "metadata"); 802 const xs_dict *metadata = xs_dict_get(user->config, "metadata");
801 if (xs_type(metadata) == XSTYPE_DICT) { 803 if (xs_type(metadata) == XSTYPE_DICT) {
802 xs_str *k; 804 const xs_str *k;
803 xs_str *v; 805 const xs_str *v;
804 806
805 xs_dict *val_links = user->links; 807 xs_dict *val_links = user->links;
806 if (xs_is_null(val_links)) 808 if (xs_is_null(val_links))
@@ -813,10 +815,10 @@ static xs_html *html_user_body(snac *user, int read_only)
813 while (xs_dict_next(metadata, &k, &v, &c)) { 815 while (xs_dict_next(metadata, &k, &v, &c)) {
814 xs_html *value; 816 xs_html *value;
815 817
816 if (xs_startswith(v, "https:/" "/")) { 818 if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/")) {
817 /* is this link validated? */ 819 /* is this link validated? */
818 xs *verified_link = NULL; 820 xs *verified_link = NULL;
819 xs_number *val_time = xs_dict_get(val_links, v); 821 const xs_number *val_time = xs_dict_get(val_links, v);
820 822
821 if (xs_type(val_time) == XSTYPE_NUMBER) { 823 if (xs_type(val_time) == XSTYPE_NUMBER) {
822 time_t t = xs_number_get(val_time); 824 time_t t = xs_number_get(val_time);
@@ -928,7 +930,7 @@ xs_html *html_top_controls(snac *snac)
928 930
929 /** user settings **/ 931 /** user settings **/
930 932
931 char *email = "[disabled by admin]"; 933 const char *email = "[disabled by admin]";
932 934
933 if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) { 935 if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) {
934 email = xs_dict_get(snac->config_o, "email"); 936 email = xs_dict_get(snac->config_o, "email");
@@ -940,40 +942,40 @@ xs_html *html_top_controls(snac *snac)
940 } 942 }
941 } 943 }
942 944
943 char *cw = xs_dict_get(snac->config, "cw"); 945 const char *cw = xs_dict_get(snac->config, "cw");
944 if (xs_is_null(cw)) 946 if (xs_is_null(cw))
945 cw = ""; 947 cw = "";
946 948
947 char *telegram_bot = xs_dict_get(snac->config, "telegram_bot"); 949 const char *telegram_bot = xs_dict_get(snac->config, "telegram_bot");
948 if (xs_is_null(telegram_bot)) 950 if (xs_is_null(telegram_bot))
949 telegram_bot = ""; 951 telegram_bot = "";
950 952
951 char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id"); 953 const char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id");
952 if (xs_is_null(telegram_chat_id)) 954 if (xs_is_null(telegram_chat_id))
953 telegram_chat_id = ""; 955 telegram_chat_id = "";
954 956
955 char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); 957 const char *ntfy_server = xs_dict_get(snac->config, "ntfy_server");
956 if (xs_is_null(ntfy_server)) 958 if (xs_is_null(ntfy_server))
957 ntfy_server = ""; 959 ntfy_server = "";
958 960
959 char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); 961 const char *ntfy_token = xs_dict_get(snac->config, "ntfy_token");
960 if (xs_is_null(ntfy_token)) 962 if (xs_is_null(ntfy_token))
961 ntfy_token = ""; 963 ntfy_token = "";
962 964
963 char *purge_days = xs_dict_get(snac->config, "purge_days"); 965 const char *purge_days = xs_dict_get(snac->config, "purge_days");
964 if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER) 966 if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER)
965 purge_days = (char *)xs_number_str(purge_days); 967 purge_days = (char *)xs_number_str(purge_days);
966 else 968 else
967 purge_days = "0"; 969 purge_days = "0";
968 970
969 xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown"); 971 const xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown");
970 xs_val *bot = xs_dict_get(snac->config, "bot"); 972 const xs_val *bot = xs_dict_get(snac->config, "bot");
971 xs_val *a_private = xs_dict_get(snac->config, "private"); 973 const xs_val *a_private = xs_dict_get(snac->config, "private");
972 974
973 xs *metadata = xs_str_new(NULL); 975 xs *metadata = xs_str_new(NULL);
974 xs_dict *md = xs_dict_get(snac->config, "metadata"); 976 const xs_dict *md = xs_dict_get(snac->config, "metadata");
975 xs_str *k; 977 const xs_str *k;
976 xs_str *v; 978 const xs_str *v;
977 979
978 int c = 0; 980 int c = 0;
979 while (xs_dict_next(md, &k, &v, &c)) { 981 while (xs_dict_next(md, &k, &v, &c)) {
@@ -1158,13 +1160,14 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg)
1158/* returns a string with the mentions in msg */ 1160/* returns a string with the mentions in msg */
1159{ 1161{
1160 xs_str *s = xs_str_new(NULL); 1162 xs_str *s = xs_str_new(NULL);
1161 char *list = xs_dict_get(msg, "tag"); 1163 const char *list = xs_dict_get(msg, "tag");
1162 char *v; 1164 const char *v;
1165 int c = 0;
1163 1166
1164 while (xs_list_iter(&list, &v)) { 1167 while (xs_list_next(list, &v, &c)) {
1165 char *type = xs_dict_get(v, "type"); 1168 const char *type = xs_dict_get(v, "type");
1166 char *href = xs_dict_get(v, "href"); 1169 const char *href = xs_dict_get(v, "href");
1167 char *name = xs_dict_get(v, "name"); 1170 const char *name = xs_dict_get(v, "name");
1168 1171
1169 if (type && strcmp(type, "Mention") == 0 && 1172 if (type && strcmp(type, "Mention") == 0 &&
1170 href && strcmp(href, snac->actor) != 0 && name) { 1173 href && strcmp(href, snac->actor) != 0 && name) {
@@ -1208,10 +1211,11 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg)
1208} 1211}
1209 1212
1210 1213
1211xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const char *md5) 1214xs_html *html_entry_controls(snac *snac, const char *actor,
1215 const xs_dict *msg, const char *md5)
1212{ 1216{
1213 char *id = xs_dict_get(msg, "id"); 1217 const char *id = xs_dict_get(msg, "id");
1214 char *group = xs_dict_get(msg, "audience"); 1218 const char *group = xs_dict_get(msg, "audience");
1215 1219
1216 xs *likes = object_likes(id); 1220 xs *likes = object_likes(id);
1217 xs *boosts = object_announces(id); 1221 xs *boosts = object_announces(id);
@@ -1265,8 +1269,8 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1265 } 1269 }
1266 1270
1267 if (is_msg_public(msg)) { 1271 if (is_msg_public(msg)) {
1268 if (strcmp(actor, snac->actor) == 0 || xs_list_in(boosts, snac->md5) == -1) { 1272 if (xs_list_in(boosts, snac->md5) == -1) {
1269 /* not already boosted or us; add button */ 1273 /* not already boosted; add button */
1270 xs_html_add(form, 1274 xs_html_add(form,
1271 html_button("boost", L("Boost"), L("Announce this post to your followers"))); 1275 html_button("boost", L("Boost"), L("Announce this post to your followers")));
1272 } 1276 }
@@ -1310,7 +1314,7 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1310 html_button("delete", L("Delete"), L("Delete this post")), 1314 html_button("delete", L("Delete"), L("Delete this post")),
1311 html_button("hide", L("Hide"), L("Hide this post and its children"))); 1315 html_button("hide", L("Hide"), L("Hide this post and its children")));
1312 1316
1313 char *prev_src = xs_dict_get(msg, "sourceContent"); 1317 const char *prev_src = xs_dict_get(msg, "sourceContent");
1314 1318
1315 if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/ 1319 if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/
1316 /* post can be edited */ 1320 /* post can be edited */
@@ -1318,13 +1322,13 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1318 xs *form_id = xs_fmt("%s_edit_form", md5); 1322 xs *form_id = xs_fmt("%s_edit_form", md5);
1319 xs *redir = xs_fmt("%s_entry", md5); 1323 xs *redir = xs_fmt("%s_entry", md5);
1320 1324
1321 char *att_file = ""; 1325 const char *att_file = "";
1322 char *att_alt_text = ""; 1326 const char *att_alt_text = "";
1323 xs_list *att_list = xs_dict_get(msg, "attachment"); 1327 const xs_list *att_list = xs_dict_get(msg, "attachment");
1324 1328
1325 /* does it have an attachment? */ 1329 /* does it have an attachment? */
1326 if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) { 1330 if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) {
1327 xs_dict *d = xs_list_get(att_list, 0); 1331 const xs_dict *d = xs_list_get(att_list, 0);
1328 1332
1329 if (xs_type(d) == XSTYPE_DICT) { 1333 if (xs_type(d) == XSTYPE_DICT) {
1330 att_file = xs_dict_get_def(d, "url", ""); 1334 att_file = xs_dict_get_def(d, "url", "");
@@ -1368,12 +1372,13 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1368 1372
1369 1373
1370xs_html *html_entry(snac *user, xs_dict *msg, int read_only, 1374xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1371 int level, char *md5, int hide_children) 1375 int level, const char *md5, int hide_children)
1372{ 1376{
1373 char *id = xs_dict_get(msg, "id"); 1377 const char *id = xs_dict_get(msg, "id");
1374 char *type = xs_dict_get(msg, "type"); 1378 const char *type = xs_dict_get(msg, "type");
1375 char *actor; 1379 const char *actor;
1376 char *v; 1380 const char *v;
1381 int has_title = 0;
1377 1382
1378 /* do not show non-public messages in the public timeline */ 1383 /* do not show non-public messages in the public timeline */
1379 if ((read_only || !user) && !is_msg_public(msg)) 1384 if ((read_only || !user) && !is_msg_public(msg))
@@ -1405,8 +1410,9 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1405 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg))); 1410 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg)));
1406 } 1411 }
1407 else 1412 else
1408 if (!xs_match(type, "Note|Question|Page|Article|Video")) { 1413 if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) {
1409 /* skip oddities */ 1414 /* skip oddities */
1415 snac_debug(user, 1, xs_fmt("html_entry: ignoring object type '%s' %s", type, id));
1410 return NULL; 1416 return NULL;
1411 } 1417 }
1412 1418
@@ -1483,6 +1489,14 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1483 } 1489 }
1484 } 1490 }
1485 1491
1492 if (strcmp(type, "Event") == 0) {
1493 /* add the calendar emoji */
1494 xs_html_add(score,
1495 xs_html_tag("span",
1496 xs_html_attr("title", L("Event")),
1497 xs_html_raw(" &#128197; ")));
1498 }
1499
1486 /* if it's a user from this same instance, add the score */ 1500 /* if it's a user from this same instance, add the score */
1487 if (xs_startswith(id, srv_baseurl)) { 1501 if (xs_startswith(id, srv_baseurl)) {
1488 int n_likes = object_likes_len(id); 1502 int n_likes = object_likes_len(id);
@@ -1499,7 +1513,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1499 1513
1500 if (xs_list_len(boosts)) { 1514 if (xs_list_len(boosts)) {
1501 /* if somebody boosted this, show as origin */ 1515 /* if somebody boosted this, show as origin */
1502 char *p = xs_list_get(boosts, -1); 1516 const char *p = xs_list_get(boosts, -1);
1503 xs *actor_r = NULL; 1517 xs *actor_r = NULL;
1504 1518
1505 if (user && xs_list_in(boosts, user->md5) != -1) { 1519 if (user && xs_list_in(boosts, user->md5) != -1) {
@@ -1519,7 +1533,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1519 1533
1520 if (!xs_is_null(name)) { 1534 if (!xs_is_null(name)) {
1521 xs *href = NULL; 1535 xs *href = NULL;
1522 char *id = xs_dict_get(actor_r, "id"); 1536 const char *id = xs_dict_get(actor_r, "id");
1523 int fwers = 0; 1537 int fwers = 0;
1524 int fwing = 0; 1538 int fwing = 0;
1525 1539
@@ -1548,7 +1562,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1548 if (strcmp(type, "Note") == 0) { 1562 if (strcmp(type, "Note") == 0) {
1549 if (level == 0) { 1563 if (level == 0) {
1550 /* is the parent not here? */ 1564 /* is the parent not here? */
1551 char *parent = xs_dict_get(msg, "inReplyTo"); 1565 const char *parent = xs_dict_get(msg, "inReplyTo");
1552 1566
1553 if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { 1567 if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) {
1554 xs_html_add(post_header, 1568 xs_html_add(post_header,
@@ -1574,11 +1588,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1574 xs_html_add(entry, 1588 xs_html_add(entry,
1575 snac_content_wrap); 1589 snac_content_wrap);
1576 1590
1577 if (!xs_is_null(v = xs_dict_get(msg, "name"))) { 1591 if (!has_title && !xs_is_null(v = xs_dict_get(msg, "name"))) {
1578 xs_html_add(snac_content_wrap, 1592 xs_html_add(snac_content_wrap,
1579 xs_html_tag("h3", 1593 xs_html_tag("h3",
1580 xs_html_attr("class", "snac-entry-title"), 1594 xs_html_attr("class", "snac-entry-title"),
1581 xs_html_text(v))); 1595 xs_html_text(v)));
1596
1597 has_title = 1;
1582 } 1598 }
1583 1599
1584 xs_html *snac_content = NULL; 1600 xs_html *snac_content = NULL;
@@ -1591,7 +1607,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1591 v = "..."; 1607 v = "...";
1592 1608
1593 /* only show it when not in the public timeline and the config setting is "open" */ 1609 /* only show it when not in the public timeline and the config setting is "open" */
1594 char *cw = xs_dict_get(user->config, "cw"); 1610 const char *cw = xs_dict_get(user->config, "cw");
1595 if (xs_is_null(cw) || read_only) 1611 if (xs_is_null(cw) || read_only)
1596 cw = ""; 1612 cw = "";
1597 1613
@@ -1603,12 +1619,15 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1603 } 1619 }
1604 else { 1620 else {
1605 /* print the summary as a header (sites like e.g. Friendica can contain one) */ 1621 /* print the summary as a header (sites like e.g. Friendica can contain one) */
1606 if (!xs_is_null(v) && *v) 1622 if (!has_title && !xs_is_null(v) && *v) {
1607 xs_html_add(snac_content_wrap, 1623 xs_html_add(snac_content_wrap,
1608 xs_html_tag("h3", 1624 xs_html_tag("h3",
1609 xs_html_attr("class", "snac-entry-title"), 1625 xs_html_attr("class", "snac-entry-title"),
1610 xs_html_text(v))); 1626 xs_html_text(v)));
1611 1627
1628 has_title = 1;
1629 }
1630
1612 snac_content = xs_html_tag("div", NULL); 1631 snac_content = xs_html_tag("div", NULL);
1613 } 1632 }
1614 1633
@@ -1617,7 +1636,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1617 1636
1618 { 1637 {
1619 /** build the content string **/ 1638 /** build the content string **/
1620 char *content = xs_dict_get(msg, "content"); 1639 const char *content = xs_dict_get(msg, "content");
1621 1640
1622 xs *c = sanitize(xs_is_null(content) ? "" : content); 1641 xs *c = sanitize(xs_is_null(content) ? "" : content);
1623 1642
@@ -1635,7 +1654,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1635 c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2); 1654 c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2);
1636 1655
1637 /* Peertube videos content is in markdown */ 1656 /* Peertube videos content is in markdown */
1638 char *mtype = xs_dict_get(msg, "mediaType"); 1657 const char *mtype = xs_dict_get(msg, "mediaType");
1639 if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "text/markdown") == 0) { 1658 if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "text/markdown") == 0) {
1640 /* a full conversion could be better */ 1659 /* a full conversion could be better */
1641 c = xs_replace_i(c, "\r", ""); 1660 c = xs_replace_i(c, "\r", "");
@@ -1648,26 +1667,33 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1648 } 1667 }
1649 1668
1650 if (strcmp(type, "Question") == 0) { /** question content **/ 1669 if (strcmp(type, "Question") == 0) { /** question content **/
1651 xs_list *oo = xs_dict_get(msg, "oneOf"); 1670 const xs_list *oo = xs_dict_get(msg, "oneOf");
1652 xs_list *ao = xs_dict_get(msg, "anyOf"); 1671 const xs_list *ao = xs_dict_get(msg, "anyOf");
1653 xs_list *p; 1672 const xs_list *p;
1654 xs_dict *v; 1673 const xs_dict *v;
1655 int closed = 0; 1674 int closed = 0;
1675 const char *f_closed = NULL;
1656 1676
1657 xs_html *poll = xs_html_tag("div", NULL); 1677 xs_html *poll = xs_html_tag("div", NULL);
1658 1678
1659 if (read_only) 1679 if (read_only)
1660 closed = 1; /* non-identified page; show as closed */ 1680 closed = 1; /* non-identified page; show as closed */
1661 else 1681 else
1662 if (xs_dict_get(msg, "closed"))
1663 closed = 2;
1664 else
1665 if (user && xs_startswith(id, user->actor)) 1682 if (user && xs_startswith(id, user->actor))
1666 closed = 1; /* we questioned; closed for us */ 1683 closed = 1; /* we questioned; closed for us */
1667 else 1684 else
1668 if (user && was_question_voted(user, id)) 1685 if (user && was_question_voted(user, id))
1669 closed = 1; /* we already voted; closed for us */ 1686 closed = 1; /* we already voted; closed for us */
1670 1687
1688 if ((f_closed = xs_dict_get(msg, "closed")) != NULL) {
1689 /* it has a closed date... but is it in the past? */
1690 time_t t0 = time(NULL);
1691 time_t t1 = xs_parse_iso_date(f_closed, 0);
1692
1693 if (t1 < t0)
1694 closed = 2;
1695 }
1696
1671 /* get the appropriate list of options */ 1697 /* get the appropriate list of options */
1672 p = oo != NULL ? oo : ao; 1698 p = oo != NULL ? oo : ao;
1673 1699
@@ -1675,10 +1701,11 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1675 /* closed poll */ 1701 /* closed poll */
1676 xs_html *poll_result = xs_html_tag("table", 1702 xs_html *poll_result = xs_html_tag("table",
1677 xs_html_attr("class", "snac-poll-result")); 1703 xs_html_attr("class", "snac-poll-result"));
1704 int c = 0;
1678 1705
1679 while (xs_list_iter(&p, &v)) { 1706 while (xs_list_next(p, &v, &c)) {
1680 char *name = xs_dict_get(v, "name"); 1707 const char *name = xs_dict_get(v, "name");
1681 xs_dict *replies = xs_dict_get(v, "replies"); 1708 const xs_dict *replies = xs_dict_get(v, "replies");
1682 1709
1683 if (name && replies) { 1710 if (name && replies) {
1684 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); 1711 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems"));
@@ -1715,9 +1742,10 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1715 xs_html_attr("name", "irt"), 1742 xs_html_attr("name", "irt"),
1716 xs_html_attr("value", id)))); 1743 xs_html_attr("value", id))));
1717 1744
1718 while (xs_list_iter(&p, &v)) { 1745 int c = 0;
1719 char *name = xs_dict_get(v, "name"); 1746 while (xs_list_next(p, &v, &c)) {
1720 xs_dict *replies = xs_dict_get(v, "replies"); 1747 const char *name = xs_dict_get(v, "name");
1748 const xs_dict *replies = xs_dict_get(v, "replies");
1721 1749
1722 if (name) { 1750 if (name) {
1723 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); 1751 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems"));
@@ -1755,7 +1783,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1755 } 1783 }
1756 else { 1784 else {
1757 /* show when the poll closes */ 1785 /* show when the poll closes */
1758 char *end_time = xs_dict_get(msg, "endTime"); 1786 const char *end_time = xs_dict_get(msg, "endTime");
1787
1788 /* Pleroma does not have an endTime field;
1789 it has a closed time in the future */
1790 if (xs_is_null(end_time))
1791 end_time = xs_dict_get(msg, "closed");
1792
1759 if (!xs_is_null(end_time)) { 1793 if (!xs_is_null(end_time)) {
1760 time_t t0 = time(NULL); 1794 time_t t0 = time(NULL);
1761 time_t t1 = xs_parse_iso_date(end_time, 0); 1795 time_t t1 = xs_parse_iso_date(end_time, 0);
@@ -1792,12 +1826,12 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1792 xs_html_add(snac_content, 1826 xs_html_add(snac_content,
1793 content_attachments); 1827 content_attachments);
1794 1828
1795 xs_list *p = attach; 1829 int c = 0;
1796 1830 const xs_dict *a;
1797 while (xs_list_iter(&p, &v)) { 1831 while (xs_list_next(attach, &a, &c)) {
1798 char *type = xs_dict_get(v, "type"); 1832 const char *type = xs_dict_get(a, "type");
1799 char *href = xs_dict_get(v, "href"); 1833 const char *href = xs_dict_get(a, "href");
1800 char *name = xs_dict_get(v, "name"); 1834 const char *name = xs_dict_get(a, "name");
1801 1835
1802 if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) { 1836 if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) {
1803 xs_html_add(content_attachments, 1837 xs_html_add(content_attachments,
@@ -1861,7 +1895,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1861 } 1895 }
1862 1896
1863 /* has this message an audience (i.e., comes from a channel or community)? */ 1897 /* has this message an audience (i.e., comes from a channel or community)? */
1864 char *audience = xs_dict_get(msg, "audience"); 1898 const char *audience = xs_dict_get(msg, "audience");
1865 if (strcmp(type, "Page") == 0 && !xs_is_null(audience)) { 1899 if (strcmp(type, "Page") == 0 && !xs_is_null(audience)) {
1866 xs_html *au_tag = xs_html_tag("p", 1900 xs_html *au_tag = xs_html_tag("p",
1867 xs_html_text("("), 1901 xs_html_text("("),
@@ -1911,7 +1945,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1911 } 1945 }
1912 1946
1913 xs_list *p = children; 1947 xs_list *p = children;
1914 char *cmd5; 1948 const char *cmd5;
1915 int cnt = 0; 1949 int cnt = 0;
1916 int o_cnt = 0; 1950 int o_cnt = 0;
1917 1951
@@ -1983,11 +2017,11 @@ xs_html *html_footer(void)
1983 2017
1984xs_str *html_timeline(snac *user, const xs_list *list, int read_only, 2018xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
1985 int skip, int show, int show_more, 2019 int skip, int show, int show_more,
1986 char *tag, char *page, int utl) 2020 char *title, char *page, int utl)
1987/* returns the HTML for the timeline */ 2021/* returns the HTML for the timeline */
1988{ 2022{
1989 xs_list *p = (xs_list *)list; 2023 xs_list *p = (xs_list *)list;
1990 char *v; 2024 const char *v;
1991 double t = ftime(); 2025 double t = ftime();
1992 2026
1993 xs *desc = NULL; 2027 xs *desc = NULL;
@@ -1995,11 +2029,12 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
1995 2029
1996 if (xs_list_len(list) == 1) { 2030 if (xs_list_len(list) == 1) {
1997 /* only one element? pick the description from the source */ 2031 /* only one element? pick the description from the source */
1998 char *id = xs_list_get(list, 0); 2032 const char *id = xs_list_get(list, 0);
1999 xs *d = NULL; 2033 xs *d = NULL;
2000 object_get_by_md5(id, &d); 2034 object_get_by_md5(id, &d);
2001 if (d && (v = xs_dict_get(d, "sourceContent")) != NULL) 2035 const char *sc = xs_dict_get(d, "sourceContent");
2002 desc = xs_dup(v); 2036 if (d && sc != NULL)
2037 desc = xs_dup(sc);
2003 2038
2004 alternate = xs_dup(xs_dict_get(d, "id")); 2039 alternate = xs_dup(xs_dict_get(d, "id"));
2005 } 2040 }
@@ -2013,7 +2048,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2013 } 2048 }
2014 else { 2049 else {
2015 head = html_instance_head(); 2050 head = html_instance_head();
2016 body = html_instance_body(tag); 2051 body = html_instance_body();
2017 } 2052 }
2018 2053
2019 xs_html *html = xs_html_tag("html", 2054 xs_html *html = xs_html_tag("html",
@@ -2024,6 +2059,41 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2024 xs_html_add(body, 2059 xs_html_add(body,
2025 html_top_controls(user)); 2060 html_top_controls(user));
2026 2061
2062 /* show links to the available lists */
2063 if (user && !read_only) {
2064 xs *lists = list_maint(user, NULL, 0); /* get list of lists */
2065
2066 if (xs_list_len(lists)) {
2067 int ct = 0;
2068 const char *v;
2069
2070 xs_html *lol = xs_html_tag("ul",
2071 xs_html_attr("class", "snac-list-of-lists"));
2072 xs_html_add(body, lol);
2073
2074 while (xs_list_next(lists, &v, &ct)) {
2075 const char *lname = xs_list_get(v, 1);
2076 xs *url = xs_fmt("%s/list/%s", user->actor, xs_list_get(v, 0));
2077 xs *ttl = xs_fmt(L("Timeline for list '%s'"), lname);
2078
2079 xs_html_add(lol,
2080 xs_html_tag("li",
2081 xs_html_tag("a",
2082 xs_html_attr("href", url),
2083 xs_html_attr("class", "snac-list-link"),
2084 xs_html_attr("title", ttl),
2085 xs_html_text(lname))));
2086 }
2087 }
2088 }
2089
2090 if (title) {
2091 xs_html_add(body,
2092 xs_html_tag("h2",
2093 xs_html_attr("class", "snac-header"),
2094 xs_html_text(title)));
2095 }
2096
2027 xs_html_add(body, 2097 xs_html_add(body,
2028 xs_html_tag("a", 2098 xs_html_tag("a",
2029 xs_html_attr("name", "snac-posts"))); 2099 xs_html_attr("name", "snac-posts")));
@@ -2052,11 +2122,18 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2052 2122
2053 /* is this message a non-public reply? */ 2123 /* is this message a non-public reply? */
2054 if (user != NULL && !is_msg_public(msg)) { 2124 if (user != NULL && !is_msg_public(msg)) {
2055 char *irt = xs_dict_get(msg, "inReplyTo"); 2125 const char *irt = xs_dict_get(msg, "inReplyTo");
2056 2126
2127 /* is it a reply to something not in the storage? */
2057 if (!xs_is_null(irt) && !object_here(irt)) { 2128 if (!xs_is_null(irt) && !object_here(irt)) {
2058 snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v)); 2129 /* is it for me? */
2059 continue; 2130 const xs_list *to = xs_dict_get_def(msg, "to", xs_stock(XSTYPE_LIST));
2131 const xs_list *cc = xs_dict_get_def(msg, "cc", xs_stock(XSTYPE_LIST));
2132
2133 if (xs_list_in(to, user->actor) == -1 && xs_list_in(cc, user->actor) == -1) {
2134 snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v));
2135 continue;
2136 }
2060 } 2137 }
2061 } 2138 }
2062 2139
@@ -2081,7 +2158,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2081 2158
2082 xs *list = history_list(user); 2159 xs *list = history_list(user);
2083 xs_list *p = list; 2160 xs_list *p = list;
2084 char *v; 2161 const char *v;
2085 2162
2086 while (xs_list_iter(&p, &v)) { 2163 while (xs_list_iter(&p, &v)) {
2087 xs *fn = xs_replace(v, ".html", ""); 2164 xs *fn = xs_replace(v, ".html", "");
@@ -2106,25 +2183,22 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2106 } 2183 }
2107 2184
2108 if (show_more) { 2185 if (show_more) {
2109 xs *t = NULL;
2110 xs *m = NULL; 2186 xs *m = NULL;
2111 xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); 2187 xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show);
2112 2188
2113 xs *url = page == NULL || user == NULL ? 2189 xs *url = xs_dup(user == NULL ? srv_baseurl : user->actor);
2114 xs_dup(srv_baseurl) : xs_fmt("%s%s", user->actor, page);
2115 2190
2116 if (tag) { 2191 if (page != NULL)
2117 t = xs_fmt("%s?t=%s", url, tag); 2192 url = xs_str_cat(url, page);
2118 m = xs_fmt("%s&%s", t, ss); 2193
2119 } 2194 if (xs_str_in(url, "?") != -1)
2120 else { 2195 m = xs_fmt("%s&%s", url, ss);
2121 t = xs_dup(url); 2196 else
2122 m = xs_fmt("%s?%s", t, ss); 2197 m = xs_fmt("%s?%s", url, ss);
2123 }
2124 2198
2125 xs_html *more_links = xs_html_tag("p", 2199 xs_html *more_links = xs_html_tag("p",
2126 xs_html_tag("a", 2200 xs_html_tag("a",
2127 xs_html_attr("href", t), 2201 xs_html_attr("href", url),
2128 xs_html_attr("name", "snac-more"), 2202 xs_html_attr("name", "snac-more"),
2129 xs_html_text(L("Back to top"))), 2203 xs_html_text(L("Back to top"))),
2130 xs_html_text(" - "), 2204 xs_html_text(" - "),
@@ -2157,7 +2231,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t)
2157 xs_html_text("...")))); 2231 xs_html_text("..."))));
2158 2232
2159 xs_list *p = list; 2233 xs_list *p = list;
2160 char *actor_id; 2234 const char *actor_id;
2161 2235
2162 while (xs_list_iter(&p, &actor_id)) { 2236 while (xs_list_iter(&p, &actor_id)) {
2163 xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); 2237 xs *md5 = xs_md5_hex(actor_id, strlen(actor_id));
@@ -2173,7 +2247,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t)
2173 html_actor_icon(snac, actor, xs_dict_get(actor, "published"), NULL, NULL, 0, 1))); 2247 html_actor_icon(snac, actor, xs_dict_get(actor, "published"), NULL, NULL, 0, 1)));
2174 2248
2175 /* content (user bio) */ 2249 /* content (user bio) */
2176 char *c = xs_dict_get(actor, "summary"); 2250 const char *c = xs_dict_get(actor, "summary");
2177 2251
2178 if (!xs_is_null(c)) { 2252 if (!xs_is_null(c)) {
2179 xs *sc = sanitize(c); 2253 xs *sc = sanitize(c);
@@ -2317,7 +2391,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
2317 xs_html *noti_seen = NULL; 2391 xs_html *noti_seen = NULL;
2318 2392
2319 xs_list *p = n_list; 2393 xs_list *p = n_list;
2320 xs_str *v; 2394 const xs_str *v;
2321 while (xs_list_iter(&p, &v)) { 2395 while (xs_list_iter(&p, &v)) {
2322 xs *noti = notify_get(user, v); 2396 xs *noti = notify_get(user, v);
2323 2397
@@ -2325,10 +2399,10 @@ xs_str *html_notifications(snac *user, int skip, int show)
2325 continue; 2399 continue;
2326 2400
2327 xs *obj = NULL; 2401 xs *obj = NULL;
2328 char *type = xs_dict_get(noti, "type"); 2402 const char *type = xs_dict_get(noti, "type");
2329 char *utype = xs_dict_get(noti, "utype"); 2403 const char *utype = xs_dict_get(noti, "utype");
2330 char *id = xs_dict_get(noti, "objid"); 2404 const char *id = xs_dict_get(noti, "objid");
2331 char *date = xs_dict_get(noti, "date"); 2405 const char *date = xs_dict_get(noti, "date");
2332 2406
2333 if (xs_is_null(id) || !valid_status(object_get(id, &obj))) 2407 if (xs_is_null(id) || !valid_status(object_get(id, &obj)))
2334 continue; 2408 continue;
@@ -2336,14 +2410,14 @@ xs_str *html_notifications(snac *user, int skip, int show)
2336 if (is_hidden(user, id)) 2410 if (is_hidden(user, id))
2337 continue; 2411 continue;
2338 2412
2339 char *actor_id = xs_dict_get(noti, "actor"); 2413 const char *actor_id = xs_dict_get(noti, "actor");
2340 xs *actor = NULL; 2414 xs *actor = NULL;
2341 2415
2342 if (!valid_status(actor_get(actor_id, &actor))) 2416 if (!valid_status(actor_get(actor_id, &actor)))
2343 continue; 2417 continue;
2344 2418
2345 xs *a_name = actor_name(actor); 2419 xs *a_name = actor_name(actor);
2346 char *label = type; 2420 const char *label = type;
2347 2421
2348 if (strcmp(type, "Create") == 0) 2422 if (strcmp(type, "Create") == 0)
2349 label = L("Mention"); 2423 label = L("Mention");
@@ -2455,14 +2529,14 @@ xs_str *html_notifications(snac *user, int skip, int show)
2455int html_get_handler(const xs_dict *req, const char *q_path, 2529int html_get_handler(const xs_dict *req, const char *q_path,
2456 char **body, int *b_size, char **ctype, xs_str **etag) 2530 char **body, int *b_size, char **ctype, xs_str **etag)
2457{ 2531{
2458 char *accept = xs_dict_get(req, "accept"); 2532 const char *accept = xs_dict_get(req, "accept");
2459 int status = 404; 2533 int status = 404;
2460 snac snac; 2534 snac snac;
2461 xs *uid = NULL; 2535 xs *uid = NULL;
2462 char *p_path; 2536 const char *p_path;
2463 int cache = 1; 2537 int cache = 1;
2464 int save = 1; 2538 int save = 1;
2465 char *v; 2539 const char *v;
2466 2540
2467 xs *l = xs_split_n(q_path, "/", 2); 2541 xs *l = xs_split_n(q_path, "/", 2);
2468 v = xs_list_get(l, 1); 2542 v = xs_list_get(l, 1);
@@ -2501,7 +2575,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2501 2575
2502 int skip = 0; 2576 int skip = 0;
2503 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); 2577 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries"));
2504 char *q_vars = xs_dict_get(req, "q_vars"); 2578 const xs_dict *q_vars = xs_dict_get(req, "q_vars");
2505 if ((v = xs_dict_get(q_vars, "skip")) != NULL) 2579 if ((v = xs_dict_get(q_vars, "skip")) != NULL)
2506 skip = atoi(v), cache = 0, save = 0; 2580 skip = atoi(v), cache = 0, save = 0;
2507 if ((v = xs_dict_get(q_vars, "show")) != NULL) 2581 if ((v = xs_dict_get(q_vars, "show")) != NULL)
@@ -2546,35 +2620,99 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2546 status = 401; 2620 status = 401;
2547 } 2621 }
2548 else { 2622 else {
2549 double t = history_mtime(&snac, "timeline.html_"); 2623 const char *q = xs_dict_get(q_vars, "q");
2624
2625 if (q && *q) {
2626 if (*q == '#') {
2627 /** search by tag **/
2628 xs *tl = tag_search(q, skip, show + 1);
2629 int more = 0;
2630 if (xs_list_len(tl) >= show + 1) {
2631 /* drop the last one */
2632 tl = xs_list_del(tl, -1);
2633 more = 1;
2634 }
2550 2635
2551 /* if enabled by admin, return a cached page if its timestamp is: 2636 xs *page = xs_fmt("/admin?q=%%23%s", q + 1);
2552 a) newer than the timeline timestamp 2637 xs *title = xs_fmt(xs_list_len(tl) ?
2553 b) newer than the start time of the server 2638 L("Search results for tag %s") : L("Nothing found for tag %s"), q);
2554 */
2555 if (cache && t > timeline_mtime(&snac) && t > p_state->srv_start_time) {
2556 snac_debug(&snac, 1, xs_fmt("serving cached timeline"));
2557 2639
2558 status = history_get(&snac, "timeline.html_", body, b_size, 2640 *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0);
2559 xs_dict_get(req, "if-none-match"), etag); 2641 *b_size = strlen(*body);
2642 status = 200;
2643 }
2644 else {
2645 /** search by content **/
2646 int to = 0;
2647 int msecs = atoi(xs_dict_get_def(q_vars, "msecs", "0"));
2648 xs *tl = content_search(&snac, q, 1, skip, show, msecs, &to);
2649 xs *title = NULL;
2650 xs *page = xs_fmt("/admin?q=%s&msecs=%d", q, msecs + 10);
2651 int tl_len = xs_list_len(tl);
2652
2653 if (tl_len)
2654 title = xs_fmt(L("Search results for '%s'"), q);
2655 else
2656 if (skip)
2657 title = xs_fmt(L("No more matches for '%s'"), q);
2658 else
2659 title = xs_fmt(L("Nothing found for '%s'"), q);
2660
2661 *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, title, page, 0);
2662 *b_size = strlen(*body);
2663 status = 200;
2664 }
2560 } 2665 }
2561 else { 2666 else {
2562 snac_debug(&snac, 1, xs_fmt("building timeline")); 2667 double t = history_mtime(&snac, "timeline.html_");
2668
2669 /* if enabled by admin, return a cached page if its timestamp is:
2670 a) newer than the timeline timestamp
2671 b) newer than the start time of the server
2672 */
2673 if (cache && t > timeline_mtime(&snac) && t > p_state->srv_start_time) {
2674 snac_debug(&snac, 1, xs_fmt("serving cached timeline"));
2563 2675
2564 xs *list = timeline_list(&snac, "private", skip, show); 2676 status = history_get(&snac, "timeline.html_", body, b_size,
2565 xs *next = timeline_list(&snac, "private", skip + show, 1); 2677 xs_dict_get(req, "if-none-match"), etag);
2678 }
2679 else {
2680 snac_debug(&snac, 1, xs_fmt("building timeline"));
2681
2682 xs *list = timeline_list(&snac, "private", skip, show);
2683 xs *next = timeline_list(&snac, "private", skip + show, 1);
2684
2685 xs *pins = pinned_list(&snac);
2686 pins = xs_list_cat(pins, list);
2566 2687
2567 xs *pins = pinned_list(&snac); 2688 *body = html_timeline(&snac, pins, 0, skip, show,
2568 pins = xs_list_cat(pins, list); 2689 xs_list_len(next), NULL, "/admin", 1);
2569 2690
2570 *body = html_timeline(&snac, pins, 0, skip, show, 2691 *b_size = strlen(*body);
2571 xs_list_len(next), NULL, "/admin", 1); 2692 status = 200;
2572 2693
2694 if (save)
2695 history_add(&snac, "timeline.html_", *body, *b_size, etag);
2696 }
2697 }
2698 }
2699 }
2700 else
2701 if (xs_startswith(p_path, "admin/p/")) { /** unique post by md5 **/
2702 if (!login(&snac, req)) {
2703 *body = xs_dup(uid);
2704 status = 401;
2705 }
2706 else {
2707 xs *l = xs_split(p_path, "/");
2708 const char *md5 = xs_list_get(l, -1);
2709
2710 if (md5 && *md5 && timeline_here(&snac, md5)) {
2711 xs *list = xs_list_append(xs_list_new(), md5);
2712
2713 *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1);
2573 *b_size = strlen(*body); 2714 *b_size = strlen(*body);
2574 status = 200; 2715 status = 200;
2575
2576 if (save)
2577 history_add(&snac, "timeline.html_", *body, *b_size, etag);
2578 } 2716 }
2579 } 2717 }
2580 } 2718 }
@@ -2613,12 +2751,37 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2613 xs *next = timeline_instance_list(skip + show, 1); 2751 xs *next = timeline_instance_list(skip + show, 1);
2614 2752
2615 *body = html_timeline(&snac, list, 0, skip, show, 2753 *body = html_timeline(&snac, list, 0, skip, show,
2616 xs_list_len(next), NULL, "/instance", 0); 2754 xs_list_len(next), L("Showing instance timeline"), "/instance", 0);
2617 *b_size = strlen(*body); 2755 *b_size = strlen(*body);
2618 status = 200; 2756 status = 200;
2619 } 2757 }
2620 } 2758 }
2621 else 2759 else
2760 if (xs_startswith(p_path, "list/")) { /** list timelines **/
2761 if (!login(&snac, req)) {
2762 *body = xs_dup(uid);
2763 status = 401;
2764 }
2765 else {
2766 xs *l = xs_split(p_path, "/");
2767 const char *lid = xs_list_get(l, -1);
2768
2769 xs *list = list_timeline(&snac, lid, skip, show);
2770 xs *next = list_timeline(&snac, lid, skip + show, 1);
2771
2772 if (list != NULL) {
2773 xs *base = xs_fmt("/list/%s", lid);
2774 xs *name = list_maint(&snac, lid, 3);
2775 xs *title = xs_fmt(L("Showing timeline for list '%s'"), name);
2776
2777 *body = html_timeline(&snac, list, 0, skip, show,
2778 xs_list_len(next), title, base, 1);
2779 *b_size = strlen(*body);
2780 status = 200;
2781 }
2782 }
2783 }
2784 else
2622 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ 2785 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/
2623 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) 2786 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE)
2624 return 403; 2787 return 403;
@@ -2640,7 +2803,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2640 else 2803 else
2641 if (xs_startswith(p_path, "s/")) { /** a static file **/ 2804 if (xs_startswith(p_path, "s/")) { /** a static file **/
2642 xs *l = xs_split(p_path, "/"); 2805 xs *l = xs_split(p_path, "/");
2643 char *id = xs_list_get(l, 1); 2806 const char *id = xs_list_get(l, 1);
2644 int sz; 2807 int sz;
2645 2808
2646 if (id && *id) { 2809 if (id && *id) {
@@ -2661,8 +2824,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2661 if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE) 2824 if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE)
2662 return 403; 2825 return 403;
2663 2826
2664 xs *l = xs_split(p_path, "/"); 2827 xs *l = xs_split(p_path, "/");
2665 char *id = xs_list_get(l, 1); 2828 const char *id = xs_list_get(l, 1);
2666 2829
2667 if (id && *id) { 2830 if (id && *id) {
2668 if (xs_endswith(id, "timeline.html_")) { 2831 if (xs_endswith(id, "timeline.html_")) {
@@ -2689,55 +2852,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2689 xs_dict_get(srv_config, "host")); 2852 xs_dict_get(srv_config, "host"));
2690 xs *rss_link = xs_fmt("%s.rss", snac.actor); 2853 xs *rss_link = xs_fmt("%s.rss", snac.actor);
2691 2854
2692 xs_html *rss = xs_html_tag("rss", 2855 *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio);
2693 xs_html_attr("version", "0.91"));
2694
2695 xs_html *channel = xs_html_tag("channel",
2696 xs_html_tag("title",
2697 xs_html_text(rss_title)),
2698 xs_html_tag("language",
2699 xs_html_text("en")),
2700 xs_html_tag("link",
2701 xs_html_text(rss_link)),
2702 xs_html_tag("description",
2703 xs_html_text(bio)));
2704
2705 xs_html_add(rss, channel);
2706
2707 xs_list *p = elems;
2708 char *v;
2709
2710 while (xs_list_iter(&p, &v)) {
2711 xs *msg = NULL;
2712
2713 if (!valid_status(timeline_get_by_md5(&snac, v, &msg)))
2714 continue;
2715
2716 char *id = xs_dict_get(msg, "id");
2717 char *content = xs_dict_get(msg, "content");
2718
2719 if (!xs_startswith(id, snac.actor))
2720 continue;
2721
2722 /* create a title with the first line of the content */
2723 xs *es_title = xs_replace(content, "<br>", "\n");
2724 xs *title = xs_str_new(NULL);
2725 int i;
2726
2727 for (i = 0; es_title[i] && es_title[i] != '\n' && es_title[i] != '&' && i < 50; i++)
2728 title = xs_append_m(title, &es_title[i], 1);
2729
2730 xs_html_add(channel,
2731 xs_html_tag("item",
2732 xs_html_tag("title",
2733 xs_html_text(title)),
2734 xs_html_tag("link",
2735 xs_html_text(id)),
2736 xs_html_tag("description",
2737 xs_html_text(content))));
2738 }
2739
2740 *body = xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2741 *b_size = strlen(*body); 2856 *b_size = strlen(*body);
2742 *ctype = "application/rss+xml; charset=utf-8"; 2857 *ctype = "application/rss+xml; charset=utf-8";
2743 status = 200; 2858 status = 200;
@@ -2766,8 +2881,9 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2766 2881
2767 int status = 0; 2882 int status = 0;
2768 snac snac; 2883 snac snac;
2769 char *uid, *p_path; 2884 const char *uid;
2770 xs_dict *p_vars; 2885 const char *p_path;
2886 const xs_dict *p_vars;
2771 2887
2772 xs *l = xs_split_n(q_path, "/", 2); 2888 xs *l = xs_split_n(q_path, "/", 2);
2773 2889
@@ -2795,15 +2911,15 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2795 2911
2796 if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/ 2912 if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/
2797 /* post note */ 2913 /* post note */
2798 xs_str *content = xs_dict_get(p_vars, "content"); 2914 const xs_str *content = xs_dict_get(p_vars, "content");
2799 xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); 2915 const xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to");
2800 xs_str *attach_url = xs_dict_get(p_vars, "attach_url"); 2916 const xs_str *attach_url = xs_dict_get(p_vars, "attach_url");
2801 xs_list *attach_file = xs_dict_get(p_vars, "attach"); 2917 const xs_list *attach_file = xs_dict_get(p_vars, "attach");
2802 xs_str *to = xs_dict_get(p_vars, "to"); 2918 const xs_str *to = xs_dict_get(p_vars, "to");
2803 xs_str *sensitive = xs_dict_get(p_vars, "sensitive"); 2919 const xs_str *sensitive = xs_dict_get(p_vars, "sensitive");
2804 xs_str *summary = xs_dict_get(p_vars, "summary"); 2920 const xs_str *summary = xs_dict_get(p_vars, "summary");
2805 xs_str *edit_id = xs_dict_get(p_vars, "edit_id"); 2921 const xs_str *edit_id = xs_dict_get(p_vars, "edit_id");
2806 xs_str *alt_text = xs_dict_get(p_vars, "alt_text"); 2922 const xs_str *alt_text = xs_dict_get(p_vars, "alt_text");
2807 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); 2923 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only"));
2808 xs *attach_list = xs_list_new(); 2924 xs *attach_list = xs_list_new();
2809 2925
@@ -2823,7 +2939,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2823 2939
2824 /* is attach_file set? */ 2940 /* is attach_file set? */
2825 if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) { 2941 if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) {
2826 char *fn = xs_list_get(attach_file, 0); 2942 const char *fn = xs_list_get(attach_file, 0);
2827 2943
2828 if (*fn != '\0') { 2944 if (*fn != '\0') {
2829 char *ext = strrchr(fn, '.'); 2945 char *ext = strrchr(fn, '.');
@@ -2899,7 +3015,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2899 int n; 3015 int n;
2900 3016
2901 for (n = 0; fields[n]; n++) { 3017 for (n = 0; fields[n]; n++) {
2902 char *v = xs_dict_get(p_msg, fields[n]); 3018 const char *v = xs_dict_get(p_msg, fields[n]);
2903 msg = xs_dict_set(msg, fields[n], v); 3019 msg = xs_dict_set(msg, fields[n], v);
2904 } 3020 }
2905 3021
@@ -2928,10 +3044,10 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2928 else 3044 else
2929 if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/ 3045 if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/
2930 /* action on an entry */ 3046 /* action on an entry */
2931 char *id = xs_dict_get(p_vars, "id"); 3047 const char *id = xs_dict_get(p_vars, "id");
2932 char *actor = xs_dict_get(p_vars, "actor"); 3048 const char *actor = xs_dict_get(p_vars, "actor");
2933 char *action = xs_dict_get(p_vars, "action"); 3049 const char *action = xs_dict_get(p_vars, "action");
2934 char *group = xs_dict_get(p_vars, "group"); 3050 const char *group = xs_dict_get(p_vars, "group");
2935 3051
2936 if (action == NULL) 3052 if (action == NULL)
2937 return 404; 3053 return 404;
@@ -3055,7 +3171,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3055 } 3171 }
3056 else 3172 else
3057 if (strcmp(action, L("Delete")) == 0) { /** **/ 3173 if (strcmp(action, L("Delete")) == 0) { /** **/
3058 char *actor_form = xs_dict_get(p_vars, "actor-form"); 3174 const char *actor_form = xs_dict_get(p_vars, "actor-form");
3059 if (actor_form != NULL) { 3175 if (actor_form != NULL) {
3060 /* delete follower */ 3176 /* delete follower */
3061 if (valid_status(follower_del(&snac, actor))) 3177 if (valid_status(follower_del(&snac, actor)))
@@ -3099,8 +3215,8 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3099 else 3215 else
3100 if (p_path && strcmp(p_path, "admin/user-setup") == 0) { /** **/ 3216 if (p_path && strcmp(p_path, "admin/user-setup") == 0) { /** **/
3101 /* change of user data */ 3217 /* change of user data */
3102 char *v; 3218 const char *v;
3103 char *p1, *p2; 3219 const char *p1, *p2;
3104 3220
3105 if ((v = xs_dict_get(p_vars, "name")) != NULL) 3221 if ((v = xs_dict_get(p_vars, "name")) != NULL)
3106 snac.config = xs_dict_set(snac.config, "name", v); 3222 snac.config = xs_dict_set(snac.config, "name", v);
@@ -3145,7 +3261,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3145 xs_dict *md = xs_dict_new(); 3261 xs_dict *md = xs_dict_new();
3146 xs *l = xs_split(v, "\n"); 3262 xs *l = xs_split(v, "\n");
3147 xs_list *p = l; 3263 xs_list *p = l;
3148 xs_str *kp; 3264 const xs_str *kp;
3149 3265
3150 while (xs_list_iter(&p, &kp)) { 3266 while (xs_list_iter(&p, &kp)) {
3151 xs *kpl = xs_split_n(kp, "=", 1); 3267 xs *kpl = xs_split_n(kp, "=", 1);
@@ -3166,7 +3282,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3166 for (n = 0; uploads[n]; n++) { 3282 for (n = 0; uploads[n]; n++) {
3167 xs *var_name = xs_fmt("%s_file", uploads[n]); 3283 xs *var_name = xs_fmt("%s_file", uploads[n]);
3168 3284
3169 xs_list *uploaded_file = xs_dict_get(p_vars, var_name); 3285 const xs_list *uploaded_file = xs_dict_get(p_vars, var_name);
3170 if (xs_type(uploaded_file) == XSTYPE_LIST) { 3286 if (xs_type(uploaded_file) == XSTYPE_LIST) {
3171 const char *fn = xs_list_get(uploaded_file, 0); 3287 const char *fn = xs_list_get(uploaded_file, 0);
3172 3288
@@ -3231,7 +3347,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3231 } 3347 }
3232 else 3348 else
3233 if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/ 3349 if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/
3234 char *irt = xs_dict_get(p_vars, "irt"); 3350 const char *irt = xs_dict_get(p_vars, "irt");
3235 const char *opt = xs_dict_get(p_vars, "question"); 3351 const char *opt = xs_dict_get(p_vars, "question");
3236 const char *actor = xs_dict_get(p_vars, "actor"); 3352 const char *actor = xs_dict_get(p_vars, "actor");
3237 3353
@@ -3246,7 +3362,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3246 } 3362 }
3247 3363
3248 xs_list *p = ls; 3364 xs_list *p = ls;
3249 xs_str *v; 3365 const xs_str *v;
3250 3366
3251 while (xs_list_iter(&p, &v)) { 3367 while (xs_list_iter(&p, &v)) {
3252 xs *msg = msg_note(&snac, "", actor, irt, NULL, 1); 3368 xs *msg = msg_note(&snac, "", actor, irt, NULL, 1);
@@ -3261,11 +3377,30 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3261 timeline_add(&snac, xs_dict_get(msg, "id"), msg); 3377 timeline_add(&snac, xs_dict_get(msg, "id"), msg);
3262 } 3378 }
3263 3379
3380 {
3381 /* get the poll object */
3382 xs *poll = NULL;
3383
3384 if (valid_status(object_get(irt, &poll))) {
3385 const char *date = xs_dict_get(poll, "endTime");
3386 if (xs_is_null(date))
3387 date = xs_dict_get(poll, "closed");
3388
3389 if (!xs_is_null(date)) {
3390 time_t t = xs_parse_iso_date(date, 0) - time(NULL);
3391
3392 /* request the poll when it's closed;
3393 Pleroma does not send and update when the poll closes */
3394 enqueue_object_request(&snac, irt, t + 2);
3395 }
3396 }
3397 }
3398
3264 status = 303; 3399 status = 303;
3265 } 3400 }
3266 3401
3267 if (status == 303) { 3402 if (status == 303) {
3268 char *redir = xs_dict_get(p_vars, "redir"); 3403 const char *redir = xs_dict_get(p_vars, "redir");
3269 3404
3270 if (xs_is_null(redir)) 3405 if (xs_is_null(redir))
3271 redir = "top"; 3406 redir = "top";
@@ -3278,3 +3413,71 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3278 3413
3279 return status; 3414 return status;
3280} 3415}
3416
3417
3418xs_str *timeline_to_rss(snac *user, const xs_list *timeline, char *title, char *link, char *desc)
3419/* converts a timeline to rss */
3420{
3421 xs_html *rss = xs_html_tag("rss",
3422 xs_html_attr("version", "0.91"));
3423
3424 xs_html *channel = xs_html_tag("channel",
3425 xs_html_tag("title",
3426 xs_html_text(title)),
3427 xs_html_tag("language",
3428 xs_html_text("en")),
3429 xs_html_tag("link",
3430 xs_html_text(link)),
3431 xs_html_tag("description",
3432 xs_html_text(desc)));
3433
3434 xs_html_add(rss, channel);
3435
3436 int c = 0;
3437 const char *v;
3438
3439 while (xs_list_next(timeline, &v, &c)) {
3440 xs *msg = NULL;
3441
3442 if (user) {
3443 if (!valid_status(timeline_get_by_md5(user, v, &msg)))
3444 continue;
3445 }
3446 else {
3447 if (!valid_status(object_get_by_md5(v, &msg)))
3448 continue;
3449 }
3450
3451 const char *id = xs_dict_get(msg, "id");
3452 const char *content = xs_dict_get(msg, "content");
3453
3454 if (user && !xs_startswith(id, user->actor))
3455 continue;
3456
3457 /* create a title with the first line of the content */
3458 xs *title = xs_replace(content, "<br>", "\n");
3459 title = xs_regex_replace_i(title, "<[^>]+>", " ");
3460 title = xs_regex_replace_i(title, "&[^;]+;", " ");
3461 int i;
3462
3463 for (i = 0; title[i] && title[i] != '\n' && i < 50; i++);
3464
3465 if (title[i] != '\0') {
3466 title[i] = '\0';
3467 title = xs_str_cat(title, "...");
3468 }
3469
3470 title = xs_strip_i(title);
3471
3472 xs_html_add(channel,
3473 xs_html_tag("item",
3474 xs_html_tag("title",
3475 xs_html_text(title)),
3476 xs_html_tag("link",
3477 xs_html_text(id)),
3478 xs_html_tag("description",
3479 xs_html_text(content))));
3480 }
3481
3482 return xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
3483}
diff --git a/http.c b/http.c
index 1b3d590..b21f1dc 100644
--- a/http.c
+++ b/http.c
@@ -12,7 +12,7 @@
12 12
13xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, 13xs_dict *http_signed_request_raw(const char *keyid, const char *seckey,
14 const char *method, const char *url, 14 const char *method, const char *url,
15 xs_dict *headers, 15 const xs_dict *headers,
16 const char *body, int b_size, 16 const char *body, int b_size,
17 int *status, xs_str **payload, int *p_size, 17 int *status, xs_str **payload, int *p_size,
18 int timeout) 18 int timeout)
@@ -24,15 +24,16 @@ xs_dict *http_signed_request_raw(const char *keyid, const char *seckey,
24 xs *s64 = NULL; 24 xs *s64 = NULL;
25 xs *signature = NULL; 25 xs *signature = NULL;
26 xs *hdrs = NULL; 26 xs *hdrs = NULL;
27 char *host; 27 const char *host;
28 char *target; 28 const char *target;
29 char *k, *v; 29 const char *k, *v;
30 xs_dict *response; 30 xs_dict *response;
31 31
32 date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT"); 32 date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT");
33 33
34 { 34 {
35 xs *s = xs_replace_n(url, "https:/" "/", "", 1); 35 xs *s1 = xs_replace_n(url, "http:/" "/", "", 1);
36 xs *s = xs_replace_n(s1, "https:/" "/", "", 1);
36 l1 = xs_split_n(s, "/", 1); 37 l1 = xs_split_n(s, "/", 1);
37 } 38 }
38 39
@@ -105,13 +106,13 @@ xs_dict *http_signed_request_raw(const char *keyid, const char *seckey,
105 106
106 107
107xs_dict *http_signed_request(snac *snac, const char *method, const char *url, 108xs_dict *http_signed_request(snac *snac, const char *method, const char *url,
108 xs_dict *headers, 109 const xs_dict *headers,
109 const char *body, int b_size, 110 const char *body, int b_size,
110 int *status, xs_str **payload, int *p_size, 111 int *status, xs_str **payload, int *p_size,
111 int timeout) 112 int timeout)
112/* does a signed HTTP request */ 113/* does a signed HTTP request */
113{ 114{
114 char *seckey = xs_dict_get(snac->key, "secret"); 115 const char *seckey = xs_dict_get(snac->key, "secret");
115 xs_dict *response; 116 xs_dict *response;
116 117
117 response = http_signed_request_raw(snac->actor, seckey, method, url, 118 response = http_signed_request_raw(snac->actor, seckey, method, url,
@@ -121,17 +122,18 @@ xs_dict *http_signed_request(snac *snac, const char *method, const char *url,
121} 122}
122 123
123 124
124int check_signature(xs_dict *req, xs_str **err) 125int check_signature(const xs_dict *req, xs_str **err)
125/* check the signature */ 126/* check the signature */
126{ 127{
127 char *sig_hdr = xs_dict_get(req, "signature"); 128 const char *sig_hdr = xs_dict_get(req, "signature");
128 xs *keyId = NULL; 129 xs *keyId = NULL;
129 xs *headers = NULL; 130 xs *headers = NULL;
130 xs *signature = NULL; 131 xs *signature = NULL;
131 xs *created = NULL; 132 xs *created = NULL;
132 xs *expires = NULL; 133 xs *expires = NULL;
133 char *pubkey;
134 char *p; 134 char *p;
135 const char *pubkey;
136 const char *k;
135 137
136 if (xs_is_null(sig_hdr)) { 138 if (xs_is_null(sig_hdr)) {
137 *err = xs_fmt("missing 'signature' header"); 139 *err = xs_fmt("missing 'signature' header");
@@ -141,10 +143,10 @@ int check_signature(xs_dict *req, xs_str **err)
141 { 143 {
142 /* extract the values */ 144 /* extract the values */
143 xs *l = xs_split(sig_hdr, ","); 145 xs *l = xs_split(sig_hdr, ",");
144 xs_list *p = l; 146 int c = 0;
145 xs_val *v; 147 const xs_val *v;
146 148
147 while (xs_list_iter(&p, &v)) { 149 while (xs_list_next(l, &v, &c)) {
148 xs *kv = xs_split_n(v, "=", 1); 150 xs *kv = xs_split_n(v, "=", 1);
149 151
150 if (xs_list_len(kv) != 2) 152 if (xs_list_len(kv) != 2)
@@ -191,8 +193,8 @@ int check_signature(xs_dict *req, xs_str **err)
191 return 0; 193 return 0;
192 } 194 }
193 195
194 if ((p = xs_dict_get(actor, "publicKey")) == NULL || 196 if ((k = xs_dict_get(actor, "publicKey")) == NULL ||
195 ((pubkey = xs_dict_get(p, "publicKeyPem")) == NULL)) { 197 ((pubkey = xs_dict_get(k, "publicKeyPem")) == NULL)) {
196 *err = xs_fmt("cannot get pubkey from %s", keyId); 198 *err = xs_fmt("cannot get pubkey from %s", keyId);
197 return 0; 199 return 0;
198 } 200 }
@@ -203,11 +205,11 @@ int check_signature(xs_dict *req, xs_str **err)
203 { 205 {
204 xs *l = xs_split(headers, " "); 206 xs *l = xs_split(headers, " ");
205 xs_list *p; 207 xs_list *p;
206 xs_val *v; 208 const xs_val *v;
207 209
208 p = l; 210 p = l;
209 while (xs_list_iter(&p, &v)) { 211 while (xs_list_iter(&p, &v)) {
210 char *hc; 212 const char *hc;
211 xs *ss = NULL; 213 xs *ss = NULL;
212 214
213 if (*sig_str != '\0') 215 if (*sig_str != '\0')
diff --git a/httpd.c b/httpd.c
index e402e61..a7396e8 100644
--- a/httpd.c
+++ b/httpd.c
@@ -75,7 +75,7 @@ xs_str *nodeinfo_2_0(void)
75 int n_posts = 0; 75 int n_posts = 0;
76 xs *users = user_list(); 76 xs *users = user_list();
77 xs_list *p = users; 77 xs_list *p = users;
78 char *v; 78 const char *v;
79 double now = (double)time(NULL); 79 double now = (double)time(NULL);
80 80
81 while (xs_list_iter(&p, &v)) { 81 while (xs_list_iter(&p, &v)) {
@@ -125,10 +125,10 @@ static xs_str *greeting_html(void)
125 125
126 /* does it have a %userlist% mark? */ 126 /* does it have a %userlist% mark? */
127 if (xs_str_in(s, "%userlist%") != -1) { 127 if (xs_str_in(s, "%userlist%") != -1) {
128 char *host = xs_dict_get(srv_config, "host"); 128 const char *host = xs_dict_get(srv_config, "host");
129 xs *list = user_list(); 129 xs *list = user_list();
130 xs_list *p = list; 130 xs_list *p = list;
131 xs_str *uid; 131 const xs_str *uid;
132 132
133 xs_html *ul = xs_html_tag("ul", 133 xs_html *ul = xs_html_tag("ul",
134 xs_html_attr("class", "snac-user-list")); 134 xs_html_attr("class", "snac-user-list"));
@@ -169,18 +169,16 @@ int server_get_handler(xs_dict *req, const char *q_path,
169{ 169{
170 int status = 0; 170 int status = 0;
171 171
172 (void)req;
173
174 /* is it the server root? */ 172 /* is it the server root? */
175 if (*q_path == '\0') { 173 if (*q_path == '\0') {
176 xs_dict *q_vars = xs_dict_get(req, "q_vars"); 174 const xs_dict *q_vars = xs_dict_get(req, "q_vars");
177 char *t = NULL; 175 const char *t = NULL;
178 176
179 if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { 177 if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) {
180 /** search by tag **/ 178 /** search by tag **/
181 int skip = 0; 179 int skip = 0;
182 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); 180 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries"));
183 char *v; 181 const char *v;
184 182
185 if ((v = xs_dict_get(q_vars, "skip")) != NULL) 183 if ((v = xs_dict_get(q_vars, "skip")) != NULL)
186 skip = atoi(v); 184 skip = atoi(v);
@@ -195,13 +193,25 @@ int server_get_handler(xs_dict *req, const char *q_path,
195 more = 1; 193 more = 1;
196 } 194 }
197 195
198 *body = html_timeline(NULL, tl, 0, skip, show, more, t, NULL, 0); 196 const char *accept = xs_dict_get(req, "accept");
197 if (!xs_is_null(accept) && strcmp(accept, "application/rss+xml") == 0) {
198 xs *link = xs_fmt("%s/?t=%s", srv_baseurl, t);
199
200 *body = timeline_to_rss(NULL, tl, link, link, link);
201 *ctype = "application/rss+xml; charset=utf-8";
202 }
203 else {
204 xs *page = xs_fmt("?t=%s", t);
205 xs *title = xs_fmt(L("Search results for tag #%s"), t);
206 *body = html_timeline(NULL, tl, 0, skip, show, more, title, page, 0);
207 }
199 } 208 }
200 else 209 else
201 if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) { 210 if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) {
202 /** instance timeline **/ 211 /** instance timeline **/
203 xs *tl = timeline_instance_list(0, 30); 212 xs *tl = timeline_instance_list(0, 30);
204 *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL, NULL, 0); 213 *body = html_timeline(NULL, tl, 0, 0, 0, 0,
214 L("Recent posts by users in this instance"), NULL, 0);
205 } 215 }
206 else 216 else
207 *body = greeting_html(); 217 *body = greeting_html();
@@ -258,7 +268,7 @@ void httpd_connection(FILE *f)
258/* the connection processor */ 268/* the connection processor */
259{ 269{
260 xs *req; 270 xs *req;
261 char *method; 271 const char *method;
262 int status = 0; 272 int status = 0;
263 xs_str *body = NULL; 273 xs_str *body = NULL;
264 int b_size = 0; 274 int b_size = 0;
@@ -268,7 +278,7 @@ void httpd_connection(FILE *f)
268 xs *payload = NULL; 278 xs *payload = NULL;
269 xs *etag = NULL; 279 xs *etag = NULL;
270 int p_size = 0; 280 int p_size = 0;
271 char *p; 281 const char *p;
272 int fcgi_id; 282 int fcgi_id;
273 283
274 if (p_state->use_fcgi) 284 if (p_state->use_fcgi)
@@ -360,7 +370,7 @@ void httpd_connection(FILE *f)
360#ifndef NO_MASTODON_API 370#ifndef NO_MASTODON_API
361 if (status == 0) 371 if (status == 0)
362 status = mastoapi_delete_handler(req, q_path, 372 status = mastoapi_delete_handler(req, q_path,
363 &body, &b_size, &ctype); 373 payload, p_size, &body, &b_size, &ctype);
364#endif 374#endif
365 } 375 }
366 376
@@ -401,9 +411,9 @@ void httpd_connection(FILE *f)
401 headers = xs_dict_append(headers, "etag", etag); 411 headers = xs_dict_append(headers, "etag", etag);
402 412
403 /* if there are any additional headers, add them */ 413 /* if there are any additional headers, add them */
404 xs_dict *more_headers = xs_dict_get(srv_config, "http_headers"); 414 const xs_dict *more_headers = xs_dict_get(srv_config, "http_headers");
405 if (xs_type(more_headers) == XSTYPE_DICT) { 415 if (xs_type(more_headers) == XSTYPE_DICT) {
406 char *k, *v; 416 const char *k, *v;
407 int c = 0; 417 int c = 0;
408 while (xs_dict_next(more_headers, &k, &v, &c)) 418 while (xs_dict_next(more_headers, &k, &v, &c))
409 headers = xs_dict_set(headers, k, v); 419 headers = xs_dict_set(headers, k, v);
@@ -580,7 +590,8 @@ static void *background_thread(void *arg)
580 590
581 { 591 {
582 xs *list = user_list(); 592 xs *list = user_list();
583 char *p, *uid; 593 char *p;
594 const char *uid;
584 595
585 /* process queues for all users */ 596 /* process queues for all users */
586 p = list; 597 p = list;
@@ -654,6 +665,13 @@ srv_state *srv_state_op(xs_str **fname, int op)
654 665
655 switch (op) { 666 switch (op) {
656 case 0: /* open for writing */ 667 case 0: /* open for writing */
668
669#ifdef WITHOUT_SHM
670
671 errno = ENOTSUP;
672
673#else
674
657 if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) { 675 if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) {
658 ftruncate(fd, sizeof(*ss)); 676 ftruncate(fd, sizeof(*ss));
659 677
@@ -664,6 +682,8 @@ srv_state *srv_state_op(xs_str **fname, int op)
664 close(fd); 682 close(fd);
665 } 683 }
666 684
685#endif
686
667 if (ss == NULL) { 687 if (ss == NULL) {
668 /* shared memory error: just create a plain structure */ 688 /* shared memory error: just create a plain structure */
669 srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno))); 689 srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno)));
@@ -677,6 +697,13 @@ srv_state *srv_state_op(xs_str **fname, int op)
677 break; 697 break;
678 698
679 case 1: /* open for reading */ 699 case 1: /* open for reading */
700
701#ifdef WITHOUT_SHM
702
703 errno = ENOTSUP;
704
705#else
706
680 if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) { 707 if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) {
681 if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) 708 if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED)
682 ss = NULL; 709 ss = NULL;
@@ -684,6 +711,8 @@ srv_state *srv_state_op(xs_str **fname, int op)
684 close(fd); 711 close(fd);
685 } 712 }
686 713
714#endif
715
687 if (ss == NULL) { 716 if (ss == NULL) {
688 /* shared memory error */ 717 /* shared memory error */
689 srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno))); 718 srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno)));
@@ -701,9 +730,14 @@ srv_state *srv_state_op(xs_str **fname, int op)
701 break; 730 break;
702 731
703 case 2: /* unlink */ 732 case 2: /* unlink */
733
734#ifndef WITHOUT_SHM
735
704 if (*fname) 736 if (*fname)
705 shm_unlink(*fname); 737 shm_unlink(*fname);
706 738
739#endif
740
707 break; 741 break;
708 } 742 }
709 743
diff --git a/main.c b/main.c
index 06cae78..c88eebe 100644
--- a/main.c
+++ b/main.c
@@ -44,6 +44,7 @@ int usage(void)
44 printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\n"); 44 printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\n");
45 printf("unlimit {basedir} {uid} {actor} Unlimits an actor\n"); 45 printf("unlimit {basedir} {uid} {actor} Unlimits an actor\n");
46 printf("verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n"); 46 printf("verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n");
47 printf("search {basedir} {uid} {regex} Searches posts by content\n");
47 48
48 return 1; 49 return 1;
49} 50}
@@ -314,7 +315,7 @@ int main(int argc, char *argv[])
314 xs *msg = msg_follow(&snac, url); 315 xs *msg = msg_follow(&snac, url);
315 316
316 if (msg != NULL) { 317 if (msg != NULL) {
317 char *actor = xs_dict_get(msg, "object"); 318 const char *actor = xs_dict_get(msg, "object");
318 319
319 following_add(&snac, actor, msg); 320 following_add(&snac, actor, msg);
320 321
@@ -374,6 +375,23 @@ int main(int argc, char *argv[])
374 return 0; 375 return 0;
375 } 376 }
376 377
378 if (strcmp(cmd, "search") == 0) { /** **/
379 int to;
380
381 /* 'url' contains the regex */
382 xs *r = content_search(&snac, url, 1, 0, XS_ALL, 10, &to);
383
384 int c = 0;
385 const char *v;
386
387 /* print results as standalone links */
388 while (xs_list_next(r, &v, &c)) {
389 printf("%s/admin/p/%s\n", snac.actor, v);
390 }
391
392 return 0;
393 }
394
377 if (strcmp(cmd, "ping") == 0) { /** **/ 395 if (strcmp(cmd, "ping") == 0) { /** **/
378 xs *actor_o = NULL; 396 xs *actor_o = NULL;
379 397
@@ -458,6 +476,12 @@ int main(int argc, char *argv[])
458 return 0; 476 return 0;
459 } 477 }
460 478
479 if (strcmp(cmd, "request2") == 0) { /** **/
480 enqueue_object_request(&snac, url, 2);
481
482 return 0;
483 }
484
461 if (strcmp(cmd, "actor") == 0) { /** **/ 485 if (strcmp(cmd, "actor") == 0) { /** **/
462 int status; 486 int status;
463 xs *data = NULL; 487 xs *data = NULL;
diff --git a/mastoapi.c b/mastoapi.c
index 4d80f69..3936c2a 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -156,7 +156,7 @@ const char *login_page = ""
156"</head>\n" 156"</head>\n"
157"<body><h1>%s OAuth identify</h1>\n" 157"<body><h1>%s OAuth identify</h1>\n"
158"<div style=\"background-color: red; color: white\">%s</div>\n" 158"<div style=\"background-color: red; color: white\">%s</div>\n"
159"<form method=\"post\" action=\"https:/" "/%s/%s\">\n" 159"<form method=\"post\" action=\"%s:/" "/%s/%s\">\n"
160"<p>Login: <input type=\"text\" name=\"login\"></p>\n" 160"<p>Login: <input type=\"text\" name=\"login\"></p>\n"
161"<p>Password: <input type=\"password\" name=\"passwd\"></p>\n" 161"<p>Password: <input type=\"password\" name=\"passwd\"></p>\n"
162"<input type=\"hidden\" name=\"redir\" value=\"%s\">\n" 162"<input type=\"hidden\" name=\"redir\" value=\"%s\">\n"
@@ -175,7 +175,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
175 return 0; 175 return 0;
176 176
177 int status = 404; 177 int status = 404;
178 xs_dict *msg = xs_dict_get(req, "q_vars"); 178 const xs_dict *msg = xs_dict_get(req, "q_vars");
179 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); 179 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
180 180
181 srv_debug(1, xs_fmt("oauth_get_handler %s", q_path)); 181 srv_debug(1, xs_fmt("oauth_get_handler %s", q_path));
@@ -193,11 +193,12 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
193 193
194 if (app != NULL) { 194 if (app != NULL) {
195 const char *host = xs_dict_get(srv_config, "host"); 195 const char *host = xs_dict_get(srv_config, "host");
196 const char *proto = xs_dict_get_def(srv_config, "protocol", "https");
196 197
197 if (xs_is_null(state)) 198 if (xs_is_null(state))
198 state = ""; 199 state = "";
199 200
200 *body = xs_fmt(login_page, host, host, "", host, "oauth/x-snac-login", 201 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-login",
201 ruri, cid, state, USER_AGENT); 202 ruri, cid, state, USER_AGENT);
202 *ctype = "text/html"; 203 *ctype = "text/html";
203 status = 200; 204 status = 200;
@@ -213,8 +214,9 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
213 else 214 else
214 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ 215 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/
215 const char *host = xs_dict_get(srv_config, "host"); 216 const char *host = xs_dict_get(srv_config, "host");
217 const char *proto = xs_dict_get_def(srv_config, "protocol", "https");
216 218
217 *body = xs_fmt(login_page, host, host, "", host, "oauth/x-snac-get-token", 219 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-get-token",
218 "", "", "", USER_AGENT); 220 "", "", "", USER_AGENT);
219 *ctype = "text/html"; 221 *ctype = "text/html";
220 status = 200; 222 status = 200;
@@ -237,7 +239,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
237 239
238 int status = 404; 240 int status = 404;
239 241
240 char *i_ctype = xs_dict_get(req, "content-type"); 242 const char *i_ctype = xs_dict_get(req, "content-type");
241 xs *args = NULL; 243 xs *args = NULL;
242 244
243 if (i_ctype && xs_startswith(i_ctype, "application/json")) { 245 if (i_ctype && xs_startswith(i_ctype, "application/json")) {
@@ -265,11 +267,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
265 const char *redir = xs_dict_get(args, "redir"); 267 const char *redir = xs_dict_get(args, "redir");
266 const char *cid = xs_dict_get(args, "cid"); 268 const char *cid = xs_dict_get(args, "cid");
267 const char *state = xs_dict_get(args, "state"); 269 const char *state = xs_dict_get(args, "state");
268 270 const char *host = xs_dict_get(srv_config, "host");
269 const char *host = xs_dict_get(srv_config, "host"); 271 const char *proto = xs_dict_get_def(srv_config, "protocol", "https");
270 272
271 /* by default, generate another login form with an error */ 273 /* by default, generate another login form with an error */
272 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", host, "oauth/x-snac-login", 274 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-login",
273 redir, cid, state, USER_AGENT); 275 redir, cid, state, USER_AGENT);
274 *ctype = "text/html"; 276 *ctype = "text/html";
275 status = 200; 277 status = 200;
@@ -289,7 +291,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
289 *body = xs_dup(code); 291 *body = xs_dup(code);
290 } 292 }
291 else { 293 else {
292 *body = xs_fmt("%s?code=%s", redir, code); 294 if (xs_str_in(redir, "?") != -1)
295 *body = xs_fmt("%s&code=%s", redir, code);
296 else
297 *body = xs_fmt("%s?code=%s", redir, code);
298
293 status = 303; 299 status = 303;
294 } 300 }
295 301
@@ -446,11 +452,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
446 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ 452 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/
447 const char *login = xs_dict_get(args, "login"); 453 const char *login = xs_dict_get(args, "login");
448 const char *passwd = xs_dict_get(args, "passwd"); 454 const char *passwd = xs_dict_get(args, "passwd");
449 455 const char *host = xs_dict_get(srv_config, "host");
450 const char *host = xs_dict_get(srv_config, "host"); 456 const char *proto = xs_dict_get_def(srv_config, "protocol", "https");
451 457
452 /* by default, generate another login form with an error */ 458 /* by default, generate another login form with an error */
453 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", host, "oauth/x-snac-get-token", 459 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-get-token",
454 "", "", "", USER_AGENT); 460 "", "", "", USER_AGENT);
455 *ctype = "text/html"; 461 *ctype = "text/html";
456 status = 200; 462 status = 200;
@@ -544,6 +550,9 @@ xs_dict *mastoapi_account(const xs_dict *actor)
544 acct = xs_dict_append(acct, "created_at", date); 550 acct = xs_dict_append(acct, "created_at", date);
545 } 551 }
546 552
553 xs *last_status_at = xs_str_utctime(0, "%Y-%m-%d");
554 acct = xs_dict_append(acct, "last_status_at", last_status_at);
555
547 const char *note = xs_dict_get(actor, "summary"); 556 const char *note = xs_dict_get(actor, "summary");
548 if (xs_is_null(note)) 557 if (xs_is_null(note))
549 note = ""; 558 note = "";
@@ -559,10 +568,10 @@ xs_dict *mastoapi_account(const xs_dict *actor)
559 acct = xs_dict_append(acct, "uri", id); 568 acct = xs_dict_append(acct, "uri", id);
560 569
561 xs *avatar = NULL; 570 xs *avatar = NULL;
562 xs_dict *av = xs_dict_get(actor, "icon"); 571 const xs_dict *av = xs_dict_get(actor, "icon");
563 572
564 if (xs_type(av) == XSTYPE_DICT) { 573 if (xs_type(av) == XSTYPE_DICT) {
565 char *url = xs_dict_get(av, "url"); 574 const char *url = xs_dict_get(av, "url");
566 575
567 if (url != NULL) 576 if (url != NULL)
568 avatar = xs_dup(url); 577 avatar = xs_dup(url);
@@ -575,7 +584,7 @@ xs_dict *mastoapi_account(const xs_dict *actor)
575 acct = xs_dict_append(acct, "avatar_static", avatar); 584 acct = xs_dict_append(acct, "avatar_static", avatar);
576 585
577 xs *header = NULL; 586 xs *header = NULL;
578 xs_dict *hd = xs_dict_get(actor, "image"); 587 const xs_dict *hd = xs_dict_get(actor, "image");
579 588
580 if (xs_type(hd) == XSTYPE_DICT) 589 if (xs_type(hd) == XSTYPE_DICT)
581 header = xs_dup(xs_dict_get(hd, "url")); 590 header = xs_dup(xs_dict_get(hd, "url"));
@@ -587,12 +596,13 @@ xs_dict *mastoapi_account(const xs_dict *actor)
587 acct = xs_dict_append(acct, "header_static", header); 596 acct = xs_dict_append(acct, "header_static", header);
588 597
589 /* emojis */ 598 /* emojis */
590 xs_list *p; 599 const xs_list *p;
591 if (!xs_is_null(p = xs_dict_get(actor, "tag"))) { 600 if (!xs_is_null(p = xs_dict_get(actor, "tag"))) {
592 xs *eml = xs_list_new(); 601 xs *eml = xs_list_new();
593 xs_dict *v; 602 const xs_dict *v;
603 int c = 0;
594 604
595 while (xs_list_iter(&p, &v)) { 605 while (xs_list_next(p, &v, &c)) {
596 const char *type = xs_dict_get(v, "type"); 606 const char *type = xs_dict_get(v, "type");
597 607
598 if (!xs_is_null(type) && strcmp(type, "Emoji") == 0) { 608 if (!xs_is_null(type) && strcmp(type, "Emoji") == 0) {
@@ -627,11 +637,11 @@ xs_dict *mastoapi_account(const xs_dict *actor)
627 637
628 xs *fields = xs_list_new(); 638 xs *fields = xs_list_new();
629 p = xs_dict_get(actor, "attachment"); 639 p = xs_dict_get(actor, "attachment");
630 xs_dict *v; 640 const xs_dict *v;
631 641
632 /* dict of validated links */ 642 /* dict of validated links */
633 xs_dict *val_links = NULL; 643 xs_dict *val_links = NULL;
634 xs_dict *metadata = xs_stock(XSTYPE_DICT); 644 const xs_dict *metadata = xs_stock(XSTYPE_DICT);
635 snac user = {0}; 645 snac user = {0};
636 646
637 if (xs_startswith(id, srv_baseurl)) { 647 if (xs_startswith(id, srv_baseurl)) {
@@ -645,19 +655,20 @@ xs_dict *mastoapi_account(const xs_dict *actor)
645 if (xs_is_null(val_links)) 655 if (xs_is_null(val_links))
646 val_links = xs_stock(XSTYPE_DICT); 656 val_links = xs_stock(XSTYPE_DICT);
647 657
648 while (xs_list_iter(&p, &v)) { 658 int c = 0;
649 char *type = xs_dict_get(v, "type"); 659 while (xs_list_next(p, &v, &c)) {
650 char *name = xs_dict_get(v, "name"); 660 const char *type = xs_dict_get(v, "type");
651 char *value = xs_dict_get(v, "value"); 661 const char *name = xs_dict_get(v, "name");
662 const char *value = xs_dict_get(v, "value");
652 663
653 if (!xs_is_null(type) && !xs_is_null(name) && 664 if (!xs_is_null(type) && !xs_is_null(name) &&
654 !xs_is_null(value) && strcmp(type, "PropertyValue") == 0) { 665 !xs_is_null(value) && strcmp(type, "PropertyValue") == 0) {
655 xs *val_date = NULL; 666 xs *val_date = NULL;
656 667
657 char *url = xs_dict_get(metadata, name); 668 const char *url = xs_dict_get(metadata, name);
658 669
659 if (!xs_is_null(url) && xs_startswith(url, "https:/" "/")) { 670 if (!xs_is_null(url) && xs_startswith(url, "https:/" "/")) {
660 xs_number *verified_time = xs_dict_get(val_links, url); 671 const xs_number *verified_time = xs_dict_get(val_links, url);
661 if (xs_type(verified_time) == XSTYPE_NUMBER) { 672 if (xs_type(verified_time) == XSTYPE_NUMBER) {
662 time_t t = xs_number_get(verified_time); 673 time_t t = xs_number_get(verified_time);
663 674
@@ -686,7 +697,7 @@ xs_dict *mastoapi_account(const xs_dict *actor)
686} 697}
687 698
688 699
689xs_str *mastoapi_date(char *date) 700xs_str *mastoapi_date(const char *date)
690/* converts an ISO 8601 date to whatever format Mastodon uses */ 701/* converts an ISO 8601 date to whatever format Mastodon uses */
691{ 702{
692 xs_str *s = xs_crop_i(xs_dup(date), 0, 19); 703 xs_str *s = xs_crop_i(xs_dup(date), 0, 19);
@@ -701,16 +712,29 @@ xs_dict *mastoapi_poll(snac *snac, const xs_dict *msg)
701{ 712{
702 xs_dict *poll = xs_dict_new(); 713 xs_dict *poll = xs_dict_new();
703 xs *mid = mastoapi_id(msg); 714 xs *mid = mastoapi_id(msg);
704 xs_list *opts = NULL; 715 const xs_list *opts = NULL;
705 xs_val *v; 716 const xs_val *v;
706 int num_votes = 0; 717 int num_votes = 0;
707 xs *options = xs_list_new(); 718 xs *options = xs_list_new();
708 719
709 poll = xs_dict_append(poll, "id", mid); 720 poll = xs_dict_append(poll, "id", mid);
710 xs *fd = mastoapi_date(xs_dict_get(msg, "endTime")); 721 const char *date = xs_dict_get(msg, "endTime");
722 if (date == NULL)
723 date = xs_dict_get(msg, "closed");
724 if (date == NULL)
725 return NULL;
726
727 xs *fd = mastoapi_date(date);
711 poll = xs_dict_append(poll, "expires_at", fd); 728 poll = xs_dict_append(poll, "expires_at", fd);
729
730 date = xs_dict_get(msg, "closed");
731 time_t t = 0;
732
733 if (date != NULL)
734 t = xs_parse_iso_date(date, 0);
735
712 poll = xs_dict_append(poll, "expired", 736 poll = xs_dict_append(poll, "expired",
713 xs_dict_get(msg, "closed") != NULL ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); 737 t < time(NULL) ? xs_stock(XSTYPE_FALSE) : xs_stock(XSTYPE_TRUE));
714 738
715 if ((opts = xs_dict_get(msg, "oneOf")) != NULL) 739 if ((opts = xs_dict_get(msg, "oneOf")) != NULL)
716 poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_FALSE)); 740 poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_FALSE));
@@ -719,7 +743,8 @@ xs_dict *mastoapi_poll(snac *snac, const xs_dict *msg)
719 poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_TRUE)); 743 poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_TRUE));
720 } 744 }
721 745
722 while (xs_list_iter(&opts, &v)) { 746 int c = 0;
747 while (xs_list_next(opts, &v, &c)) {
723 const char *title = xs_dict_get(v, "name"); 748 const char *title = xs_dict_get(v, "name");
724 const char *replies = xs_dict_get(v, "replies"); 749 const char *replies = xs_dict_get(v, "replies");
725 750
@@ -772,7 +797,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
772 797
773 xs *idx = NULL; 798 xs *idx = NULL;
774 xs *ixc = NULL; 799 xs *ixc = NULL;
775 char *tmp; 800 const char *tmp;
776 xs *mid = mastoapi_id(msg); 801 xs *mid = mastoapi_id(msg);
777 802
778 xs_dict *st = xs_dict_new(); 803 xs_dict *st = xs_dict_new();
@@ -824,14 +849,14 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
824 849
825 { 850 {
826 xs_list *p = attach; 851 xs_list *p = attach;
827 xs_dict *v; 852 const xs_dict *v;
828 853
829 xs *matt = xs_list_new(); 854 xs *matt = xs_list_new();
830 855
831 while (xs_list_iter(&p, &v)) { 856 while (xs_list_iter(&p, &v)) {
832 char *type = xs_dict_get(v, "type"); 857 const char *type = xs_dict_get(v, "type");
833 char *href = xs_dict_get(v, "href"); 858 const char *href = xs_dict_get(v, "href");
834 char *name = xs_dict_get(v, "name"); 859 const char *name = xs_dict_get(v, "name");
835 860
836 if (xs_match(type, "image/*|video/*|Image|Video")) { /* */ 861 if (xs_match(type, "image/*|video/*|Image|Video")) { /* */
837 xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt)); 862 xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt));
@@ -857,7 +882,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
857 xs *ml = xs_list_new(); 882 xs *ml = xs_list_new();
858 xs *htl = xs_list_new(); 883 xs *htl = xs_list_new();
859 xs *eml = xs_list_new(); 884 xs *eml = xs_list_new();
860 xs_list *tag = xs_dict_get(msg, "tag"); 885 const xs_list *tag = xs_dict_get(msg, "tag");
861 int n = 0; 886 int n = 0;
862 887
863 xs *tag_list = NULL; 888 xs *tag_list = NULL;
@@ -873,9 +898,10 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
873 tag_list = xs_list_new(); 898 tag_list = xs_list_new();
874 899
875 tag = tag_list; 900 tag = tag_list;
876 xs_dict *v; 901 const xs_dict *v;
877 902
878 while (xs_list_iter(&tag, &v)) { 903 int c = 0;
904 while (xs_list_next(tag, &v, &c)) {
879 const char *type = xs_dict_get(v, "type"); 905 const char *type = xs_dict_get(v, "type");
880 906
881 if (xs_is_null(type)) 907 if (xs_is_null(type))
@@ -984,7 +1010,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
984 xs *irt_mid = mastoapi_id(irto); 1010 xs *irt_mid = mastoapi_id(irto);
985 st = xs_dict_set(st, "in_reply_to_id", irt_mid); 1011 st = xs_dict_set(st, "in_reply_to_id", irt_mid);
986 1012
987 char *at = NULL; 1013 const char *at = NULL;
988 if (!xs_is_null(at = get_atto(irto))) { 1014 if (!xs_is_null(at = get_atto(irto))) {
989 xs *at_md5 = xs_md5_hex(at, strlen(at)); 1015 xs *at_md5 = xs_md5_hex(at, strlen(at));
990 st = xs_dict_set(st, "in_reply_to_account_id", at_md5); 1016 st = xs_dict_set(st, "in_reply_to_account_id", at_md5);
@@ -994,7 +1020,10 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
994 1020
995 st = xs_dict_append(st, "reblog", xs_stock(XSTYPE_NULL)); 1021 st = xs_dict_append(st, "reblog", xs_stock(XSTYPE_NULL));
996 st = xs_dict_append(st, "card", xs_stock(XSTYPE_NULL)); 1022 st = xs_dict_append(st, "card", xs_stock(XSTYPE_NULL));
997 st = xs_dict_append(st, "language", xs_stock(XSTYPE_NULL)); 1023 st = xs_dict_append(st, "language", "en");
1024
1025 st = xs_dict_append(st, "filtered", xs_stock(XSTYPE_LIST));
1026 st = xs_dict_append(st, "muted", xs_stock(XSTYPE_FALSE));
998 1027
999 tmp = xs_dict_get(msg, "sourceContent"); 1028 tmp = xs_dict_get(msg, "sourceContent");
1000 if (xs_is_null(tmp)) 1029 if (xs_is_null(tmp))
@@ -1093,7 +1122,7 @@ int process_auth_token(snac *snac, const xs_dict *req)
1093/* processes an authorization token, if there is one */ 1122/* processes an authorization token, if there is one */
1094{ 1123{
1095 int logged_in = 0; 1124 int logged_in = 0;
1096 char *v; 1125 const char *v;
1097 1126
1098 /* if there is an authorization field, try to validate it */ 1127 /* if there is an authorization field, try to validate it */
1099 if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) { 1128 if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) {
@@ -1131,7 +1160,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1131 return 0; 1160 return 0;
1132 1161
1133 int status = 404; 1162 int status = 404;
1134 xs_dict *args = xs_dict_get(req, "q_vars"); 1163 const xs_dict *args = xs_dict_get(req, "q_vars");
1135 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 1164 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
1136 1165
1137 snac snac1 = {0}; 1166 snac snac1 = {0};
@@ -1157,7 +1186,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1157 acct = xs_dict_append(acct, "source", src); 1186 acct = xs_dict_append(acct, "source", src);
1158 1187
1159 xs *avatar = NULL; 1188 xs *avatar = NULL;
1160 char *av = xs_dict_get(snac1.config, "avatar"); 1189 const char *av = xs_dict_get(snac1.config, "avatar");
1161 1190
1162 if (xs_is_null(av) || *av == '\0') 1191 if (xs_is_null(av) || *av == '\0')
1163 avatar = xs_fmt("%s/susie.png", srv_baseurl); 1192 avatar = xs_fmt("%s/susie.png", srv_baseurl);
@@ -1168,7 +1197,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1168 acct = xs_dict_append(acct, "avatar_static", avatar); 1197 acct = xs_dict_append(acct, "avatar_static", avatar);
1169 1198
1170 xs *header = NULL; 1199 xs *header = NULL;
1171 char *hd = xs_dict_get(snac1.config, "header"); 1200 const char *hd = xs_dict_get(snac1.config, "header");
1172 1201
1173 if (!xs_is_null(hd)) 1202 if (!xs_is_null(hd))
1174 header = xs_dup(hd); 1203 header = xs_dup(hd);
@@ -1178,11 +1207,11 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1178 acct = xs_dict_append(acct, "header", header); 1207 acct = xs_dict_append(acct, "header", header);
1179 acct = xs_dict_append(acct, "header_static", header); 1208 acct = xs_dict_append(acct, "header_static", header);
1180 1209
1181 xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); 1210 const xs_dict *metadata = xs_dict_get(snac1.config, "metadata");
1182 if (xs_type(metadata) == XSTYPE_DICT) { 1211 if (xs_type(metadata) == XSTYPE_DICT) {
1183 xs *fields = xs_list_new(); 1212 xs *fields = xs_list_new();
1184 xs_str *k; 1213 const xs_str *k;
1185 xs_str *v; 1214 const xs_str *v;
1186 1215
1187 xs_dict *val_links = snac1.links; 1216 xs_dict *val_links = snac1.links;
1188 if (xs_is_null(val_links)) 1217 if (xs_is_null(val_links))
@@ -1192,7 +1221,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1192 while (xs_dict_next(metadata, &k, &v, &c)) { 1221 while (xs_dict_next(metadata, &k, &v, &c)) {
1193 xs *val_date = NULL; 1222 xs *val_date = NULL;
1194 1223
1195 xs_number *verified_time = xs_dict_get(val_links, v); 1224 const xs_number *verified_time = xs_dict_get(val_links, v);
1196 if (xs_type(verified_time) == XSTYPE_NUMBER) { 1225 if (xs_type(verified_time) == XSTYPE_NUMBER) {
1197 time_t t = xs_number_get(verified_time); 1226 time_t t = xs_number_get(verified_time);
1198 1227
@@ -1258,13 +1287,13 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1258 else 1287 else
1259 if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/ 1288 if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/
1260 /* lookup an account */ 1289 /* lookup an account */
1261 char *acct = xs_dict_get(args, "acct"); 1290 const char *acct = xs_dict_get(args, "acct");
1262 1291
1263 if (!xs_is_null(acct)) { 1292 if (!xs_is_null(acct)) {
1264 xs *s = xs_strip_chars_i(xs_dup(acct), "@"); 1293 xs *s = xs_strip_chars_i(xs_dup(acct), "@");
1265 xs *l = xs_split_n(s, "@", 1); 1294 xs *l = xs_split_n(s, "@", 1);
1266 char *uid = xs_list_get(l, 0); 1295 const char *uid = xs_list_get(l, 0);
1267 char *host = xs_list_get(l, 1); 1296 const char *host = xs_list_get(l, 1);
1268 1297
1269 if (uid && (!host || strcmp(host, xs_dict_get(srv_config, "host")) == 0)) { 1298 if (uid && (!host || strcmp(host, xs_dict_get(srv_config, "host")) == 0)) {
1270 snac user; 1299 snac user;
@@ -1305,7 +1334,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1305 xs *wers = follower_list(&snac1); 1334 xs *wers = follower_list(&snac1);
1306 xs *ulst = user_list(); 1335 xs *ulst = user_list();
1307 xs_list *p; 1336 xs_list *p;
1308 xs_str *v; 1337 const xs_str *v;
1309 xs_set seen; 1338 xs_set seen;
1310 1339
1311 xs_set_init(&seen); 1340 xs_set_init(&seen);
@@ -1381,7 +1410,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1381 /* the public list of posts of a user */ 1410 /* the public list of posts of a user */
1382 xs *timeline = timeline_simple_list(&snac2, "public", 0, 256); 1411 xs *timeline = timeline_simple_list(&snac2, "public", 0, 256);
1383 xs_list *p = timeline; 1412 xs_list *p = timeline;
1384 xs_str *v; 1413 const xs_str *v;
1385 1414
1386 out = xs_list_new(); 1415 out = xs_list_new();
1387 1416
@@ -1446,7 +1475,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1446 1475
1447 xs *out = xs_list_new(); 1476 xs *out = xs_list_new();
1448 xs_list *p = timeline; 1477 xs_list *p = timeline;
1449 xs_str *v; 1478 const xs_str *v;
1450 1479
1451 while (xs_list_iter(&p, &v) && cnt < limit) { 1480 while (xs_list_iter(&p, &v) && cnt < limit) {
1452 xs *msg = NULL; 1481 xs *msg = NULL;
@@ -1479,7 +1508,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1479 /* discard non-Notes */ 1508 /* discard non-Notes */
1480 const char *id = xs_dict_get(msg, "id"); 1509 const char *id = xs_dict_get(msg, "id");
1481 const char *type = xs_dict_get(msg, "type"); 1510 const char *type = xs_dict_get(msg, "type");
1482 if (!xs_match(type, "Note|Question|Page|Article|Video")) 1511 if (!xs_match(type, POSTLIKE_OBJECT_TYPE))
1483 continue; 1512 continue;
1484 1513
1485 const char *from = NULL; 1514 const char *from = NULL;
@@ -1550,7 +1579,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1550 xs *timeline = timeline_instance_list(0, limit); 1579 xs *timeline = timeline_instance_list(0, limit);
1551 xs *out = xs_list_new(); 1580 xs *out = xs_list_new();
1552 xs_list *p = timeline; 1581 xs_list *p = timeline;
1553 xs_str *md5; 1582 const xs_str *md5;
1554 1583
1555 snac *user = NULL; 1584 snac *user = NULL;
1556 if (logged_in) 1585 if (logged_in)
@@ -1599,12 +1628,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1599 1628
1600 /* get the tag */ 1629 /* get the tag */
1601 xs *l = xs_split(cmd, "/"); 1630 xs *l = xs_split(cmd, "/");
1602 char *tag = xs_list_get(l, -1); 1631 const char *tag = xs_list_get(l, -1);
1603 1632
1604 xs *timeline = tag_search(tag, 0, limit); 1633 xs *timeline = tag_search(tag, 0, limit);
1605 xs *out = xs_list_new(); 1634 xs *out = xs_list_new();
1606 xs_list *p = timeline; 1635 xs_list *p = timeline;
1607 xs_str *md5; 1636 const xs_str *md5;
1608 1637
1609 while (xs_list_iter(&p, &md5) && cnt < limit) { 1638 while (xs_list_iter(&p, &md5) && cnt < limit) {
1610 xs *msg = NULL; 1639 xs *msg = NULL;
@@ -1635,6 +1664,77 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1635 status = 200; 1664 status = 200;
1636 } 1665 }
1637 else 1666 else
1667 if (xs_startswith(cmd, "/v1/timelines/list/")) { /** **/
1668 /* get the list id */
1669 if (logged_in) {
1670 xs *l = xs_split(cmd, "/");
1671 const char *list = xs_list_get(l, -1);
1672
1673 xs *timeline = list_timeline(&snac1, list, 0, 2048);
1674 xs *out = xs_list_new();
1675 int c = 0;
1676 const char *md5;
1677
1678 while (xs_list_next(timeline, &md5, &c)) {
1679 xs *msg = NULL;
1680
1681 /* get the entry */
1682 if (!valid_status(timeline_get_by_md5(&snac1, md5, &msg)))
1683 continue;
1684
1685 /* discard non-Notes */
1686 const char *id = xs_dict_get(msg, "id");
1687 const char *type = xs_dict_get(msg, "type");
1688 if (!xs_match(type, POSTLIKE_OBJECT_TYPE))
1689 continue;
1690
1691 const char *from = NULL;
1692 if (strcmp(type, "Page") == 0)
1693 from = xs_dict_get(msg, "audience");
1694
1695 if (from == NULL)
1696 from = get_atto(msg);
1697
1698 if (from == NULL)
1699 continue;
1700
1701 /* is this message from a person we don't follow? */
1702 if (strcmp(from, snac1.actor) && !following_check(&snac1, from)) {
1703 /* discard if it was not boosted */
1704 xs *idx = object_announces(id);
1705
1706 if (xs_list_len(idx) == 0)
1707 continue;
1708 }
1709
1710 /* discard notes from muted morons */
1711 if (is_muted(&snac1, from))
1712 continue;
1713
1714 /* discard hidden notes */
1715 if (is_hidden(&snac1, id))
1716 continue;
1717
1718 /* if it has a name and it's not a Page or a Video,
1719 it's a poll vote, so discard it */
1720 if (!xs_is_null(xs_dict_get(msg, "name")) && !xs_match(type, "Page|Video"))
1721 continue;
1722
1723 /* convert the Note into a Mastodon status */
1724 xs *st = mastoapi_status(&snac1, msg);
1725
1726 if (st != NULL)
1727 out = xs_list_append(out, st);
1728 }
1729
1730 *body = xs_json_dumps(out, 4);
1731 *ctype = "application/json";
1732 status = 200;
1733 }
1734 else
1735 status = 421;
1736 }
1737 else
1638 if (strcmp(cmd, "/v1/conversations") == 0) { /** **/ 1738 if (strcmp(cmd, "/v1/conversations") == 0) { /** **/
1639 /* TBD */ 1739 /* TBD */
1640 *body = xs_dup("[]"); 1740 *body = xs_dup("[]");
@@ -1647,8 +1747,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1647 xs *l = notify_list(&snac1, 0, 64); 1747 xs *l = notify_list(&snac1, 0, 64);
1648 xs *out = xs_list_new(); 1748 xs *out = xs_list_new();
1649 xs_list *p = l; 1749 xs_list *p = l;
1650 xs_dict *v; 1750 const xs_dict *v;
1651 xs_list *excl = xs_dict_get(args, "exclude_types[]"); 1751 const xs_list *excl = xs_dict_get(args, "exclude_types[]");
1652 1752
1653 while (xs_list_iter(&p, &v)) { 1753 while (xs_list_iter(&p, &v)) {
1654 xs *noti = notify_get(&snac1, v); 1754 xs *noti = notify_get(&snac1, v);
@@ -1753,11 +1853,88 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1753 status = 200; 1853 status = 200;
1754 } 1854 }
1755 else 1855 else
1756 if (strcmp(cmd, "/v1/lists") == 0) { /** **/ 1856 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/
1757 /* snac does not support lists */ 1857 if (logged_in) {
1758 *body = xs_dup("[]"); 1858 xs *lol = list_maint(&snac1, NULL, 0);
1759 *ctype = "application/json"; 1859 xs *l = xs_list_new();
1760 status = 200; 1860 int c = 0;
1861 const xs_list *li;
1862
1863 while (xs_list_next(lol, &li, &c)) {
1864 xs *d = xs_dict_new();
1865
1866 d = xs_dict_append(d, "id", xs_list_get(li, 0));
1867 d = xs_dict_append(d, "title", xs_list_get(li, 1));
1868 d = xs_dict_append(d, "replies_policy", "list");
1869 d = xs_dict_append(d, "exclusive", xs_stock(XSTYPE_FALSE));
1870
1871 l = xs_list_append(l, d);
1872 }
1873
1874 *body = xs_json_dumps(l, 4);
1875 *ctype = "application/json";
1876 status = 200;
1877 }
1878 }
1879 else
1880 if (xs_startswith(cmd, "/v1/lists/")) { /** list information **/
1881 if (logged_in) {
1882 xs *l = xs_split(cmd, "/");
1883 const char *p = xs_list_get(l, -1);
1884
1885 if (p) {
1886 if (strcmp(p, "accounts") == 0) {
1887 p = xs_list_get(l, -2);
1888
1889 if (p && xs_is_hex(p)) {
1890 xs *actors = list_content(&snac1, p, NULL, 0);
1891 xs *out = xs_list_new();
1892 int c = 0;
1893 const char *v;
1894
1895 while (xs_list_next(actors, &v, &c)) {
1896 xs *actor = NULL;
1897
1898 if (valid_status(object_get_by_md5(v, &actor))) {
1899 xs *acct = mastoapi_account(actor);
1900 out = xs_list_append(out, acct);
1901 }
1902 }
1903
1904 *body = xs_json_dumps(out, 4);
1905 *ctype = "application/json";
1906 status = 200;
1907 }
1908 }
1909 else
1910 if (xs_is_hex(p)) {
1911 xs *out = xs_list_new();
1912 xs *lol = list_maint(&snac1, NULL, 0);
1913 int c = 0;
1914 const xs_list *v;
1915
1916 while (xs_list_next(lol, &v, &c)) {
1917 const char *id = xs_list_get(v, 0);
1918
1919 if (id && strcmp(id, p) == 0) {
1920 xs *d = xs_dict_new();
1921
1922 d = xs_dict_append(d, "id", p);
1923 d = xs_dict_append(d, "title", xs_list_get(v, 1));
1924 d = xs_dict_append(d, "replies_policy", "list");
1925 d = xs_dict_append(d, "exclusive", xs_stock(XSTYPE_FALSE));
1926
1927 out = xs_list_append(out, d);
1928 break;
1929 }
1930 }
1931
1932 *body = xs_json_dumps(out, 4);
1933 *ctype = "application/json";
1934 status = 200;
1935 }
1936 }
1937 }
1761 } 1938 }
1762 else 1939 else
1763 if (strcmp(cmd, "/v1/scheduled_statuses") == 0) { /** **/ 1940 if (strcmp(cmd, "/v1/scheduled_statuses") == 0) { /** **/
@@ -1928,7 +2105,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1928 xs *anc = xs_list_new(); 2105 xs *anc = xs_list_new();
1929 xs *des = xs_list_new(); 2106 xs *des = xs_list_new();
1930 xs_list *p; 2107 xs_list *p;
1931 xs_str *v; 2108 const xs_str *v;
1932 char pid[64]; 2109 char pid[64];
1933 2110
1934 /* build the [grand]parent list, moving up */ 2111 /* build the [grand]parent list, moving up */
@@ -1982,7 +2159,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1982 l = object_likes(xs_dict_get(msg, "id")); 2159 l = object_likes(xs_dict_get(msg, "id"));
1983 2160
1984 xs_list *p = l; 2161 xs_list *p = l;
1985 xs_str *v; 2162 const xs_str *v;
1986 2163
1987 while (xs_list_iter(&p, &v)) { 2164 while (xs_list_iter(&p, &v)) {
1988 xs *actor2 = NULL; 2165 xs *actor2 = NULL;
@@ -2052,8 +2229,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2052 /* reply something only for offset 0; otherwise, 2229 /* reply something only for offset 0; otherwise,
2053 apps like Tusky keep asking again and again */ 2230 apps like Tusky keep asking again and again */
2054 2231
2055 if (!xs_is_null(q) && !xs_is_null(type)) { 2232 if (!xs_is_null(q)) {
2056 if (strcmp(type, "accounts") == 0) { 2233 if (xs_is_null(type) || strcmp(type, "accounts") == 0) {
2057 /* do a webfinger query */ 2234 /* do a webfinger query */
2058 char *actor = NULL; 2235 char *actor = NULL;
2059 char *user = NULL; 2236 char *user = NULL;
@@ -2068,8 +2245,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2068 } 2245 }
2069 } 2246 }
2070 } 2247 }
2071 else 2248
2072 if (strcmp(type, "hashtags") == 0) { 2249 if (xs_is_null(type) || strcmp(type, "hashtags") == 0) {
2073 /* search this tag */ 2250 /* search this tag */
2074 xs *tl = tag_search((char *)q, 0, 1); 2251 xs *tl = tag_search((char *)q, 0, 1);
2075 2252
@@ -2084,6 +2261,26 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2084 htl = xs_list_append(htl, d); 2261 htl = xs_list_append(htl, d);
2085 } 2262 }
2086 } 2263 }
2264
2265 if (xs_is_null(type) || strcmp(type, "statuses") == 0) {
2266 int to = 0;
2267 int cnt = 40;
2268 xs *tl = content_search(&snac1, q, 1, 0, cnt, 0, &to);
2269 int c = 0;
2270 const char *v;
2271
2272 while (xs_list_next(tl, &v, &c) && --cnt) {
2273 xs *post = NULL;
2274
2275 if (!valid_status(timeline_get_by_md5(&snac1, v, &post)))
2276 continue;
2277
2278 xs *s = mastoapi_status(&snac1, post);
2279
2280 if (!xs_is_null(s))
2281 stl = xs_list_append(stl, s);
2282 }
2283 }
2087 } 2284 }
2088 } 2285 }
2089 2286
@@ -2119,11 +2316,9 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2119 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2316 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2120 return 0; 2317 return 0;
2121 2318
2122 srv_debug(1, xs_fmt("mastoapi_post_handler %s", q_path));
2123
2124 int status = 404; 2319 int status = 404;
2125 xs *args = NULL; 2320 xs *args = NULL;
2126 char *i_ctype = xs_dict_get(req, "content-type"); 2321 const char *i_ctype = xs_dict_get(req, "content-type");
2127 2322
2128 if (i_ctype && xs_startswith(i_ctype, "application/json")) { 2323 if (i_ctype && xs_startswith(i_ctype, "application/json")) {
2129 if (!xs_is_null(payload)) 2324 if (!xs_is_null(payload))
@@ -2238,7 +2433,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2238 } 2433 }
2239 2434
2240 xs_list *p = mi; 2435 xs_list *p = mi;
2241 xs_str *v; 2436 const xs_str *v;
2242 2437
2243 while (xs_list_iter(&p, &v)) { 2438 while (xs_list_iter(&p, &v)) {
2244 xs *l = xs_list_new(); 2439 xs *l = xs_list_new();
@@ -2296,7 +2491,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2296 mid = MID_TO_MD5(mid); 2491 mid = MID_TO_MD5(mid);
2297 2492
2298 if (valid_status(timeline_get_by_md5(&snac, mid, &msg))) { 2493 if (valid_status(timeline_get_by_md5(&snac, mid, &msg))) {
2299 char *id = xs_dict_get(msg, "id"); 2494 const char *id = xs_dict_get(msg, "id");
2300 2495
2301 if (op == NULL) { 2496 if (op == NULL) {
2302 /* no operation (?) */ 2497 /* no operation (?) */
@@ -2402,7 +2597,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2402 if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/ 2597 if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/
2403 /* I don't know what I'm doing */ 2598 /* I don't know what I'm doing */
2404 if (logged_in) { 2599 if (logged_in) {
2405 char *v; 2600 const char *v;
2406 2601
2407 xs *wpush = xs_dict_new(); 2602 xs *wpush = xs_dict_new();
2408 2603
@@ -2574,7 +2769,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2574 const char *id = xs_dict_get(msg, "id"); 2769 const char *id = xs_dict_get(msg, "id");
2575 const char *atto = get_atto(msg); 2770 const char *atto = get_atto(msg);
2576 2771
2577 xs_list *opts = xs_dict_get(msg, "oneOf"); 2772 const xs_list *opts = xs_dict_get(msg, "oneOf");
2578 if (opts == NULL) 2773 if (opts == NULL)
2579 opts = xs_dict_get(msg, "anyOf"); 2774 opts = xs_dict_get(msg, "anyOf");
2580 2775
@@ -2582,15 +2777,16 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2582 } 2777 }
2583 else 2778 else
2584 if (strcmp(op, "votes") == 0) { 2779 if (strcmp(op, "votes") == 0) {
2585 xs_list *choices = xs_dict_get(args, "choices[]"); 2780 const xs_list *choices = xs_dict_get(args, "choices[]");
2586 2781
2587 if (xs_is_null(choices)) 2782 if (xs_is_null(choices))
2588 choices = xs_dict_get(args, "choices"); 2783 choices = xs_dict_get(args, "choices");
2589 2784
2590 if (xs_type(choices) == XSTYPE_LIST) { 2785 if (xs_type(choices) == XSTYPE_LIST) {
2591 xs_str *v; 2786 const xs_str *v;
2592 2787
2593 while (xs_list_iter(&choices, &v)) { 2788 int c = 0;
2789 while (xs_list_next(choices, &v, &c)) {
2594 int io = atoi(v); 2790 int io = atoi(v);
2595 const xs_dict *o = xs_list_get(opts, io); 2791 const xs_dict *o = xs_list_get(opts, io);
2596 2792
@@ -2621,19 +2817,73 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2621 else 2817 else
2622 status = 401; 2818 status = 401;
2623 } 2819 }
2820 else
2821 if (strcmp(cmd, "/v1/lists") == 0) {
2822 if (logged_in) {
2823 const char *title = xs_dict_get(args, "title");
2824
2825 if (xs_type(title) == XSTYPE_STRING) {
2826 /* add the list */
2827 xs *out = xs_dict_new();
2828
2829 if (xs_type(list_maint(&snac, title, 1)) == XSTYPE_TRUE) {
2830 out = xs_dict_append(out, "title", title);
2831 out = xs_dict_append(out, "replies_policy", xs_dict_get_def(args, "replies_policy", "list"));
2832 out = xs_dict_append(out, "exclusive", xs_stock(XSTYPE_FALSE));
2833
2834 status = 200;
2835 }
2836 else {
2837 out = xs_dict_append(out, "error", "cannot create list");
2838 status = 422;
2839 }
2840
2841 *body = xs_json_dumps(out, 4);
2842 *ctype = "application/json";
2843 }
2844 else
2845 status = 422;
2846 }
2847 }
2848 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/
2849 if (logged_in) {
2850 xs *l = xs_split(cmd, "/");
2851 const char *op = xs_list_get(l, -1);
2852 const char *id = xs_list_get(l, -2);
2853
2854 if (op && id && xs_is_hex(id)) {
2855 if (strcmp(op, "accounts") == 0) {
2856 const xs_list *accts = xs_dict_get(args, "account_ids[]");
2857 int c = 0;
2858 const char *v;
2859
2860 while (xs_list_next(accts, &v, &c)) {
2861 list_content(&snac, id, v, 1);
2862 }
2863
2864 status = 200;
2865 }
2866 }
2867 }
2868 else
2869 status = 422;
2870 }
2624 2871
2625 /* user cleanup */ 2872 /* user cleanup */
2626 if (logged_in) 2873 if (logged_in)
2627 user_free(&snac); 2874 user_free(&snac);
2628 2875
2876 srv_debug(1, xs_fmt("mastoapi_post_handler %s %d", q_path, status));
2877
2629 return status; 2878 return status;
2630} 2879}
2631 2880
2632 2881
2633int mastoapi_delete_handler(const xs_dict *req, const char *q_path, 2882int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2634 char **body, int *b_size, char **ctype) { 2883 const char *payload, int p_size,
2635 2884 char **body, int *b_size, char **ctype)
2636 (void)req; 2885{
2886 (void)p_size;
2637 (void)body; 2887 (void)body;
2638 (void)b_size; 2888 (void)b_size;
2639 (void)ctype; 2889 (void)ctype;
@@ -2641,13 +2891,76 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2641 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2891 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2642 return 0; 2892 return 0;
2643 2893
2644 srv_debug(1, xs_fmt("mastoapi_delete_handler %s", q_path)); 2894 int status = 404;
2895 xs *args = NULL;
2896 const char *i_ctype = xs_dict_get(req, "content-type");
2897
2898 if (i_ctype && xs_startswith(i_ctype, "application/json")) {
2899 if (!xs_is_null(payload))
2900 args = xs_json_loads(payload);
2901 }
2902 else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded"))
2903 {
2904 // Some apps send form data instead of json so we should cater for those
2905 if (!xs_is_null(payload)) {
2906 xs *upl = xs_url_dec(payload);
2907 args = xs_url_vars(upl);
2908 }
2909 }
2910 else
2911 args = xs_dup(xs_dict_get(req, "p_vars"));
2912
2913 if (args == NULL)
2914 return 400;
2915
2916 snac snac = {0};
2917 int logged_in = process_auth_token(&snac, req);
2918
2645 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 2919 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
2920
2646 if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/ 2921 if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/
2647 // pretend we deleted it, since it doesn't exist anyway 2922 // pretend we deleted it, since it doesn't exist anyway
2648 return 200; 2923 status = 200;
2924 }
2925 else
2926 if (xs_startswith(cmd, "/v1/lists/")) {
2927 if (logged_in) {
2928 xs *l = xs_split(cmd, "/");
2929 const char *p = xs_list_get(l, -1);
2930
2931 if (p) {
2932 if (strcmp(p, "accounts") == 0) {
2933 /* delete account from list */
2934 p = xs_list_get(l, -2);
2935 const xs_list *accts = xs_dict_get(args, "account_ids[]");
2936 int c = 0;
2937 const char *v;
2938
2939 while (xs_list_next(accts, &v, &c)) {
2940 list_content(&snac, p, v, 2);
2941 }
2942 }
2943 else {
2944 /* delete list */
2945 if (xs_is_hex(p)) {
2946 list_maint(&snac, p, 2);
2947 }
2948 }
2949 }
2950
2951 status = 200;
2952 }
2953 else
2954 status = 401;
2649 } 2955 }
2650 return 0; 2956
2957 /* user cleanup */
2958 if (logged_in)
2959 user_free(&snac);
2960
2961 srv_debug(1, xs_fmt("mastoapi_delete_handler %s %d", q_path, status));
2962
2963 return status;
2651} 2964}
2652 2965
2653 2966
@@ -2663,7 +2976,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
2663 2976
2664 int status = 404; 2977 int status = 404;
2665 xs *args = NULL; 2978 xs *args = NULL;
2666 char *i_ctype = xs_dict_get(req, "content-type"); 2979 const char *i_ctype = xs_dict_get(req, "content-type");
2667 2980
2668 if (i_ctype && xs_startswith(i_ctype, "application/json")) { 2981 if (i_ctype && xs_startswith(i_ctype, "application/json")) {
2669 if (!xs_is_null(payload)) 2982 if (!xs_is_null(payload))
@@ -2770,7 +3083,7 @@ void mastoapi_purge(void)
2770 xs *spec = xs_fmt("%s/app/" "*.json", srv_basedir); 3083 xs *spec = xs_fmt("%s/app/" "*.json", srv_basedir);
2771 xs *files = xs_glob(spec, 1, 0); 3084 xs *files = xs_glob(spec, 1, 0);
2772 xs_list *p = files; 3085 xs_list *p = files;
2773 xs_str *v; 3086 const xs_str *v;
2774 3087
2775 time_t mt = time(NULL) - 3600; 3088 time_t mt = time(NULL) - 3600;
2776 3089
diff --git a/snac.h b/snac.h
index cac09a9..79e144a 100644
--- a/snac.h
+++ b/snac.h
@@ -1,7 +1,7 @@
1/* snac - A simple, minimalistic ActivityPub instance */ 1/* snac - A simple, minimalistic ActivityPub instance */
2/* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ 2/* copyright (c) 2022 - 2024 grunfink et al. / MIT license */
3 3
4#define VERSION "2.52-dev" 4#define VERSION "2.53"
5 5
6#define USER_AGENT "snac/" VERSION 6#define USER_AGENT "snac/" VERSION
7 7
@@ -29,6 +29,8 @@ extern int dbglevel;
29 29
30#define L(s) (s) 30#define L(s) (s)
31 31
32#define POSTLIKE_OBJECT_TYPE "Note|Question|Page|Article|Video|Event"
33
32int mkdirx(const char *pathname); 34int mkdirx(const char *pathname);
33 35
34int valid_status(int status); 36int valid_status(int status);
@@ -67,7 +69,7 @@ void snac_log(snac *user, xs_str *str);
67#define snac_debug(user, level, str) do { if (dbglevel >= (level)) \ 69#define snac_debug(user, level, str) do { if (dbglevel >= (level)) \
68 { snac_log((user), (str)); } } while (0) 70 { snac_log((user), (str)); } } while (0)
69 71
70int srv_open(char *basedir, int auto_upgrade); 72int srv_open(const char *basedir, int auto_upgrade);
71void srv_free(void); 73void srv_free(void);
72 74
73int user_open(snac *snac, const char *uid); 75int user_open(snac *snac, const char *uid);
@@ -86,7 +88,7 @@ void srv_archive(const char *direction, const char *url, xs_dict *req,
86 const char *body, int b_size); 88 const char *body, int b_size);
87void srv_archive_error(const char *prefix, const xs_str *err, 89void srv_archive_error(const char *prefix, const xs_str *err,
88 const xs_dict *req, const xs_val *data); 90 const xs_dict *req, const xs_val *data);
89void srv_archive_qitem(char *prefix, xs_dict *q_item); 91void srv_archive_qitem(const char *prefix, xs_dict *q_item);
90 92
91double mtime_nl(const char *fn, int *n_link); 93double mtime_nl(const char *fn, int *n_link);
92#define mtime(fn) mtime_nl(fn, NULL) 94#define mtime(fn) mtime_nl(fn, NULL)
@@ -137,13 +139,13 @@ double timeline_mtime(snac *snac);
137int timeline_touch(snac *snac); 139int timeline_touch(snac *snac);
138int timeline_here(snac *snac, const char *md5); 140int timeline_here(snac *snac, const char *md5);
139int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg); 141int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg);
140int timeline_del(snac *snac, char *id); 142int timeline_del(snac *snac, const char *id);
141xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show); 143xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show);
142xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show); 144xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show);
143int timeline_add(snac *snac, const char *id, const xs_dict *o_msg); 145int timeline_add(snac *snac, const char *id, const xs_dict *o_msg);
144int timeline_admire(snac *snac, const char *id, const char *admirer, int like); 146int timeline_admire(snac *snac, const char *id, const char *admirer, int like);
145 147
146xs_list *timeline_top_level(snac *snac, xs_list *list); 148xs_list *timeline_top_level(snac *snac, const xs_list *list);
147xs_list *local_list(snac *snac, int max); 149xs_list *local_list(snac *snac, int max);
148xs_list *timeline_instance_list(int skip, int show); 150xs_list *timeline_instance_list(int skip, int show);
149 151
@@ -172,9 +174,14 @@ void hide(snac *snac, const char *id);
172int is_hidden(snac *snac, const char *id); 174int is_hidden(snac *snac, const char *id);
173 175
174void tag_index(const char *id, const xs_dict *obj); 176void tag_index(const char *id, const xs_dict *obj);
175xs_list *tag_search(char *tag, int skip, int show); 177xs_list *tag_search(const char *tag, int skip, int show);
178
179xs_val *list_maint(snac *user, const char *list, int op);
180xs_list *list_timeline(snac *user, const char *list, int skip, int show);
181xs_val *list_content(snac *user, const char *list_id, const char *actor_md5, int op);
182void list_distribute(snac *user, const char *who, const xs_dict *post);
176 183
177int actor_add(const char *actor, xs_dict *msg); 184int actor_add(const char *actor, const xs_dict *msg);
178int actor_get(const char *actor, xs_dict **data); 185int actor_get(const char *actor, xs_dict **data);
179int actor_get_refresh(snac *user, const char *actor, xs_dict **data); 186int actor_get_refresh(snac *user, const char *actor, xs_dict **data);
180 187
@@ -209,22 +216,27 @@ int is_instance_blocked(const char *instance);
209int instance_block(const char *instance); 216int instance_block(const char *instance);
210int instance_unblock(const char *instance); 217int instance_unblock(const char *instance);
211 218
212int content_check(const char *file, const xs_dict *msg); 219int content_match(const char *file, const xs_dict *msg);
220xs_list *content_search(snac *user, const char *regex,
221 int priv, int skip, int show, int max_secs, int *timeout);
213 222
214void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries); 223void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries);
215void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries); 224void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries);
216void enqueue_output_raw(const char *keyid, const char *seckey, 225void enqueue_output_raw(const char *keyid, const char *seckey,
217 xs_dict *msg, xs_str *inbox, int retries, int p_status); 226 const xs_dict *msg, const xs_str *inbox,
218void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries, int p_status); 227 int retries, int p_status);
219void enqueue_output_by_actor(snac *snac, xs_dict *msg, const xs_str *actor, int retries); 228void enqueue_output(snac *snac, const xs_dict *msg,
220void enqueue_email(xs_str *msg, int retries); 229 const xs_str *inbox, int retries, int p_status);
230void enqueue_output_by_actor(snac *snac, const xs_dict *msg,
231 const xs_str *actor, int retries);
232void enqueue_email(const xs_str *msg, int retries);
221void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id); 233void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id);
222void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_token); 234void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_token);
223void enqueue_message(snac *snac, const xs_dict *msg); 235void enqueue_message(snac *snac, const xs_dict *msg);
224void enqueue_close_question(snac *user, const char *id, int end_secs); 236void enqueue_close_question(snac *user, const char *id, int end_secs);
237void enqueue_object_request(snac *user, const char *id, int forward_secs);
225void enqueue_verify_links(snac *user); 238void enqueue_verify_links(snac *user);
226void enqueue_actor_refresh(snac *user, const char *actor); 239void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs);
227void enqueue_request_replies(snac *user, const char *id);
228int was_question_voted(snac *user, const char *id); 240int was_question_voted(snac *user, const char *id);
229 241
230xs_list *user_queue(snac *snac); 242xs_list *user_queue(snac *snac);
@@ -237,16 +249,16 @@ void purge_all(void);
237 249
238xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, 250xs_dict *http_signed_request_raw(const char *keyid, const char *seckey,
239 const char *method, const char *url, 251 const char *method, const char *url,
240 xs_dict *headers, 252 const xs_dict *headers,
241 const char *body, int b_size, 253 const char *body, int b_size,
242 int *status, xs_str **payload, int *p_size, 254 int *status, xs_str **payload, int *p_size,
243 int timeout); 255 int timeout);
244xs_dict *http_signed_request(snac *snac, const char *method, const char *url, 256xs_dict *http_signed_request(snac *snac, const char *method, const char *url,
245 xs_dict *headers, 257 const xs_dict *headers,
246 const char *body, int b_size, 258 const char *body, int b_size,
247 int *status, xs_str **payload, int *p_size, 259 int *status, xs_str **payload, int *p_size,
248 int timeout); 260 int timeout);
249int check_signature(xs_dict *req, xs_str **err); 261int check_signature(const xs_dict *req, xs_str **err);
250 262
251srv_state *srv_state_op(xs_str **fname, int op); 263srv_state *srv_state_op(xs_str **fname, int op);
252void httpd(void); 264void httpd(void);
@@ -260,21 +272,21 @@ const char *default_avatar_base64(void);
260 272
261xs_str *process_tags(snac *snac, const char *content, xs_list **tag); 273xs_str *process_tags(snac *snac, const char *content, xs_list **tag);
262 274
263char *get_atto(const xs_dict *msg); 275const char *get_atto(const xs_dict *msg);
264xs_list *get_attachments(const xs_dict *msg); 276xs_list *get_attachments(const xs_dict *msg);
265 277
266xs_dict *msg_admiration(snac *snac, char *object, char *type); 278xs_dict *msg_admiration(snac *snac, const char *object, const char *type);
267xs_dict *msg_repulsion(snac *user, char *id, char *type); 279xs_dict *msg_repulsion(snac *user, const char *id, const char *type);
268xs_dict *msg_create(snac *snac, const xs_dict *object); 280xs_dict *msg_create(snac *snac, const xs_dict *object);
269xs_dict *msg_follow(snac *snac, const char *actor); 281xs_dict *msg_follow(snac *snac, const char *actor);
270 282
271xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, 283xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
272 xs_str *in_reply_to, xs_list *attach, int priv); 284 const xs_str *in_reply_to, const xs_list *attach, int priv);
273 285
274xs_dict *msg_undo(snac *snac, char *object); 286xs_dict *msg_undo(snac *snac, const xs_val *object);
275xs_dict *msg_delete(snac *snac, char *id); 287xs_dict *msg_delete(snac *snac, const char *id);
276xs_dict *msg_actor(snac *snac); 288xs_dict *msg_actor(snac *snac);
277xs_dict *msg_update(snac *snac, xs_dict *object); 289xs_dict *msg_update(snac *snac, const xs_dict *object);
278xs_dict *msg_ping(snac *user, const char *rcpt); 290xs_dict *msg_ping(snac *user, const char *rcpt);
279xs_dict *msg_pong(snac *user, const char *rcpt, const char *object); 291xs_dict *msg_pong(snac *user, const char *rcpt, const char *object);
280xs_dict *msg_question(snac *user, const char *content, xs_list *attach, 292xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
@@ -282,7 +294,6 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
282 294
283int activitypub_request(snac *snac, const char *url, xs_dict **data); 295int activitypub_request(snac *snac, const char *url, xs_dict **data);
284int actor_request(snac *user, const char *actor, xs_dict **data); 296int actor_request(snac *user, const char *actor, xs_dict **data);
285void timeline_request_replies(snac *user, const char *id);
286int send_to_inbox_raw(const char *keyid, const char *seckey, 297int send_to_inbox_raw(const char *keyid, const char *seckey,
287 const xs_str *inbox, const xs_dict *msg, 298 const xs_str *inbox, const xs_dict *msg,
288 xs_val **payload, int *p_size, int timeout); 299 xs_val **payload, int *p_size, int timeout);
@@ -312,13 +323,14 @@ xs_str *encode_html(const char *str);
312 323
313xs_str *html_timeline(snac *user, const xs_list *list, int read_only, 324xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
314 int skip, int show, int show_more, 325 int skip, int show, int show_more,
315 char *tag, char *page, int utl); 326 char *title, char *page, int utl);
316 327
317int html_get_handler(const xs_dict *req, const char *q_path, 328int html_get_handler(const xs_dict *req, const char *q_path,
318 char **body, int *b_size, char **ctype, xs_str **etag); 329 char **body, int *b_size, char **ctype, xs_str **etag);
319int html_post_handler(const xs_dict *req, const char *q_path, 330int html_post_handler(const xs_dict *req, const char *q_path,
320 char *payload, int p_size, 331 char *payload, int p_size,
321 char **body, int *b_size, char **ctype); 332 char **body, int *b_size, char **ctype);
333xs_str *timeline_to_rss(snac *user, const xs_list *timeline, char *title, char *link, char *desc);
322 334
323int snac_init(const char *_basedir); 335int snac_init(const char *_basedir);
324int adduser(const char *uid); 336int adduser(const char *uid);
@@ -337,11 +349,12 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
337 char **body, int *b_size, char **ctype); 349 char **body, int *b_size, char **ctype);
338int mastoapi_get_handler(const xs_dict *req, const char *q_path, 350int mastoapi_get_handler(const xs_dict *req, const char *q_path,
339 char **body, int *b_size, char **ctype); 351 char **body, int *b_size, char **ctype);
340int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
341 char **body, int *b_size, char **ctype);
342int mastoapi_post_handler(const xs_dict *req, const char *q_path, 352int mastoapi_post_handler(const xs_dict *req, const char *q_path,
343 const char *payload, int p_size, 353 const char *payload, int p_size,
344 char **body, int *b_size, char **ctype); 354 char **body, int *b_size, char **ctype);
355int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
356 const char *payload, int p_size,
357 char **body, int *b_size, char **ctype);
345int mastoapi_put_handler(const xs_dict *req, const char *q_path, 358int mastoapi_put_handler(const xs_dict *req, const char *q_path,
346 const char *payload, int p_size, 359 const char *payload, int p_size,
347 char **body, int *b_size, char **ctype); 360 char **body, int *b_size, char **ctype);
diff --git a/upgrade.c b/upgrade.c
index 7510ac8..847c62e 100644
--- a/upgrade.c
+++ b/upgrade.c
@@ -18,7 +18,7 @@ int snac_upgrade(xs_str **error)
18 double f = 0.0; 18 double f = 0.0;
19 19
20 for (;;) { 20 for (;;) {
21 char *layout = xs_dict_get(srv_config, "layout"); 21 const char *layout = xs_dict_get(srv_config, "layout");
22 double nf; 22 double nf;
23 23
24 f = nf = xs_number_get(layout); 24 f = nf = xs_number_get(layout);
@@ -43,7 +43,8 @@ int snac_upgrade(xs_str **error)
43 else 43 else
44 if (f < 2.2) { 44 if (f < 2.2) {
45 xs *users = user_list(); 45 xs *users = user_list();
46 char *p, *v; 46 char *p;
47 const char *v;
47 48
48 p = users; 49 p = users;
49 while (xs_list_iter(&p, &v)) { 50 while (xs_list_iter(&p, &v)) {
@@ -52,12 +53,13 @@ int snac_upgrade(xs_str **error)
52 if (user_open(&snac, v)) { 53 if (user_open(&snac, v)) {
53 xs *spec = xs_fmt("%s/actors/" "*.json", snac.basedir); 54 xs *spec = xs_fmt("%s/actors/" "*.json", snac.basedir);
54 xs *list = xs_glob(spec, 0, 0); 55 xs *list = xs_glob(spec, 0, 0);
55 char *g, *fn; 56 char *g;
57 const char *fn;
56 58
57 g = list; 59 g = list;
58 while (xs_list_iter(&g, &fn)) { 60 while (xs_list_iter(&g, &fn)) {
59 xs *l = xs_split(fn, "/"); 61 xs *l = xs_split(fn, "/");
60 char *b = xs_list_get(l, -1); 62 const char *b = xs_list_get(l, -1);
61 xs *dir = xs_fmt("%s/object/%c%c", srv_basedir, b[0], b[1]); 63 xs *dir = xs_fmt("%s/object/%c%c", srv_basedir, b[0], b[1]);
62 xs *nfn = xs_fmt("%s/%s", dir, b); 64 xs *nfn = xs_fmt("%s/%s", dir, b);
63 65
@@ -77,14 +79,16 @@ int snac_upgrade(xs_str **error)
77 else 79 else
78 if (f < 2.3) { 80 if (f < 2.3) {
79 xs *users = user_list(); 81 xs *users = user_list();
80 char *p, *v; 82 char *p;
83 const char *v;
81 84
82 p = users; 85 p = users;
83 while (xs_list_iter(&p, &v)) { 86 while (xs_list_iter(&p, &v)) {
84 snac snac; 87 snac snac;
85 88
86 if (user_open(&snac, v)) { 89 if (user_open(&snac, v)) {
87 char *p, *v; 90 char *p;
91 const char *v;
88 xs *dir = xs_fmt("%s/hidden", snac.basedir); 92 xs *dir = xs_fmt("%s/hidden", snac.basedir);
89 93
90 /* create the hidden directory */ 94 /* create the hidden directory */
@@ -109,7 +113,8 @@ int snac_upgrade(xs_str **error)
109 else 113 else
110 if (f < 2.4) { 114 if (f < 2.4) {
111 xs *users = user_list(); 115 xs *users = user_list();
112 char *p, *v; 116 char *p;
117 const char *v;
113 118
114 p = users; 119 p = users;
115 while (xs_list_iter(&p, &v)) { 120 while (xs_list_iter(&p, &v)) {
@@ -132,7 +137,8 @@ int snac_upgrade(xs_str **error)
132 if (f < 2.5) { 137 if (f < 2.5) {
133 /* upgrade followers */ 138 /* upgrade followers */
134 xs *users = user_list(); 139 xs *users = user_list();
135 char *p, *v; 140 char *p;
141 const char *v;
136 142
137 p = users; 143 p = users;
138 while (xs_list_iter(&p, &v)) { 144 while (xs_list_iter(&p, &v)) {
@@ -141,7 +147,8 @@ int snac_upgrade(xs_str **error)
141 if (user_open(&snac, v)) { 147 if (user_open(&snac, v)) {
142 xs *spec = xs_fmt("%s/followers/" "*.json", snac.basedir); 148 xs *spec = xs_fmt("%s/followers/" "*.json", snac.basedir);
143 xs *dir = xs_glob(spec, 0, 0); 149 xs *dir = xs_glob(spec, 0, 0);
144 char *p, *v; 150 char *p;
151 const char *v;
145 152
146 p = dir; 153 p = dir;
147 while (xs_list_iter(&p, &v)) { 154 while (xs_list_iter(&p, &v)) {
@@ -152,12 +159,12 @@ int snac_upgrade(xs_str **error)
152 xs *o = xs_json_loads(s); 159 xs *o = xs_json_loads(s);
153 fclose(f); 160 fclose(f);
154 161
155 char *type = xs_dict_get(o, "type"); 162 const char *type = xs_dict_get(o, "type");
156 163
157 if (!xs_is_null(type) && strcmp(type, "Follow") == 0) { 164 if (!xs_is_null(type) && strcmp(type, "Follow") == 0) {
158 unlink(v); 165 unlink(v);
159 166
160 char *actor = xs_dict_get(o, "actor"); 167 const char *actor = xs_dict_get(o, "actor");
161 168
162 if (!xs_is_null(actor)) 169 if (!xs_is_null(actor))
163 follower_add(&snac, actor); 170 follower_add(&snac, actor);
@@ -175,7 +182,8 @@ int snac_upgrade(xs_str **error)
175 if (f < 2.6) { 182 if (f < 2.6) {
176 /* upgrade local/ to public/ */ 183 /* upgrade local/ to public/ */
177 xs *users = user_list(); 184 xs *users = user_list();
178 char *p, *v; 185 char *p;
186 const char *v;
179 187
180 p = users; 188 p = users;
181 while (xs_list_iter(&p, &v)) { 189 while (xs_list_iter(&p, &v)) {
@@ -184,7 +192,8 @@ int snac_upgrade(xs_str **error)
184 if (user_open(&snac, v)) { 192 if (user_open(&snac, v)) {
185 xs *spec = xs_fmt("%s/local/" "*.json", snac.basedir); 193 xs *spec = xs_fmt("%s/local/" "*.json", snac.basedir);
186 xs *dir = xs_glob(spec, 0, 0); 194 xs *dir = xs_glob(spec, 0, 0);
187 char *p, *v; 195 char *p;
196 const char *v;
188 197
189 p = dir; 198 p = dir;
190 while (xs_list_iter(&p, &v)) { 199 while (xs_list_iter(&p, &v)) {
@@ -198,22 +207,29 @@ int snac_upgrade(xs_str **error)
198 xs *meta = xs_dup(xs_dict_get(o, "_snac")); 207 xs *meta = xs_dup(xs_dict_get(o, "_snac"));
199 o = xs_dict_del(o, "_snac"); 208 o = xs_dict_del(o, "_snac");
200 209
201 char *id = xs_dict_get(o, "id"); 210 const char *id = xs_dict_get(o, "id");
202 211
203 /* store object */ 212 /* store object */
204 object_add_ow(id, o); 213 object_add_ow(id, o);
205 214
206 /* if it's from us, add to public */ 215 /* if it's from us, add to public */
207 if (xs_startswith(id, snac.actor)) { 216 if (xs_startswith(id, snac.actor)) {
208 char *p, *v; 217 const xs_list *p;
218 const char *v;
219 int c;
209 220
210 object_user_cache_add(&snac, id, "public"); 221 object_user_cache_add(&snac, id, "public");
211 222
212 p = xs_dict_get(meta, "announced_by"); 223 p = xs_dict_get(meta, "announced_by");
213 while (xs_list_iter(&p, &v)) 224
225 c = 0;
226 while (xs_list_next(p, &v, &c))
214 object_admire(id, v, 0); 227 object_admire(id, v, 0);
228
215 p = xs_dict_get(meta, "liked_by"); 229 p = xs_dict_get(meta, "liked_by");
216 while (xs_list_iter(&p, &v)) 230
231 c = 0;
232 while (xs_list_next(p, &v, &c))
217 object_admire(id, v, 1); 233 object_admire(id, v, 1);
218 } 234 }
219 235
@@ -234,7 +250,8 @@ int snac_upgrade(xs_str **error)
234 if (f < 2.7) { 250 if (f < 2.7) {
235 /* upgrade timeline/ to private/ */ 251 /* upgrade timeline/ to private/ */
236 xs *users = user_list(); 252 xs *users = user_list();
237 char *p, *v; 253 char *p;
254 const char *v;
238 255
239 p = users; 256 p = users;
240 while (xs_list_iter(&p, &v)) { 257 while (xs_list_iter(&p, &v)) {
@@ -243,7 +260,8 @@ int snac_upgrade(xs_str **error)
243 if (user_open(&snac, v)) { 260 if (user_open(&snac, v)) {
244 xs *spec = xs_fmt("%s/timeline/" "*.json", snac.basedir); 261 xs *spec = xs_fmt("%s/timeline/" "*.json", snac.basedir);
245 xs *dir = xs_glob(spec, 0, 0); 262 xs *dir = xs_glob(spec, 0, 0);
246 char *p, *v; 263 char *p;
264 const char *v;
247 265
248 p = dir; 266 p = dir;
249 while (xs_list_iter(&p, &v)) { 267 while (xs_list_iter(&p, &v)) {
@@ -257,21 +275,28 @@ int snac_upgrade(xs_str **error)
257 xs *meta = xs_dup(xs_dict_get(o, "_snac")); 275 xs *meta = xs_dup(xs_dict_get(o, "_snac"));
258 o = xs_dict_del(o, "_snac"); 276 o = xs_dict_del(o, "_snac");
259 277
260 char *id = xs_dict_get(o, "id"); 278 const char *id = xs_dict_get(o, "id");
261 279
262 /* store object */ 280 /* store object */
263 object_add_ow(id, o); 281 object_add_ow(id, o);
264 282
265 { 283 {
266 char *p, *v; 284 const xs_list *p;
285 const char *v;
286 int c = 0;
267 287
268 object_user_cache_add(&snac, id, "private"); 288 object_user_cache_add(&snac, id, "private");
269 289
270 p = xs_dict_get(meta, "announced_by"); 290 p = xs_dict_get(meta, "announced_by");
271 while (xs_list_iter(&p, &v)) 291
292 c = 0;
293 while (xs_list_next(p, &v, &c))
272 object_admire(id, v, 0); 294 object_admire(id, v, 0);
295
273 p = xs_dict_get(meta, "liked_by"); 296 p = xs_dict_get(meta, "liked_by");
274 while (xs_list_iter(&p, &v)) 297
298 c = 0;
299 while (xs_list_next(p, &v, &c))
275 object_admire(id, v, 1); 300 object_admire(id, v, 1);
276 } 301 }
277 302
diff --git a/utils.c b/utils.c
index 35caa6e..0e8e3be 100644
--- a/utils.c
+++ b/utils.c
@@ -25,6 +25,8 @@ static const char *default_srv_config = "{"
25 "\"dbglevel\": 0," 25 "\"dbglevel\": 0,"
26 "\"queue_retry_minutes\": 2," 26 "\"queue_retry_minutes\": 2,"
27 "\"queue_retry_max\": 10," 27 "\"queue_retry_max\": 10,"
28 "\"queue_timeout\": 6,"
29 "\"queue_timeout_2\": 8,"
28 "\"cssurls\": [\"\"]," 30 "\"cssurls\": [\"\"],"
29 "\"max_timeline_entries\": 50," 31 "\"max_timeline_entries\": 50,"
30 "\"timeline_purge_days\": 120," 32 "\"timeline_purge_days\": 120,"
@@ -34,6 +36,7 @@ static const char *default_srv_config = "{"
34 "\"admin_account\": \"\"," 36 "\"admin_account\": \"\","
35 "\"title\": \"\"," 37 "\"title\": \"\","
36 "\"short_description\": \"\"," 38 "\"short_description\": \"\","
39 "\"protocol\": \"https\","
37 "\"fastcgi\": false" 40 "\"fastcgi\": false"
38 "}"; 41 "}";
39 42
@@ -46,7 +49,7 @@ static const char *default_css =
46 ".snac-top-user { text-align: center; padding-bottom: 2em }\n" 49 ".snac-top-user { text-align: center; padding-bottom: 2em }\n"
47 ".snac-top-user-name { font-size: 200% }\n" 50 ".snac-top-user-name { font-size: 200% }\n"
48 ".snac-top-user-id { font-size: 150% }\n" 51 ".snac-top-user-id { font-size: 150% }\n"
49 ".snac-avatar { float: left; height: 2.5em; padding: 0.25em }\n" 52 ".snac-avatar { float: left; height: 2.5em; width: 2.5em; padding: 0.25em }\n"
50 ".snac-author { font-size: 90%; text-decoration: none }\n" 53 ".snac-author { font-size: 90%; text-decoration: none }\n"
51 ".snac-author-tag { font-size: 80% }\n" 54 ".snac-author-tag { font-size: 80% }\n"
52 ".snac-pubdate { color: #a0a0a0; font-size: 90% }\n" 55 ".snac-pubdate { color: #a0a0a0; font-size: 90% }\n"
@@ -355,7 +358,7 @@ void rm_rf(const char *dir)
355 xs *d = xs_str_cat(xs_dup(dir), "/" "*"); 358 xs *d = xs_str_cat(xs_dup(dir), "/" "*");
356 xs *l = xs_glob(d, 0, 0); 359 xs *l = xs_glob(d, 0, 0);
357 xs_list *p = l; 360 xs_list *p = l;
358 xs_str *v; 361 const xs_str *v;
359 362
360 if (dbglevel >= 1) 363 if (dbglevel >= 1)
361 printf("Deleting directory %s\n", dir); 364 printf("Deleting directory %s\n", dir);
@@ -390,7 +393,7 @@ int deluser(snac *user)
390 int ret = 0; 393 int ret = 0;
391 xs *fwers = following_list(user); 394 xs *fwers = following_list(user);
392 xs_list *p = fwers; 395 xs_list *p = fwers;
393 xs_str *v; 396 const xs_str *v;
394 397
395 while (xs_list_iter(&p, &v)) { 398 while (xs_list_iter(&p, &v)) {
396 xs *object = NULL; 399 xs *object = NULL;
@@ -415,8 +418,8 @@ int deluser(snac *user)
415void verify_links(snac *user) 418void verify_links(snac *user)
416/* verifies a user's links */ 419/* verifies a user's links */
417{ 420{
418 xs_dict *p = xs_dict_get(user->config, "metadata"); 421 const xs_dict *p = xs_dict_get(user->config, "metadata");
419 char *k, *v; 422 const char *k, *v;
420 int changed = 0; 423 int changed = 0;
421 424
422 xs *headers = xs_dict_new(); 425 xs *headers = xs_dict_new();
@@ -446,7 +449,7 @@ void verify_links(snac *user)
446 xs *ls = xs_regex_select(payload, "< *(a|link) +[^>]+>"); 449 xs *ls = xs_regex_select(payload, "< *(a|link) +[^>]+>");
447 450
448 xs_list *lp = ls; 451 xs_list *lp = ls;
449 char *ll; 452 const char *ll;
450 int vfied = 0; 453 int vfied = 0;
451 454
452 while (!vfied && xs_list_iter(&lp, &ll)) { 455 while (!vfied && xs_list_iter(&lp, &ll)) {
@@ -460,7 +463,7 @@ void verify_links(snac *user)
460 xs *href = NULL; 463 xs *href = NULL;
461 int is_rel_me = 0; 464 int is_rel_me = 0;
462 xs_list *pr = r; 465 xs_list *pr = r;
463 char *ar; 466 const char *ar;
464 467
465 while (xs_list_iter(&pr, &ar)) { 468 while (xs_list_iter(&pr, &ar)) {
466 xs *nq = xs_dup(ar); 469 xs *nq = xs_dup(ar);
diff --git a/webfinger.c b/webfinger.c
index a883d7f..c79fd44 100644
--- a/webfinger.c
+++ b/webfinger.c
@@ -16,12 +16,13 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us
16 int p_size = 0; 16 int p_size = 0;
17 xs *headers = xs_dict_new(); 17 xs *headers = xs_dict_new();
18 xs *l = NULL; 18 xs *l = NULL;
19 xs_str *host = NULL; 19 const char *host = NULL;
20 xs *resource = NULL; 20 xs *resource = NULL;
21 21
22 if (xs_startswith(qs, "https:/" "/")) { 22 if (xs_startswith(qs, "https:/") || xs_startswith(qs, "http:/")) {
23 /* actor query: pick the host */ 23 /* actor query: pick the host */
24 xs *s = xs_replace_n(qs, "https:/" "/", "", 1); 24 xs *s1 = xs_replace_n(qs, "http:/" "/", "", 1);
25 xs *s = xs_replace_n(s1, "https:/" "/", "", 1);
25 26
26 l = xs_split_n(s, "/", 1); 27 l = xs_split_n(s, "/", 1);
27 28
@@ -69,7 +70,9 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us
69 &payload, &p_size, &ctype); 70 &payload, &p_size, &ctype);
70 } 71 }
71 else { 72 else {
72 xs *url = xs_fmt("https:/" "/%s/.well-known/webfinger?resource=%s", host, resource); 73 const char *proto = xs_dict_get_def(srv_config, "protocol", "https");
74
75 xs *url = xs_fmt("%s:/" "/%s/.well-known/webfinger?resource=%s", proto, host, resource);
73 76
74 if (snac == NULL) 77 if (snac == NULL)
75 xs_http_request("GET", url, headers, NULL, 0, &status, &payload, &p_size, 0); 78 xs_http_request("GET", url, headers, NULL, 0, &status, &payload, &p_size, 0);
@@ -84,22 +87,24 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us
84 87
85 if (obj) { 88 if (obj) {
86 if (user != NULL) { 89 if (user != NULL) {
87 char *subject = xs_dict_get(obj, "subject"); 90 const char *subject = xs_dict_get(obj, "subject");
88 91
89 if (subject) 92 if (subject)
90 *user = xs_replace_n(subject, "acct:", "", 1); 93 *user = xs_replace_n(subject, "acct:", "", 1);
91 } 94 }
92 95
93 if (actor != NULL) { 96 if (actor != NULL) {
94 char *list = xs_dict_get(obj, "links"); 97 const xs_list *list = xs_dict_get(obj, "links");
95 char *v; 98 int c = 0;
99 const char *v;
96 100
97 while (xs_list_iter(&list, &v)) { 101 while (xs_list_next(list, &v, &c)) {
98 if (xs_type(v) == XSTYPE_DICT) { 102 if (xs_type(v) == XSTYPE_DICT) {
99 char *type = xs_dict_get(v, "type"); 103 const char *type = xs_dict_get(v, "type");
100 104
101 if (type && (strcmp(type, "application/activity+json") == 0 || 105 if (type && (strcmp(type, "application/activity+json") == 0 ||
102 strcmp(type, "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") == 0)) { 106 strcmp(type, "application/ld+json; profile=\"https:/"
107 "/www.w3.org/ns/activitystreams\"") == 0)) {
103 *actor = xs_dup(xs_dict_get(v, "href")); 108 *actor = xs_dup(xs_dict_get(v, "href"));
104 break; 109 break;
105 } 110 }
@@ -130,8 +135,8 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
130 if (strcmp(q_path, "/.well-known/webfinger") != 0) 135 if (strcmp(q_path, "/.well-known/webfinger") != 0)
131 return 0; 136 return 0;
132 137
133 char *q_vars = xs_dict_get(req, "q_vars"); 138 const char *q_vars = xs_dict_get(req, "q_vars");
134 char *resource = xs_dict_get(q_vars, "resource"); 139 const char *resource = xs_dict_get(q_vars, "resource");
135 140
136 if (resource == NULL) 141 if (resource == NULL)
137 return 400; 142 return 400;
@@ -139,10 +144,10 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
139 snac snac; 144 snac snac;
140 int found = 0; 145 int found = 0;
141 146
142 if (xs_startswith(resource, "https:/" "/")) { 147 if (xs_startswith(resource, "https:/") || xs_startswith(resource, "http:/")) {
143 /* actor search: find a user with this actor */ 148 /* actor search: find a user with this actor */
144 xs *l = xs_split(resource, "/"); 149 xs *l = xs_split(resource, "/");
145 char *uid = xs_list_get(l, -1); 150 const char *uid = xs_list_get(l, -1);
146 151
147 if (uid) 152 if (uid)
148 found = user_open(&snac, uid); 153 found = user_open(&snac, uid);
@@ -160,8 +165,8 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
160 l = xs_split_n(an, "@", 1); 165 l = xs_split_n(an, "@", 1);
161 166
162 if (xs_list_len(l) == 2) { 167 if (xs_list_len(l) == 2) {
163 char *uid = xs_list_get(l, 0); 168 const char *uid = xs_list_get(l, 0);
164 char *host = xs_list_get(l, 1); 169 const char *host = xs_list_get(l, 1);
165 170
166 if (strcmp(host, xs_dict_get(srv_config, "host")) == 0) 171 if (strcmp(host, xs_dict_get(srv_config, "host")) == 0)
167 found = user_open(&snac, uid); 172 found = user_open(&snac, uid);
@@ -185,13 +190,19 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
185 190
186 links = xs_list_append(links, aaj); 191 links = xs_list_append(links, aaj);
187 192
193 /* duplicate with the ld+json type */
194 aaj = xs_dict_set(aaj, "type", "application/ld+json; profile=\"https:/"
195 "/www.w3.org/ns/activitystreams\"");
196
197 links = xs_list_append(links, aaj);
198
188 prof = xs_dict_append(prof, "rel", "http://webfinger.net/rel/profile-page"); 199 prof = xs_dict_append(prof, "rel", "http://webfinger.net/rel/profile-page");
189 prof = xs_dict_append(prof, "type", "text/html"); 200 prof = xs_dict_append(prof, "type", "text/html");
190 prof = xs_dict_append(prof, "href", snac.actor); 201 prof = xs_dict_append(prof, "href", snac.actor);
191 202
192 links = xs_list_append(links, prof); 203 links = xs_list_append(links, prof);
193 204
194 char *avatar = xs_dict_get(snac.config, "avatar"); 205 const char *avatar = xs_dict_get(snac.config, "avatar");
195 if (!xs_is_null(avatar) && *avatar) { 206 if (!xs_is_null(avatar) && *avatar) {
196 xs *d = xs_dict_new(); 207 xs *d = xs_dict_new();
197 208
@@ -211,10 +222,12 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
211 222
212 status = 200; 223 status = 200;
213 *body = j; 224 *body = j;
214 *ctype = "application/json"; 225 *ctype = "application/jrd+json";
215 } 226 }
216 else 227 else
217 status = 404; 228 status = 404;
218 229
230 srv_debug(1, xs_fmt("webfinger_get_handler resource=%s %d", resource, status));
231
219 return status; 232 return status;
220} 233}
diff --git a/xs.h b/xs.h
index d2de44a..972665c 100644
--- a/xs.h
+++ b/xs.h
@@ -21,8 +21,8 @@ typedef enum {
21 XSTYPE_FALSE = 0x15, /* Boolean */ 21 XSTYPE_FALSE = 0x15, /* Boolean */
22 XSTYPE_LIST = 0x1d, /* Sequence of LITEMs up to EOM (with size) */ 22 XSTYPE_LIST = 0x1d, /* Sequence of LITEMs up to EOM (with size) */
23 XSTYPE_LITEM = 0x1f, /* Element of a list (any type) */ 23 XSTYPE_LITEM = 0x1f, /* Element of a list (any type) */
24 XSTYPE_DICT = 0x1c, /* Sequence of DITEMs up to EOM (with size) */ 24 XSTYPE_DICT = 0x1c, /* Sequence of KEYVALs up to EOM (with size) */
25 XSTYPE_DITEM = 0x1e, /* Element of a dict (STRING key + any type) */ 25 XSTYPE_KEYVAL = 0x1e, /* key + value (STRING key + any type) */
26 XSTYPE_EOM = 0x19, /* End of Multiple (LIST or DICT) */ 26 XSTYPE_EOM = 0x19, /* End of Multiple (LIST or DICT) */
27 XSTYPE_DATA = 0x10 /* A block of anonymous data */ 27 XSTYPE_DATA = 0x10 /* A block of anonymous data */
28} xstype; 28} xstype;
@@ -32,6 +32,7 @@ typedef enum {
32typedef char xs_val; 32typedef char xs_val;
33typedef char xs_str; 33typedef char xs_str;
34typedef char xs_list; 34typedef char xs_list;
35typedef char xs_keyval;
35typedef char xs_dict; 36typedef char xs_dict;
36typedef char xs_number; 37typedef char xs_number;
37typedef char xs_data; 38typedef char xs_data;
@@ -45,6 +46,10 @@ typedef char xs_data;
45/* not really all, just very much */ 46/* not really all, just very much */
46#define XS_ALL 0xfffffff 47#define XS_ALL 0xfffffff
47 48
49#ifndef xs_countof
50#define xs_countof(a) (sizeof((a)) / sizeof((*a)))
51#endif
52
48void *xs_free(void *ptr); 53void *xs_free(void *ptr);
49void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func); 54void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func);
50#define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__) 55#define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__)
@@ -89,9 +94,10 @@ xs_list *xs_list_new(void);
89xs_list *xs_list_append_m(xs_list *list, const char *mem, int dsz); 94xs_list *xs_list_append_m(xs_list *list, const char *mem, int dsz);
90xs_list *_xs_list_append(xs_list *list, const xs_val *vals[]); 95xs_list *_xs_list_append(xs_list *list, const xs_val *vals[]);
91#define xs_list_append(list, ...) _xs_list_append(list, (const xs_val *[]){ __VA_ARGS__, NULL }) 96#define xs_list_append(list, ...) _xs_list_append(list, (const xs_val *[]){ __VA_ARGS__, NULL })
92int xs_list_iter(xs_list **list, xs_val **value); 97int xs_list_iter(xs_list **list, const xs_val **value);
98int xs_list_next(const xs_list *list, const xs_val **value, int *ctxt);
93int xs_list_len(const xs_list *list); 99int xs_list_len(const xs_list *list);
94xs_val *xs_list_get(const xs_list *list, int num); 100const xs_val *xs_list_get(const xs_list *list, int num);
95xs_list *xs_list_del(xs_list *list, int num); 101xs_list *xs_list_del(xs_list *list, int num);
96xs_list *xs_list_insert(xs_list *list, int num, const xs_val *data); 102xs_list *xs_list_insert(xs_list *list, int num, const xs_val *data);
97xs_list *xs_list_set(xs_list *list, int num, const xs_val *data); 103xs_list *xs_list_set(xs_list *list, int num, const xs_val *data);
@@ -104,17 +110,20 @@ xs_list *xs_split_n(const char *str, const char *sep, int times);
104#define xs_split(str, sep) xs_split_n(str, sep, XS_ALL) 110#define xs_split(str, sep) xs_split_n(str, sep, XS_ALL)
105xs_list *xs_list_cat(xs_list *l1, const xs_list *l2); 111xs_list *xs_list_cat(xs_list *l1, const xs_list *l2);
106 112
113int xs_keyval_size(const xs_str *key, const xs_val *value);
114xs_str *xs_keyval_key(const xs_keyval *keyval);
115xs_val *xs_keyval_value(const xs_keyval *keyval);
116xs_keyval *xs_keyval_make(xs_keyval *keyval, const xs_str *key, const xs_val *value);
117
107xs_dict *xs_dict_new(void); 118xs_dict *xs_dict_new(void);
108xs_dict *xs_dict_append_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz); 119xs_dict *xs_dict_append(xs_dict *dict, const xs_str *key, const xs_val *value);
109#define xs_dict_append(dict, key, data) xs_dict_append_m(dict, key, data, xs_size(data)) 120xs_dict *xs_dict_prepend(xs_dict *dict, const xs_str *key, const xs_val *value);
110xs_dict *xs_dict_prepend_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz); 121int xs_dict_next(const xs_dict *dict, const xs_str **key, const xs_val **value, int *ctxt);
111#define xs_dict_prepend(dict, key, data) xs_dict_prepend_m(dict, key, data, xs_size(data)) 122const xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def);
112int xs_dict_iter(xs_dict **dict, xs_str **key, xs_val **value);
113int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt);
114xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def);
115#define xs_dict_get(dict, key) xs_dict_get_def(dict, key, NULL) 123#define xs_dict_get(dict, key) xs_dict_get_def(dict, key, NULL)
116xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key); 124xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key);
117xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data); 125xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data);
126xs_dict *xs_dict_gc(xs_dict *dict);
118 127
119xs_val *xs_val_new(xstype t); 128xs_val *xs_val_new(xstype t);
120xs_number *xs_number_new(double f); 129xs_number *xs_number_new(double f);
@@ -242,7 +251,7 @@ xstype xs_type(const xs_val *data)
242 case XSTYPE_LIST: 251 case XSTYPE_LIST:
243 case XSTYPE_LITEM: 252 case XSTYPE_LITEM:
244 case XSTYPE_DICT: 253 case XSTYPE_DICT:
245 case XSTYPE_DITEM: 254 case XSTYPE_KEYVAL:
246 case XSTYPE_NUMBER: 255 case XSTYPE_NUMBER:
247 case XSTYPE_EOM: 256 case XSTYPE_EOM:
248 case XSTYPE_DATA: 257 case XSTYPE_DATA:
@@ -260,7 +269,7 @@ xstype xs_type(const xs_val *data)
260void _xs_put_size(xs_val *ptr, int i) 269void _xs_put_size(xs_val *ptr, int i)
261/* must match _XS_TYPE_SIZE */ 270/* must match _XS_TYPE_SIZE */
262{ 271{
263 memcpy(ptr, &i, sizeof(i)); 272 memcpy(ptr + 1, &i, sizeof(i));
264} 273}
265 274
266 275
@@ -294,7 +303,7 @@ int xs_size(const xs_val *data)
294 303
295 break; 304 break;
296 305
297 case XSTYPE_DITEM: 306 case XSTYPE_KEYVAL:
298 /* calculate the size of the key and the value */ 307 /* calculate the size of the key and the value */
299 p = data + 1; 308 p = data + 1;
300 p += xs_size(p); 309 p += xs_size(p);
@@ -378,7 +387,7 @@ xs_val *xs_expand(xs_val *data, int offset, int size)
378 if (xs_type(data) == XSTYPE_LIST || 387 if (xs_type(data) == XSTYPE_LIST ||
379 xs_type(data) == XSTYPE_DICT || 388 xs_type(data) == XSTYPE_DICT ||
380 xs_type(data) == XSTYPE_DATA) 389 xs_type(data) == XSTYPE_DATA)
381 _xs_put_size(data + 1, sz); 390 _xs_put_size(data, sz);
382 391
383 return data; 392 return data;
384} 393}
@@ -403,7 +412,7 @@ xs_val *xs_collapse(xs_val *data, int offset, int size)
403 if (xs_type(data) == XSTYPE_LIST || 412 if (xs_type(data) == XSTYPE_LIST ||
404 xs_type(data) == XSTYPE_DICT || 413 xs_type(data) == XSTYPE_DICT ||
405 xs_type(data) == XSTYPE_DATA) 414 xs_type(data) == XSTYPE_DATA)
406 _xs_put_size(data + 1, sz); 415 _xs_put_size(data, sz);
407 416
408 return xs_realloc(data, _xs_blk_size(sz)); 417 return xs_realloc(data, _xs_blk_size(sz));
409} 418}
@@ -664,10 +673,10 @@ xs_list *xs_list_new(void)
664{ 673{
665 int sz = 1 + _XS_TYPE_SIZE + 1; 674 int sz = 1 + _XS_TYPE_SIZE + 1;
666 xs_list *l = xs_realloc(NULL, sz); 675 xs_list *l = xs_realloc(NULL, sz);
667 memset(l, '\0', sz); 676 memset(l, XSTYPE_EOM, sz);
668 677
669 l[0] = XSTYPE_LIST; 678 l[0] = XSTYPE_LIST;
670 _xs_put_size(&l[1], sz); 679 _xs_put_size(l, sz);
671 680
672 return l; 681 return l;
673} 682}
@@ -717,7 +726,7 @@ xs_list *_xs_list_append(xs_list *list, const xs_val *vals[])
717} 726}
718 727
719 728
720int xs_list_iter(xs_list **list, xs_val **value) 729int xs_list_iter(xs_list **list, const xs_val **value)
721/* iterates a list value */ 730/* iterates a list value */
722{ 731{
723 int goon = 1; 732 int goon = 1;
@@ -748,23 +757,58 @@ int xs_list_iter(xs_list **list, xs_val **value)
748} 757}
749 758
750 759
760int xs_list_next(const xs_list *list, const xs_val **value, int *ctxt)
761/* iterates a list, with context */
762{
763 if (xs_type(list) != XSTYPE_LIST)
764 return 0;
765
766 int goon = 1;
767
768 const char *p = list;
769
770 /* skip the start of the list */
771 if (*ctxt == 0)
772 *ctxt = 1 + _XS_TYPE_SIZE;
773
774 p += *ctxt;
775
776 /* an element? */
777 if (xs_type(p) == XSTYPE_LITEM) {
778 p++;
779
780 *value = p;
781
782 p += xs_size(*value);
783 }
784 else {
785 /* end of list */
786 goon = 0;
787 }
788
789 /* update the context */
790 *ctxt = p - list;
791
792 return goon;
793}
794
795
751int xs_list_len(const xs_list *list) 796int xs_list_len(const xs_list *list)
752/* returns the number of elements in the list */ 797/* returns the number of elements in the list */
753{ 798{
754 XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST); 799 XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST);
755 800
756 int c = 0; 801 int c = 0, ct = 0;
757 xs_list *p = (xs_list *)list; 802 const xs_val *v;
758 xs_val *v;
759 803
760 while (xs_list_iter(&p, &v)) 804 while (xs_list_next(list, &v, &ct))
761 c++; 805 c++;
762 806
763 return c; 807 return c;
764} 808}
765 809
766 810
767xs_val *xs_list_get(const xs_list *list, int num) 811const xs_val *xs_list_get(const xs_list *list, int num)
768/* returns the element #num */ 812/* returns the element #num */
769{ 813{
770 XS_ASSERT_TYPE(list, XSTYPE_LIST); 814 XS_ASSERT_TYPE(list, XSTYPE_LIST);
@@ -772,11 +816,10 @@ xs_val *xs_list_get(const xs_list *list, int num)
772 if (num < 0) 816 if (num < 0)
773 num = xs_list_len(list) + num; 817 num = xs_list_len(list) + num;
774 818
775 int c = 0; 819 int c = 0, ct = 0;
776 xs_list *p = (xs_list *)list; 820 const xs_val *v;
777 xs_val *v;
778 821
779 while (xs_list_iter(&p, &v)) { 822 while (xs_list_next(list, &v, &ct)) {
780 if (c == num) 823 if (c == num)
781 return v; 824 return v;
782 825
@@ -792,7 +835,7 @@ xs_list *xs_list_del(xs_list *list, int num)
792{ 835{
793 XS_ASSERT_TYPE(list, XSTYPE_LIST); 836 XS_ASSERT_TYPE(list, XSTYPE_LIST);
794 837
795 xs_val *v; 838 const xs_val *v;
796 839
797 if ((v = xs_list_get(list, num)) != NULL) 840 if ((v = xs_list_get(list, num)) != NULL)
798 list = xs_collapse(list, v - 1 - list, xs_size(v - 1)); 841 list = xs_collapse(list, v - 1 - list, xs_size(v - 1));
@@ -806,7 +849,7 @@ xs_list *xs_list_insert(xs_list *list, int num, const xs_val *data)
806{ 849{
807 XS_ASSERT_TYPE(list, XSTYPE_LIST); 850 XS_ASSERT_TYPE(list, XSTYPE_LIST);
808 851
809 xs_val *v; 852 const xs_val *v;
810 int offset; 853 int offset;
811 854
812 if ((v = xs_list_get(list, num)) != NULL) 855 if ((v = xs_list_get(list, num)) != NULL)
@@ -835,16 +878,16 @@ xs_list *xs_list_dequeue(xs_list *list, xs_val **data, int last)
835{ 878{
836 XS_ASSERT_TYPE(list, XSTYPE_LIST); 879 XS_ASSERT_TYPE(list, XSTYPE_LIST);
837 880
838 xs_list *p = list; 881 int ct = 0;
839 xs_val *v = NULL; 882 const xs_val *v = NULL;
840 883
841 if (!last) { 884 if (!last) {
842 /* get the first */ 885 /* get the first */
843 xs_list_iter(&p, &v); 886 xs_list_next(list, &v, &ct);
844 } 887 }
845 else { 888 else {
846 /* iterate to the end */ 889 /* iterate to the end */
847 while (xs_list_iter(&p, &v)); 890 while (xs_list_next(list, &v, &ct));
848 } 891 }
849 892
850 if (v != NULL) { 893 if (v != NULL) {
@@ -864,11 +907,11 @@ int xs_list_in(const xs_list *list, const xs_val *val)
864 XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST); 907 XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST);
865 908
866 int n = 0; 909 int n = 0;
867 xs_list *p = (xs_list *)list; 910 int ct = 0;
868 xs_val *v; 911 const xs_val *v;
869 int sz = xs_size(val); 912 int sz = xs_size(val);
870 913
871 while (xs_list_iter(&p, &v)) { 914 while (xs_list_next(list, &v, &ct)) {
872 if (sz == xs_size(v) && memcmp(val, v, sz) == 0) 915 if (sz == xs_size(v) && memcmp(val, v, sz) == 0)
873 return n; 916 return n;
874 917
@@ -885,13 +928,13 @@ xs_str *xs_join(const xs_list *list, const char *sep)
885 XS_ASSERT_TYPE(list, XSTYPE_LIST); 928 XS_ASSERT_TYPE(list, XSTYPE_LIST);
886 929
887 xs_str *s = NULL; 930 xs_str *s = NULL;
888 xs_list *p = (xs_list *)list; 931 const xs_val *v;
889 xs_val *v;
890 int c = 0; 932 int c = 0;
933 int ct = 0;
891 int offset = 0; 934 int offset = 0;
892 int ssz = strlen(sep); 935 int ssz = strlen(sep);
893 936
894 while (xs_list_iter(&p, &v)) { 937 while (xs_list_next(list, &v, &ct)) {
895 /* refuse to join non-string values */ 938 /* refuse to join non-string values */
896 if (xs_type(v) == XSTYPE_STRING) { 939 if (xs_type(v) == XSTYPE_STRING) {
897 int sz; 940 int sz;
@@ -961,6 +1004,40 @@ xs_list *xs_list_cat(xs_list *l1, const xs_list *l2)
961} 1004}
962 1005
963 1006
1007/** keyvals **/
1008
1009int xs_keyval_size(const xs_str *key, const xs_val *value)
1010/* returns the needed size for a keyval */
1011{
1012 return 1 + xs_size(key) + xs_size(value);
1013}
1014
1015
1016xs_str *xs_keyval_key(const xs_keyval *keyval)
1017/* returns a pointer to the key of the keyval */
1018{
1019 return (xs_str *)&keyval[1];
1020}
1021
1022
1023xs_val *xs_keyval_value(const xs_keyval *keyval)
1024/* returns a pointer to the value of the keyval */
1025{
1026 return (xs_val *)&keyval[1 + xs_size(xs_keyval_key(keyval))];
1027}
1028
1029
1030xs_keyval *xs_keyval_make(xs_keyval *keyval, const xs_str *key, const xs_val *value)
1031/* builds a keyval into mem (should have enough size) */
1032{
1033 keyval[0] = XSTYPE_KEYVAL;
1034 memcpy(xs_keyval_key(keyval), key, xs_size(key));
1035 memcpy(xs_keyval_value(keyval), value, xs_size(value));
1036
1037 return keyval;
1038}
1039
1040
964/** dicts **/ 1041/** dicts **/
965 1042
966xs_dict *xs_dict_new(void) 1043xs_dict *xs_dict_new(void)
@@ -968,87 +1045,47 @@ xs_dict *xs_dict_new(void)
968{ 1045{
969 int sz = 1 + _XS_TYPE_SIZE + 1; 1046 int sz = 1 + _XS_TYPE_SIZE + 1;
970 xs_dict *d = xs_realloc(NULL, sz); 1047 xs_dict *d = xs_realloc(NULL, sz);
971 memset(d, '\0', sz); 1048 memset(d, XSTYPE_EOM, sz);
972 1049
973 d[0] = XSTYPE_DICT; 1050 d[0] = XSTYPE_DICT;
974 _xs_put_size(&d[1], sz); 1051 _xs_put_size(d, sz);
975 1052
976 return d; 1053 return d;
977} 1054}
978 1055
979 1056
980xs_dict *_xs_dict_write_ditem(xs_dict *dict, int offset, const xs_str *key, 1057xs_dict *_xs_dict_write_keyval(xs_dict *dict, int offset, const xs_str *key, const xs_val *value)
981 const xs_val *data, int dsz) 1058/* adds a new keyval to the dict */
982/* inserts a memory block into the dict */
983{ 1059{
984 XS_ASSERT_TYPE(dict, XSTYPE_DICT); 1060 XS_ASSERT_TYPE(dict, XSTYPE_DICT);
985 XS_ASSERT_TYPE(key, XSTYPE_STRING); 1061 XS_ASSERT_TYPE(key, XSTYPE_STRING);
986 1062
987 if (data == NULL) { 1063 if (value == NULL)
988 data = xs_stock(XSTYPE_NULL); 1064 value = xs_stock(XSTYPE_NULL);
989 dsz = xs_size(data);
990 }
991
992 int ksz = xs_size(key);
993 1065
994 dict = xs_expand(dict, offset, 1 + ksz + dsz); 1066 dict = xs_expand(dict, offset, xs_keyval_size(key, value));
995 1067
996 dict[offset] = XSTYPE_DITEM; 1068 xs_keyval_make(&dict[offset], key, value);
997 memcpy(&dict[offset + 1], key, ksz);
998 memcpy(&dict[offset + 1 + ksz], data, dsz);
999 1069
1000 return dict; 1070 return dict;
1001} 1071}
1002 1072
1003 1073
1004xs_dict *xs_dict_append_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz) 1074xs_dict *xs_dict_append(xs_dict *dict, const xs_str *key, const xs_val *value)
1005/* appends a memory block to the dict */ 1075/* appends a memory block to the dict */
1006{ 1076{
1007 return _xs_dict_write_ditem(dict, xs_size(dict) - 1, key, mem, dsz); 1077 return _xs_dict_write_keyval(dict, xs_size(dict) - 1, key, value);
1008} 1078}
1009 1079
1010 1080
1011xs_dict *xs_dict_prepend_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz) 1081xs_dict *xs_dict_prepend(xs_dict *dict, const xs_str *key, const xs_val *value)
1012/* prepends a memory block to the dict */ 1082/* prepends a memory block to the dict */
1013{ 1083{
1014 return _xs_dict_write_ditem(dict, 4, key, mem, dsz); 1084 return _xs_dict_write_keyval(dict, 1 + _XS_TYPE_SIZE, key, value);
1015}
1016
1017
1018int xs_dict_iter(xs_dict **dict, xs_str **key, xs_val **value)
1019/* iterates a dict value */
1020{
1021 int goon = 1;
1022
1023 xs_val *p = *dict;
1024
1025 /* skip the start of the list */
1026 if (xs_type(p) == XSTYPE_DICT)
1027 p += 1 + _XS_TYPE_SIZE;
1028
1029 /* an element? */
1030 if (xs_type(p) == XSTYPE_DITEM) {
1031 p++;
1032
1033 *key = p;
1034 p += xs_size(*key);
1035
1036 *value = p;
1037 p += xs_size(*value);
1038 }
1039 else {
1040 /* end of list */
1041 goon = 0;
1042 }
1043
1044 /* store back the pointer */
1045 *dict = p;
1046
1047 return goon;
1048} 1085}
1049 1086
1050 1087
1051int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt) 1088int xs_dict_next(const xs_dict *dict, const xs_str **key, const xs_val **value, int *ctxt)
1052/* iterates a dict, with context */ 1089/* iterates a dict, with context */
1053{ 1090{
1054 if (xs_type(dict) != XSTYPE_DICT) 1091 if (xs_type(dict) != XSTYPE_DICT)
@@ -1065,7 +1102,7 @@ int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt)
1065 p += *ctxt; 1102 p += *ctxt;
1066 1103
1067 /* an element? */ 1104 /* an element? */
1068 if (xs_type(p) == XSTYPE_DITEM) { 1105 if (xs_type(p) == XSTYPE_KEYVAL) {
1069 p++; 1106 p++;
1070 1107
1071 *key = p; 1108 *key = p;
@@ -1086,14 +1123,14 @@ int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt)
1086} 1123}
1087 1124
1088 1125
1089xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def) 1126const xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def)
1090/* returns the value directed by key, or the default value */ 1127/* returns the value directed by key, or the default value */
1091{ 1128{
1092 XS_ASSERT_TYPE(dict, XSTYPE_DICT); 1129 XS_ASSERT_TYPE(dict, XSTYPE_DICT);
1093 XS_ASSERT_TYPE(key, XSTYPE_STRING); 1130 XS_ASSERT_TYPE(key, XSTYPE_STRING);
1094 1131
1095 xs_str *k; 1132 const xs_str *k;
1096 xs_val *v; 1133 const xs_val *v;
1097 int c = 0; 1134 int c = 0;
1098 1135
1099 while (xs_dict_next(dict, &k, &v, &c)) { 1136 while (xs_dict_next(dict, &k, &v, &c)) {
@@ -1111,14 +1148,14 @@ xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key)
1111 XS_ASSERT_TYPE(dict, XSTYPE_DICT); 1148 XS_ASSERT_TYPE(dict, XSTYPE_DICT);
1112 XS_ASSERT_TYPE(key, XSTYPE_STRING); 1149 XS_ASSERT_TYPE(key, XSTYPE_STRING);
1113 1150
1114 xs_str *k; 1151 const xs_str *k;
1115 xs_val *v; 1152 const xs_val *v;
1116 int c = 0; 1153 int c = 0;
1117 1154
1118 while (xs_dict_next(dict, &k, &v, &c)) { 1155 while (xs_dict_next(dict, &k, &v, &c)) {
1119 if (strcmp(k, key) == 0) { 1156 if (strcmp(k, key) == 0) {
1120 /* the address of the item is just behind the key */ 1157 /* the address of the item is just behind the key */
1121 char *i = k - 1; 1158 char *i = (char *)k - 1;
1122 1159
1123 dict = xs_collapse(dict, i - dict, xs_size(i)); 1160 dict = xs_collapse(dict, i - dict, xs_size(i));
1124 break; 1161 break;
@@ -1145,6 +1182,14 @@ xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data)
1145} 1182}
1146 1183
1147 1184
1185xs_dict *xs_dict_gc(xs_dict *dict)
1186/* collects garbage (leaked values) inside a dict */
1187{
1188 /* this kind of dicts does not get garbage */
1189 return dict;
1190}
1191
1192
1148/** other values **/ 1193/** other values **/
1149 1194
1150xs_val *xs_val_new(xstype t) 1195xs_val *xs_val_new(xstype t)
@@ -1195,8 +1240,11 @@ double xs_number_get(const xs_number *v)
1195{ 1240{
1196 double f = 0.0; 1241 double f = 0.0;
1197 1242
1198 if (v != NULL && v[0] == XSTYPE_NUMBER) 1243 if (xs_type(v) == XSTYPE_NUMBER)
1199 f = atof(&v[1]); 1244 f = atof(&v[1]);
1245 else
1246 if (xs_type(v) == XSTYPE_STRING)
1247 f = atof(v);
1200 1248
1201 return f; 1249 return f;
1202} 1250}
@@ -1207,7 +1255,7 @@ const char *xs_number_str(const xs_number *v)
1207{ 1255{
1208 const char *p = NULL; 1256 const char *p = NULL;
1209 1257
1210 if (v != NULL && v[0] == XSTYPE_NUMBER) 1258 if (xs_type(v) == XSTYPE_NUMBER)
1211 p = &v[1]; 1259 p = &v[1];
1212 1260
1213 return p; 1261 return p;
@@ -1227,7 +1275,7 @@ xs_data *xs_data_new(const void *data, int size)
1227 v = xs_realloc(NULL, _xs_blk_size(total_size)); 1275 v = xs_realloc(NULL, _xs_blk_size(total_size));
1228 v[0] = XSTYPE_DATA; 1276 v[0] = XSTYPE_DATA;
1229 1277
1230 _xs_put_size(v + 1, total_size); 1278 _xs_put_size(v, total_size);
1231 1279
1232 memcpy(&v[1 + _XS_TYPE_SIZE], data, size); 1280 memcpy(&v[1 + _XS_TYPE_SIZE], data, size);
1233 1281
diff --git a/xs_curl.h b/xs_curl.h
index f7783b9..215db7f 100644
--- a/xs_curl.h
+++ b/xs_curl.h
@@ -28,7 +28,7 @@ static size_t _header_callback(char *buffer, size_t size,
28 if (xs_str_in(l, ": ") != -1) { 28 if (xs_str_in(l, ": ") != -1) {
29 xs *knv = xs_split_n(l, ": ", 1); 29 xs *knv = xs_split_n(l, ": ", 1);
30 30
31 xs_tolower_i(xs_list_get(knv, 0)); 31 xs_tolower_i((xs_str *)xs_list_get(knv, 0));
32 32
33 headers = xs_dict_set(headers, xs_list_get(knv, 0), xs_list_get(knv, 1)); 33 headers = xs_dict_set(headers, xs_list_get(knv, 0), xs_list_get(knv, 1));
34 } 34 }
@@ -93,8 +93,8 @@ xs_dict *xs_http_request(const char *method, const char *url,
93 xs_dict *response; 93 xs_dict *response;
94 CURL *curl; 94 CURL *curl;
95 struct curl_slist *list = NULL; 95 struct curl_slist *list = NULL;
96 xs_str *k; 96 const xs_str *k;
97 xs_val *v; 97 const xs_val *v;
98 long lstatus = 0; 98 long lstatus = 0;
99 struct _payload_data pd; 99 struct _payload_data pd;
100 100
diff --git a/xs_fcgi.h b/xs_fcgi.h
index 4727c5c..a1433a2 100644
--- a/xs_fcgi.h
+++ b/xs_fcgi.h
@@ -293,8 +293,8 @@ void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b
293 struct fcgi_record_header hdr = {0}; 293 struct fcgi_record_header hdr = {0};
294 struct fcgi_end_request ereq = {0}; 294 struct fcgi_end_request ereq = {0};
295 xs *out = xs_str_new(NULL); 295 xs *out = xs_str_new(NULL);
296 xs_str *k; 296 const xs_str *k;
297 xs_str *v; 297 const xs_str *v;
298 298
299 /* no previous id? it's an error */ 299 /* no previous id? it's an error */
300 if (fcgi_id == -1) 300 if (fcgi_id == -1)
diff --git a/xs_html.h b/xs_html.h
index 80ae652..a95d45a 100644
--- a/xs_html.h
+++ b/xs_html.h
@@ -6,26 +6,26 @@
6 6
7typedef struct xs_html xs_html; 7typedef struct xs_html xs_html;
8 8
9xs_str *xs_html_encode(char *str); 9xs_str *xs_html_encode(const char *str);
10 10
11xs_html *xs_html_attr(char *key, char *value); 11xs_html *xs_html_attr(const char *key, const char *value);
12xs_html *xs_html_text(char *content); 12xs_html *xs_html_text(const char *content);
13xs_html *xs_html_raw(char *content); 13xs_html *xs_html_raw(const char *content);
14 14
15xs_html *_xs_html_add(xs_html *tag, xs_html *var[]); 15xs_html *_xs_html_add(xs_html *tag, xs_html *var[]);
16#define xs_html_add(tag, ...) _xs_html_add(tag, (xs_html *[]) { __VA_ARGS__, NULL }) 16#define xs_html_add(tag, ...) _xs_html_add(tag, (xs_html *[]) { __VA_ARGS__, NULL })
17 17
18xs_html *_xs_html_tag(char *tag, xs_html *var[]); 18xs_html *_xs_html_tag(const char *tag, xs_html *var[]);
19#define xs_html_tag(tag, ...) _xs_html_tag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) 19#define xs_html_tag(tag, ...) _xs_html_tag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
20 20
21xs_html *_xs_html_sctag(char *tag, xs_html *var[]); 21xs_html *_xs_html_sctag(const char *tag, xs_html *var[]);
22#define xs_html_sctag(tag, ...) _xs_html_sctag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) 22#define xs_html_sctag(tag, ...) _xs_html_sctag(tag, (xs_html *[]) { __VA_ARGS__, NULL })
23 23
24xs_html *_xs_html_container(xs_html *var[]); 24xs_html *_xs_html_container(xs_html *var[]);
25#define xs_html_container(...) _xs_html_container((xs_html *[]) { __VA_ARGS__, NULL }) 25#define xs_html_container(...) _xs_html_container((xs_html *[]) { __VA_ARGS__, NULL })
26 26
27void xs_html_render_f(xs_html *h, FILE *f); 27void xs_html_render_f(xs_html *h, FILE *f);
28xs_str *xs_html_render_s(xs_html *tag, char *prefix); 28xs_str *xs_html_render_s(xs_html *tag, const char *prefix);
29#define xs_html_render(tag) xs_html_render_s(tag, NULL) 29#define xs_html_render(tag) xs_html_render_s(tag, NULL)
30 30
31 31
@@ -47,16 +47,16 @@ struct xs_html {
47 xs_html *next; 47 xs_html *next;
48}; 48};
49 49
50xs_str *xs_html_encode(char *str) 50xs_str *xs_html_encode(const char *str)
51/* encodes str using HTML entities */ 51/* encodes str using HTML entities */
52{ 52{
53 xs_str *s = xs_str_new(NULL); 53 xs_str *s = xs_str_new(NULL);
54 int o = 0; 54 int o = 0;
55 char *e = str + strlen(str); 55 const char *e = str + strlen(str);
56 56
57 for (;;) { 57 for (;;) {
58 char *ec = "<>\"'&"; /* characters to escape */ 58 char *ec = "<>\"'&"; /* characters to escape */
59 char *q = e; 59 const char *q = e;
60 int z; 60 int z;
61 61
62 /* find the nearest happening of a char */ 62 /* find the nearest happening of a char */
@@ -90,7 +90,7 @@ xs_str *xs_html_encode(char *str)
90 90
91#define XS_HTML_NEW() memset(xs_realloc(NULL, sizeof(xs_html)), '\0', sizeof(xs_html)) 91#define XS_HTML_NEW() memset(xs_realloc(NULL, sizeof(xs_html)), '\0', sizeof(xs_html))
92 92
93xs_html *xs_html_attr(char *key, char *value) 93xs_html *xs_html_attr(const char *key, const char *value)
94/* creates an HTML block with an attribute */ 94/* creates an HTML block with an attribute */
95{ 95{
96 xs_html *a = XS_HTML_NEW(); 96 xs_html *a = XS_HTML_NEW();
@@ -108,7 +108,7 @@ xs_html *xs_html_attr(char *key, char *value)
108} 108}
109 109
110 110
111xs_html *xs_html_text(char *content) 111xs_html *xs_html_text(const char *content)
112/* creates an HTML block of text, escaping it previously */ 112/* creates an HTML block of text, escaping it previously */
113{ 113{
114 xs_html *a = XS_HTML_NEW(); 114 xs_html *a = XS_HTML_NEW();
@@ -120,7 +120,7 @@ xs_html *xs_html_text(char *content)
120} 120}
121 121
122 122
123xs_html *xs_html_raw(char *content) 123xs_html *xs_html_raw(const char *content)
124/* creates an HTML block without escaping (for pre-formatted HTML, comments, etc) */ 124/* creates an HTML block without escaping (for pre-formatted HTML, comments, etc) */
125{ 125{
126 xs_html *a = XS_HTML_NEW(); 126 xs_html *a = XS_HTML_NEW();
@@ -152,7 +152,7 @@ xs_html *_xs_html_add(xs_html *tag, xs_html *var[])
152} 152}
153 153
154 154
155static xs_html *_xs_html_tag_t(xs_html_type type, char *tag, xs_html *var[]) 155static xs_html *_xs_html_tag_t(xs_html_type type, const char *tag, xs_html *var[])
156/* creates a tag with a variable list of attributes and subtags */ 156/* creates a tag with a variable list of attributes and subtags */
157{ 157{
158 xs_html *a = XS_HTML_NEW(); 158 xs_html *a = XS_HTML_NEW();
@@ -169,13 +169,13 @@ static xs_html *_xs_html_tag_t(xs_html_type type, char *tag, xs_html *var[])
169} 169}
170 170
171 171
172xs_html *_xs_html_tag(char *tag, xs_html *var[]) 172xs_html *_xs_html_tag(const char *tag, xs_html *var[])
173{ 173{
174 return _xs_html_tag_t(XS_HTML_TAG, tag, var); 174 return _xs_html_tag_t(XS_HTML_TAG, tag, var);
175} 175}
176 176
177 177
178xs_html *_xs_html_sctag(char *tag, xs_html *var[]) 178xs_html *_xs_html_sctag(const char *tag, xs_html *var[])
179{ 179{
180 return _xs_html_tag_t(XS_HTML_SCTAG, tag, var); 180 return _xs_html_tag_t(XS_HTML_SCTAG, tag, var);
181} 181}
@@ -239,7 +239,7 @@ void xs_html_render_f(xs_html *h, FILE *f)
239} 239}
240 240
241 241
242xs_str *xs_html_render_s(xs_html *tag, char *prefix) 242xs_str *xs_html_render_s(xs_html *tag, const char *prefix)
243/* renders to a string */ 243/* renders to a string */
244{ 244{
245 xs_str *s = NULL; 245 xs_str *s = NULL;
diff --git a/xs_httpd.h b/xs_httpd.h
index 4d006d7..4195b81 100644
--- a/xs_httpd.h
+++ b/xs_httpd.h
@@ -16,7 +16,7 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size)
16 xs *q_vars = NULL; 16 xs *q_vars = NULL;
17 xs *p_vars = NULL; 17 xs *p_vars = NULL;
18 xs *l1, *l2; 18 xs *l1, *l2;
19 char *v; 19 const char *v;
20 20
21 xs_socket_timeout(fileno(f), 2.0, 0.0); 21 xs_socket_timeout(fileno(f), 2.0, 0.0);
22 22
@@ -60,7 +60,8 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size)
60 p = xs_split_n(l, ": ", 1); 60 p = xs_split_n(l, ": ", 1);
61 61
62 if (xs_list_len(p) == 2) 62 if (xs_list_len(p) == 2)
63 req = xs_dict_append(req, xs_tolower_i(xs_list_get(p, 0)), xs_list_get(p, 1)); 63 req = xs_dict_append(req, xs_tolower_i(
64 (xs_str *)xs_list_get(p, 0)), xs_list_get(p, 1));
64 } 65 }
65 66
66 xs_socket_timeout(fileno(f), 5.0, 0.0); 67 xs_socket_timeout(fileno(f), 5.0, 0.0);
@@ -98,8 +99,8 @@ void xs_httpd_response(FILE *f, int status, xs_dict *headers, xs_str *body, int
98/* sends an httpd response */ 99/* sends an httpd response */
99{ 100{
100 xs *proto; 101 xs *proto;
101 xs_str *k; 102 const xs_str *k;
102 xs_val *v; 103 const xs_val *v;
103 104
104 proto = xs_fmt("HTTP/1.1 %d %s", status, status / 100 == 2 ? "OK" : "ERROR"); 105 proto = xs_fmt("HTTP/1.1 %d %s", status, status / 100 == 2 ? "OK" : "ERROR");
105 fprintf(f, "%s\r\n", proto); 106 fprintf(f, "%s\r\n", proto);
diff --git a/xs_json.h b/xs_json.h
index 1494fe8..b65e825 100644
--- a/xs_json.h
+++ b/xs_json.h
@@ -71,12 +71,12 @@ static void _xs_json_indent(int level, int indent, FILE *f)
71} 71}
72 72
73 73
74static void _xs_json_dump(const xs_val *s_data, int level, int indent, FILE *f) 74static void _xs_json_dump(const xs_val *data, int level, int indent, FILE *f)
75/* dumps partial data as JSON */ 75/* dumps partial data as JSON */
76{ 76{
77 int c = 0; 77 int c = 0;
78 xs_val *v; 78 int ct = 0;
79 xs_val *data = (xs_val *)s_data; 79 const xs_val *v;
80 80
81 switch (xs_type(data)) { 81 switch (xs_type(data)) {
82 case XSTYPE_NULL: 82 case XSTYPE_NULL:
@@ -98,7 +98,7 @@ static void _xs_json_dump(const xs_val *s_data, int level, int indent, FILE *f)
98 case XSTYPE_LIST: 98 case XSTYPE_LIST:
99 fputc('[', f); 99 fputc('[', f);
100 100
101 while (xs_list_iter(&data, &v)) { 101 while (xs_list_next(data, &v, &ct)) {
102 if (c != 0) 102 if (c != 0)
103 fputc(',', f); 103 fputc(',', f);
104 104
@@ -116,10 +116,9 @@ static void _xs_json_dump(const xs_val *s_data, int level, int indent, FILE *f)
116 case XSTYPE_DICT: 116 case XSTYPE_DICT:
117 fputc('{', f); 117 fputc('{', f);
118 118
119 xs_str *k; 119 const xs_str *k;
120 int ct = 0;
121 120
122 while (xs_dict_next(s_data, &k, &v, &ct)) { 121 while (xs_dict_next(data, &k, &v, &ct)) {
123 if (c != 0) 122 if (c != 0)
124 fputc(',', f); 123 fputc(',', f);
125 124
@@ -328,7 +327,7 @@ static xs_val *_xs_json_load_lexer(FILE *f, js_type *t)
328 327
329int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c) 328int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c)
330/* loads the next scalar value from the JSON stream */ 329/* loads the next scalar value from the JSON stream */
331/* if the value ahead is complex, value is NULL and pt is filled */ 330/* if the value ahead is compound, value is NULL and pt is set */
332{ 331{
333 js_type t; 332 js_type t;
334 333
@@ -348,7 +347,7 @@ int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c)
348 } 347 }
349 348
350 if (*value == NULL) { 349 if (*value == NULL) {
351 /* possible complex type ahead */ 350 /* possible compound type ahead */
352 if (t == JS_OBRACK) 351 if (t == JS_OBRACK)
353 *pt = XSTYPE_LIST; 352 *pt = XSTYPE_LIST;
354 else 353 else
@@ -365,7 +364,7 @@ int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c)
365 364
366 365
367xs_list *xs_json_load_array(FILE *f) 366xs_list *xs_json_load_array(FILE *f)
368/* loads a JSON array (after the initial OBRACK) */ 367/* loads a full JSON array (after the initial OBRACK) */
369{ 368{
370 xstype t; 369 xstype t;
371 xs_list *l = xs_list_new(); 370 xs_list *l = xs_list_new();
@@ -406,7 +405,7 @@ xs_list *xs_json_load_array(FILE *f)
406 405
407int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c) 406int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c)
408/* loads the next key and scalar value from the JSON stream */ 407/* loads the next key and scalar value from the JSON stream */
409/* if the value ahead is complex, value is NULL and pt is filled */ 408/* if the value ahead is compound, value is NULL and pt is set */
410{ 409{
411 js_type t; 410 js_type t;
412 411
@@ -453,7 +452,7 @@ int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt,
453 452
454 453
455xs_dict *xs_json_load_object(FILE *f) 454xs_dict *xs_json_load_object(FILE *f)
456/* loads a JSON object (after the initial OCURLY) */ 455/* loads a full JSON object (after the initial OCURLY) */
457{ 456{
458 xstype t; 457 xstype t;
459 xs_dict *d = xs_dict_new(); 458 xs_dict *d = xs_dict_new();
diff --git a/xs_mime.h b/xs_mime.h
index 84af49c..853b092 100644
--- a/xs_mime.h
+++ b/xs_mime.h
@@ -55,19 +55,23 @@ const char *xs_mime_by_ext(const char *file)
55 const char *ext = strrchr(file, '.'); 55 const char *ext = strrchr(file, '.');
56 56
57 if (ext) { 57 if (ext) {
58 const char **p = xs_mime_types; 58 xs *uext = xs_tolower_i(xs_dup(ext + 1));
59 xs *uext = xs_tolower_i(xs_dup(ext + 1)); 59 int b = 0;
60 int t = xs_countof(xs_mime_types) / 2 - 2;
60 61
61 while (*p) { 62 while (t >= b) {
62 int c; 63 int n = (b + t) / 2;
64 const char *p = xs_mime_types[n * 2];
63 65
64 if ((c = strcmp(*p, uext)) == 0) 66 int c = strcmp(uext, p);
65 return p[1]; 67
68 if (c < 0)
69 t = n - 1;
66 else 70 else
67 if (c > 0) 71 if (c > 0)
68 break; 72 b = n + 1;
69 73 else
70 p += 2; 74 return xs_mime_types[(n * 2) + 1];
71 } 75 }
72 } 76 }
73 77
diff --git a/xs_regex.h b/xs_regex.h
index 1adbcf8..d8d2d1d 100644
--- a/xs_regex.h
+++ b/xs_regex.h
@@ -4,6 +4,7 @@
4 4
5#define _XS_REGEX_H 5#define _XS_REGEX_H
6 6
7int xs_regex_match(const char *str, const char *rx);
7xs_list *xs_regex_split_n(const char *str, const char *rx, int count); 8xs_list *xs_regex_split_n(const char *str, const char *rx, int count);
8#define xs_regex_split(str, rx) xs_regex_split_n(str, rx, XS_ALL) 9#define xs_regex_split(str, rx) xs_regex_split_n(str, rx, XS_ALL)
9xs_list *xs_regex_select_n(const char *str, const char *rx, int count); 10xs_list *xs_regex_select_n(const char *str, const char *rx, int count);
@@ -15,21 +16,29 @@ xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int c
15 16
16#ifdef XS_IMPLEMENTATION 17#ifdef XS_IMPLEMENTATION
17 18
19#ifdef __TINYC__
20/* fix a compilation error in tcc */
21#define _REGEX_NELTS(n)
22#endif
23
18#include <regex.h> 24#include <regex.h>
19 25
20xs_list *xs_regex_split_n(const char *str, const char *rx, int count) 26xs_list *xs_regex_split_n(const char *str, const char *rx, int count)
21/* splits str by regex */ 27/* splits str using regex as a separator, at most count times.
28 Always returns a list:
29 len == 0: regcomp error
30 len == 1: full string (no matches)
31 len == odd: first part [ separator / next part ]...
32*/
22{ 33{
23 regex_t re; 34 regex_t re;
24 regmatch_t rm; 35 regmatch_t rm;
25 int offset = 0; 36 int offset = 0;
26 xs_list *list = NULL; 37 xs_list *list = xs_list_new();
27 const char *p; 38 const char *p;
28 39
29 if (regcomp(&re, rx, REG_EXTENDED)) 40 if (regcomp(&re, rx, REG_EXTENDED))
30 return NULL; 41 return list;
31
32 list = xs_list_new();
33 42
34 while (count > 0 && !regexec(&re, (p = str + offset), 1, &rm, offset > 0 ? REG_NOTBOL : 0)) { 43 while (count > 0 && !regexec(&re, (p = str + offset), 1, &rm, offset > 0 ? REG_NOTBOL : 0)) {
35 /* add first the leading part of the string */ 44 /* add first the leading part of the string */
@@ -60,16 +69,15 @@ xs_list *xs_regex_select_n(const char *str, const char *rx, int count)
60{ 69{
61 xs_list *list = xs_list_new(); 70 xs_list *list = xs_list_new();
62 xs *split = NULL; 71 xs *split = NULL;
63 xs_list *p; 72 const xs_val *v;
64 xs_val *v;
65 int n = 0; 73 int n = 0;
74 int c = 0;
66 75
67 /* split */ 76 /* split */
68 split = xs_regex_split_n(str, rx, count); 77 split = xs_regex_split_n(str, rx, count);
69 78
70 /* now iterate to get only the 'separators' (odd ones) */ 79 /* now iterate to get only the 'separators' (odd ones) */
71 p = split; 80 while (xs_list_next(split, &v, &c)) {
72 while (xs_list_iter(&p, &v)) {
73 if (n & 0x1) 81 if (n & 0x1)
74 list = xs_list_append(list, v); 82 list = xs_list_append(list, v);
75 83
@@ -86,13 +94,12 @@ xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int c
86{ 94{
87 xs_str *s = xs_str_new(NULL); 95 xs_str *s = xs_str_new(NULL);
88 xs *split = xs_regex_split_n(str, rx, count); 96 xs *split = xs_regex_split_n(str, rx, count);
89 xs_list *p; 97 const xs_val *v;
90 xs_val *v;
91 int n = 0; 98 int n = 0;
99 int c = 0;
92 int pholder = !!strchr(rep, '&'); 100 int pholder = !!strchr(rep, '&');
93 101
94 p = split; 102 while (xs_list_next(split, &v, &c)) {
95 while (xs_list_iter(&p, &v)) {
96 if (n & 0x1) { 103 if (n & 0x1) {
97 if (pholder) { 104 if (pholder) {
98 /* rep has a placeholder; process char by char */ 105 /* rep has a placeholder; process char by char */
@@ -128,6 +135,16 @@ xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int c
128 return s; 135 return s;
129} 136}
130 137
138
139int xs_regex_match(const char *str, const char *rx)
140/* returns if str matches the regex at least once */
141{
142 xs *l = xs_regex_select_n(str, rx, 1);
143
144 return xs_list_len(l) == 1;
145}
146
147
131#endif /* XS_IMPLEMENTATION */ 148#endif /* XS_IMPLEMENTATION */
132 149
133#endif /* XS_REGEX_H */ 150#endif /* XS_REGEX_H */
diff --git a/xs_set.h b/xs_set.h
index 3a334e4..d320d34 100644
--- a/xs_set.h
+++ b/xs_set.h
@@ -85,7 +85,7 @@ int xs_set_add(xs_set *s, const xs_val *data)
85{ 85{
86 /* is it 'full'? */ 86 /* is it 'full'? */
87 if (s->used >= s->elems / 2) { 87 if (s->used >= s->elems / 2) {
88 char *p, *v; 88 const xs_val *v;
89 89
90 /* expand! */ 90 /* expand! */
91 s->elems *= 2; 91 s->elems *= 2;
@@ -95,8 +95,8 @@ int xs_set_add(xs_set *s, const xs_val *data)
95 memset(s->hash, '\0', s->elems * sizeof(int)); 95 memset(s->hash, '\0', s->elems * sizeof(int));
96 96
97 /* add the list elements back */ 97 /* add the list elements back */
98 p = s->list; 98 int ct = 0;
99 while (xs_list_iter(&p, &v)) 99 while (xs_list_next(s->list, &v, &ct))
100 _store_hash(s, v, v - s->list); 100 _store_hash(s, v, v - s->list);
101 } 101 }
102 102
@@ -104,7 +104,7 @@ int xs_set_add(xs_set *s, const xs_val *data)
104 104
105 /* if it's new, add the data */ 105 /* if it's new, add the data */
106 if (ret) 106 if (ret)
107 s->list = xs_list_append_m(s->list, data, xs_size(data)); 107 s->list = xs_list_append(s->list, data);
108 108
109 return ret; 109 return ret;
110} 110}
diff --git a/xs_unicode.h b/xs_unicode.h
index 47e1101..1799d89 100644
--- a/xs_unicode.h
+++ b/xs_unicode.h
@@ -6,7 +6,7 @@
6 6
7 int _xs_utf8_enc(char buf[4], unsigned int cpoint); 7 int _xs_utf8_enc(char buf[4], unsigned int cpoint);
8 int xs_is_utf8_cont_byte(char c); 8 int xs_is_utf8_cont_byte(char c);
9 unsigned int xs_utf8_dec(char **str); 9 unsigned int xs_utf8_dec(const char **str);
10 int xs_unicode_width(unsigned int cpoint); 10 int xs_unicode_width(unsigned int cpoint);
11 int xs_is_surrogate(unsigned int cpoint); 11 int xs_is_surrogate(unsigned int cpoint);
12 unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2); 12 unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2);
@@ -27,8 +27,8 @@
27 27
28#ifdef XS_IMPLEMENTATION 28#ifdef XS_IMPLEMENTATION
29 29
30#ifndef countof 30#ifndef xs_countof
31#define countof(a) (sizeof((a)) / sizeof((*a))) 31#define xs_countof(a) (sizeof((a)) / sizeof((*a)))
32#endif 32#endif
33 33
34int _xs_utf8_enc(char buf[4], unsigned int cpoint) 34int _xs_utf8_enc(char buf[4], unsigned int cpoint)
@@ -66,10 +66,10 @@ int xs_is_utf8_cont_byte(char c)
66} 66}
67 67
68 68
69unsigned int xs_utf8_dec(char **str) 69unsigned int xs_utf8_dec(const char **str)
70/* decodes an utf-8 char inside str and updates the pointer */ 70/* decodes an utf-8 char inside str and updates the pointer */
71{ 71{
72 char *p = *str; 72 const char *p = *str;
73 unsigned int cpoint = 0; 73 unsigned int cpoint = 0;
74 unsigned char c = *p++; 74 unsigned char c = *p++;
75 int cb = 0; 75 int cb = 0;
@@ -125,7 +125,7 @@ int xs_unicode_width(unsigned int cpoint)
125/* returns the width in columns of a Unicode codepoint (somewhat simplified) */ 125/* returns the width in columns of a Unicode codepoint (somewhat simplified) */
126{ 126{
127 int b = 0; 127 int b = 0;
128 int t = countof(xs_unicode_width_table) / 3 - 1; 128 int t = xs_countof(xs_unicode_width_table) / 3 - 1;
129 129
130 while (t >= b) { 130 while (t >= b) {
131 int n = (b + t) / 2; 131 int n = (b + t) / 2;
@@ -193,7 +193,7 @@ unsigned int *_xs_unicode_upper_search(unsigned int cpoint)
193/* searches for an uppercase codepoint in the case fold table */ 193/* searches for an uppercase codepoint in the case fold table */
194{ 194{
195 int b = 0; 195 int b = 0;
196 int t = countof(xs_unicode_case_fold_table) / 2 + 1; 196 int t = xs_countof(xs_unicode_case_fold_table) / 2 + 1;
197 197
198 while (t >= b) { 198 while (t >= b) {
199 int n = (b + t) / 2; 199 int n = (b + t) / 2;
@@ -216,7 +216,7 @@ unsigned int *_xs_unicode_lower_search(unsigned int cpoint)
216/* searches for a lowercase codepoint in the case fold table */ 216/* searches for a lowercase codepoint in the case fold table */
217{ 217{
218 unsigned int *p = xs_unicode_case_fold_table; 218 unsigned int *p = xs_unicode_case_fold_table;
219 unsigned int *e = p + countof(xs_unicode_case_fold_table); 219 unsigned int *e = p + xs_countof(xs_unicode_case_fold_table);
220 220
221 while (p < e) { 221 while (p < e) {
222 if (cpoint == p[1]) 222 if (cpoint == p[1])
@@ -251,7 +251,7 @@ int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac)
251/* applies unicode Normalization Form D */ 251/* applies unicode Normalization Form D */
252{ 252{
253 int b = 0; 253 int b = 0;
254 int t = countof(xs_unicode_nfd_table) / 3 - 1; 254 int t = xs_countof(xs_unicode_nfd_table) / 3 - 1;
255 255
256 while (t >= b) { 256 while (t >= b) {
257 int n = (b + t) / 2; 257 int n = (b + t) / 2;
@@ -279,7 +279,7 @@ int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint)
279/* applies unicode Normalization Form C */ 279/* applies unicode Normalization Form C */
280{ 280{
281 unsigned int *p = xs_unicode_nfd_table; 281 unsigned int *p = xs_unicode_nfd_table;
282 unsigned int *e = p + countof(xs_unicode_nfd_table); 282 unsigned int *e = p + xs_countof(xs_unicode_nfd_table);
283 283
284 while (p < e) { 284 while (p < e) {
285 if (p[1] == base && p[2] == diac) { 285 if (p[1] == base && p[2] == diac) {
@@ -298,7 +298,7 @@ int xs_unicode_is_alpha(unsigned int cpoint)
298/* checks if a codepoint is an alpha (i.e. a letter) */ 298/* checks if a codepoint is an alpha (i.e. a letter) */
299{ 299{
300 int b = 0; 300 int b = 0;
301 int t = countof(xs_unicode_alpha_table) / 2 - 1; 301 int t = xs_countof(xs_unicode_alpha_table) / 2 - 1;
302 302
303 while (t >= b) { 303 while (t >= b) {
304 int n = (b + t) / 2; 304 int n = (b + t) / 2;
diff --git a/xs_url.h b/xs_url.h
index f335709..606c3e4 100644
--- a/xs_url.h
+++ b/xs_url.h
@@ -51,12 +51,11 @@ xs_dict *xs_url_vars(const char *str)
51 /* split by arguments */ 51 /* split by arguments */
52 xs *args = xs_split(str, "&"); 52 xs *args = xs_split(str, "&");
53 53
54 xs_list *l; 54 int ct = 0;
55 xs_val *v; 55 const xs_val *v;
56 56
57 l = args; 57 while (xs_list_next(args, &v, &ct)) {
58 while (xs_list_iter(&l, &v)) { 58 xs *kv = xs_split_n(v, "=", 1);
59 xs *kv = xs_split_n(v, "=", 2);
60 59
61 if (xs_list_len(kv) == 2) { 60 if (xs_list_len(kv) == 2) {
62 const char *key = xs_list_get(kv, 0); 61 const char *key = xs_list_get(kv, 0);
@@ -119,8 +118,8 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea
119 while ((p = xs_memmem(payload + offset, p_size - offset, boundary, bsz)) != NULL) { 118 while ((p = xs_memmem(payload + offset, p_size - offset, boundary, bsz)) != NULL) {
120 xs *s1 = NULL; 119 xs *s1 = NULL;
121 xs *l1 = NULL; 120 xs *l1 = NULL;
122 char *vn = NULL; 121 const char *vn = NULL;
123 char *fn = NULL; 122 const char *fn = NULL;
124 char *q; 123 char *q;
125 int po, ps; 124 int po, ps;
126 125
diff --git a/xs_version.h b/xs_version.h
index ef52120..7a7ba53 100644
--- a/xs_version.h
+++ b/xs_version.h
@@ -1 +1 @@
/* 0df383371d207b0adfda40912ee54b37e5b01152 2024-03-15T03:45:39+01:00 */ /* 65769f25ed99b886a643522bef21628396cd118d 2024-05-25T08:18:51+02:00 */