diff options
| -rw-r--r-- | Makefile | 7 | ||||
| -rw-r--r-- | Makefile.NetBSD | 8 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | RELEASE_NOTES.md | 32 | ||||
| -rw-r--r-- | TODO.md | 12 | ||||
| -rw-r--r-- | activitypub.c | 146 | ||||
| -rw-r--r-- | data.c | 130 | ||||
| -rw-r--r-- | doc/snac.1 | 8 | ||||
| -rw-r--r-- | doc/snac.5 | 10 | ||||
| -rw-r--r-- | doc/snac.8 | 57 | ||||
| -rw-r--r-- | format.c | 79 | ||||
| -rw-r--r-- | html.c | 212 | ||||
| -rw-r--r-- | httpd.c | 3 | ||||
| -rw-r--r-- | main.c | 115 | ||||
| -rw-r--r-- | mastoapi.c | 177 | ||||
| -rw-r--r-- | snac.h | 14 | ||||
| -rw-r--r-- | xs.h | 103 | ||||
| -rw-r--r-- | xs_json.h | 107 | ||||
| -rw-r--r-- | xs_mime.h | 22 | ||||
| -rw-r--r-- | xs_unicode.h | 16 | ||||
| -rw-r--r-- | xs_url.h | 2 | ||||
| -rw-r--r-- | xs_version.h | 2 |
22 files changed, 954 insertions, 312 deletions
| @@ -36,15 +36,16 @@ uninstall: | |||
| 36 | activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ | 36 | activitypub.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 |
| 38 | data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ | 38 | data.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 snac.h | 39 | xs_set.h xs_time.h xs_regex.h snac.h |
| 40 | format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h snac.h | 40 | format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ |
| 41 | xs_time.h snac.h | ||
| 41 | html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ | 42 | html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ |
| 42 | xs_time.h xs_mime.h xs_match.h xs_html.h snac.h | 43 | xs_time.h xs_mime.h xs_match.h xs_html.h snac.h |
| 43 | http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ | 44 | http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ |
| 44 | snac.h | 45 | snac.h |
| 45 | httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \ | 46 | httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \ |
| 46 | xs_time.h xs_openssl.h xs_fcgi.h xs_html.h snac.h | 47 | xs_time.h xs_openssl.h xs_fcgi.h xs_html.h snac.h |
| 47 | main.o: main.c xs.h xs_io.h xs_json.h xs_time.h snac.h xs_html.h | 48 | main.o: main.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h snac.h |
| 48 | mastoapi.o: mastoapi.c xs.h xs_hex.h xs_openssl.h xs_json.h xs_io.h \ | 49 | mastoapi.o: mastoapi.c xs.h xs_hex.h xs_openssl.h xs_json.h xs_io.h \ |
| 49 | xs_time.h xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h \ | 50 | xs_time.h xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h \ |
| 50 | snac.h | 51 | snac.h |
diff --git a/Makefile.NetBSD b/Makefile.NetBSD index 5ab361f..67c77a5 100644 --- a/Makefile.NetBSD +++ b/Makefile.NetBSD | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | PREFIX=/usr/pkg | 1 | PREFIX=/usr/pkg |
| 2 | PREFIX_MAN=$(PREFIX)/man | 2 | PREFIX_MAN=$(PREFIX)/man |
| 3 | CFLAGS?=-g -Wall -Wextra | 3 | CFLAGS?=-g -Wall -Wextra |
| 4 | LDFLAGS=-lrt | ||
| 4 | 5 | ||
| 5 | all: snac | 6 | all: snac |
| 6 | 7 | ||
| @@ -37,15 +38,16 @@ uninstall: | |||
| 37 | activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ | 38 | activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ |
| 38 | 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 |
| 39 | data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ | 40 | data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ |
| 40 | xs_set.h xs_time.h snac.h | 41 | xs_set.h xs_time.h xs_regex.h snac.h |
| 41 | format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h snac.h | 42 | format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ |
| 43 | xs_time.h snac.h | ||
| 42 | html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ | 44 | html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ |
| 43 | xs_time.h xs_mime.h xs_match.h xs_html.h snac.h | 45 | xs_time.h xs_mime.h xs_match.h xs_html.h snac.h |
| 44 | http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ | 46 | http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ |
| 45 | snac.h | 47 | snac.h |
| 46 | httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \ | 48 | httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \ |
| 47 | xs_time.h xs_openssl.h xs_fcgi.h xs_html.h snac.h | 49 | xs_time.h xs_openssl.h xs_fcgi.h xs_html.h snac.h |
| 48 | main.o: main.c xs.h xs_io.h xs_json.h xs_time.h snac.h xs_html.h | 50 | main.o: main.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h snac.h |
| 49 | mastoapi.o: mastoapi.c xs.h xs_hex.h xs_openssl.h xs_json.h xs_io.h \ | 51 | mastoapi.o: mastoapi.c xs.h xs_hex.h xs_openssl.h xs_json.h xs_io.h \ |
| 50 | xs_time.h xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h \ | 52 | xs_time.h xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h \ |
| 51 | snac.h | 53 | snac.h |
| @@ -93,11 +93,13 @@ This will: | |||
| 93 | - [Online snac manuals (user, administrator and data formats)](https://comam.es/snac-doc/). | 93 | - [Online snac manuals (user, administrator and data formats)](https://comam.es/snac-doc/). |
| 94 | - [How to run your own ActivityPub server on OpenBSD via snac (by Jordan Reger)](https://man.sr.ht/~jordanreger/activitypub-server-on-openbsd/). | 94 | - [How to run your own ActivityPub server on OpenBSD via snac (by Jordan Reger)](https://man.sr.ht/~jordanreger/activitypub-server-on-openbsd/). |
| 95 | - [How to install & run your own ActivityPub server on FreeBSD using snac, nginx, lets'encrypt (by gyptazy)](https://gyptazy.ch/blog/install-snac2-on-freebsd-an-activitypub-instance-for-the-fediverse/). | 95 | - [How to install & run your own ActivityPub server on FreeBSD using snac, nginx, lets'encrypt (by gyptazy)](https://gyptazy.ch/blog/install-snac2-on-freebsd-an-activitypub-instance-for-the-fediverse/). |
| 96 | - [How to install snac on OpenBSD without relayd (by @antics@mastodon.nu)](https://chai.guru/pub/openbsd/snac.html). | ||
| 97 | - [Setting up Snac in OpenBSD (by Yonle)](https://wiki.ircnow.org/index.php?n=Openbsd.Snac). | ||
| 96 | 98 | ||
| 97 | ## Incredibly awesome CSS themes for snac | 99 | ## Incredibly awesome CSS themes for snac |
| 98 | 100 | ||
| 101 | - [A compilation of themes for snac (by Ворон)](https://codeberg.org/voron/snac-style). | ||
| 99 | - [A cool, elegant theme (by Haijo7)](https://codeberg.org/Haijo7/snac-custom-css). | 102 | - [A cool, elegant theme (by Haijo7)](https://codeberg.org/Haijo7/snac-custom-css). |
| 100 | - [A light, lean theme (by Ворон)](https://codeberg.org/voron/snac-style). | ||
| 101 | - [A terminal-like theme (by Tetra)](https://codeberg.org/ERROR404NULLNOTFOUND/snac-terminal-theme). | 103 | - [A terminal-like theme (by Tetra)](https://codeberg.org/ERROR404NULLNOTFOUND/snac-terminal-theme). |
| 102 | 104 | ||
| 103 | ## License | 105 | ## License |
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6b52712..f272f5c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -1,5 +1,37 @@ | |||
| 1 | # Release Notes | 1 | # Release Notes |
| 2 | 2 | ||
| 3 | ## 2.52 | ||
| 4 | |||
| 5 | Posts that were liked or boosted can now be unliked and unboosted. | ||
| 6 | |||
| 7 | Added a header to avoid over-zealous caching in some browsers (contributed by louis77). | ||
| 8 | |||
| 9 | ## 2.51 | ||
| 10 | |||
| 11 | Support 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). | ||
| 12 | |||
| 13 | Fixed a bug that caused some notifications to be lost when coming from a user in the same instance. | ||
| 14 | |||
| 15 | Added an additional check for blocked instances (sometimes, posts from blocked sites that were ancestors of legit posts were 'leaking' into the timeline). | ||
| 16 | |||
| 17 | On OpenBSD, if the `disable_email_notifications` server flag is set to `true`, `unveil()` is not called for the execution of the `/usr/sbin/sendmail` binary and `pledge()` doesn't set the `exec` promise. | ||
| 18 | |||
| 19 | ## 2.50 | ||
| 20 | |||
| 21 | Incoming posts can now be filtered out by content using regular expressions on a server level (these regexes are written in the `filter_reject.txt` file at the server base directory; see `snac(5)` and `snac(8)`). | ||
| 22 | |||
| 23 | Improved page position after hitting the `Hide` or `MUTE` buttons (for most cases). | ||
| 24 | |||
| 25 | Use a shorter maximum conversation thread level (also, this maximum value is now configurable at compilation level with the `MAX_CONVERSATION_LEVELS` define). | ||
| 26 | |||
| 27 | Fixed a bug where editing a post made the attached media or video to be lost. | ||
| 28 | |||
| 29 | The way of refreshing remote actor data has been improved. | ||
| 30 | |||
| 31 | Posting from the command-line now allows attachments. | ||
| 32 | |||
| 33 | Added defines for time to enable MacOS builds (contributed by andypiper). | ||
| 34 | |||
| 3 | ## 2.49 | 35 | ## 2.49 |
| 4 | 36 | ||
| 5 | Mastodon API: Fixed a bug in how validated links are reported. | 37 | Mastodon API: Fixed a bug in how validated links are reported. |
| @@ -6,19 +6,19 @@ Unfollowing lemmy groups gets rejected with an http status of 400. | |||
| 6 | 6 | ||
| 7 | Unfollowing guppe groups seems to work (http status of 200), but messages continue to arrive as if it didn't. | 7 | Unfollowing guppe groups seems to work (http status of 200), but messages continue to arrive as if it didn't. |
| 8 | 8 | ||
| 9 | Post edits should preserve the image and the image description somewhat. | ||
| 10 | |||
| 11 | Mastodon API: fix whatever the fuck is making the official app and Megalodon to crash. | 9 | Mastodon API: fix whatever the fuck is making the official app and Megalodon to crash. |
| 12 | 10 | ||
| 13 | Important: deleting a follower should do more that just delete the object, see https://codeberg.org/grunfink/snac2/issues/43#issuecomment-956721 | 11 | Important: deleting a follower should do more that just delete the object, see https://codeberg.org/grunfink/snac2/issues/43#issuecomment-956721 |
| 14 | 12 | ||
| 15 | ## Wishlist | 13 | ## Wishlist |
| 16 | 14 | ||
| 15 | Implement `Group`-like accounts (i.e. an actor that boosts to their followers all posts that mention it). | ||
| 16 | |||
| 17 | Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 | 17 | Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 |
| 18 | 18 | ||
| 19 | Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 | 19 | Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 |
| 20 | 20 | ||
| 21 | Consider discarding posts by content using string or regex to mitigate spam. | 21 | Consider adding Mastodon import functionality (for following_accounts.csv and outbox.json). |
| 22 | 22 | ||
| 23 | Consider adding milter-like support to reject posts to mitigate spam. | 23 | Consider adding milter-like support to reject posts to mitigate spam. |
| 24 | 24 | ||
| @@ -26,7 +26,7 @@ Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` | |||
| 26 | 26 | ||
| 27 | Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050 | 27 | Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050 |
| 28 | 28 | ||
| 29 | Add support for /share?text=tt&website=url (whatever it is). | 29 | Add support for /share?text=tt&website=url (whatever it is, see https://mastodonshare.com/ for details). |
| 30 | 30 | ||
| 31 | Add support for /authorize_interaction (whatever it is). | 31 | Add support for /authorize_interaction (whatever it is). |
| 32 | 32 | ||
| @@ -307,3 +307,7 @@ Add support for rel="me" links, see https://codeberg.org/grunfink/snac2/issues/1 | |||
| 307 | Hide followers-only replies to unknown accounts, see https://codeberg.org/grunfink/snac2/issues/123 (2024-02-22T12:40:58+0100). | 307 | Hide followers-only replies to unknown accounts, see https://codeberg.org/grunfink/snac2/issues/123 (2024-02-22T12:40:58+0100). |
| 308 | 308 | ||
| 309 | Consider implementing the rejection of activities from recently-created accounts to mitigate spam, see https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/activity_pub/mrf/reject_newly_created_account_note_policy.ex (2024-02-24T07:46:10+0100). | 309 | Consider implementing the rejection of activities from recently-created accounts to mitigate spam, see https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/activity_pub/mrf/reject_newly_created_account_note_policy.ex (2024-02-24T07:46:10+0100). |
| 310 | |||
| 311 | Consider discarding posts by content using string or regex to mitigate spam (2024-03-14T10:40:14+0100). | ||
| 312 | |||
| 313 | Post edits should preserve the image and the image description somewhat (2024-03-22T09:57:18+0100). | ||
diff --git a/activitypub.c b/activitypub.c index 73fbbc6..53f102e 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -125,10 +125,10 @@ int actor_request(snac *user, const char *actor, xs_dict **data) | |||
| 125 | *data = NULL; | 125 | *data = NULL; |
| 126 | 126 | ||
| 127 | /* get from disk first */ | 127 | /* get from disk first */ |
| 128 | status = actor_get(actor, data); | 128 | status = actor_get_refresh(user, actor, data); |
| 129 | 129 | ||
| 130 | if (status != 200) { | 130 | if (!valid_status(status)) { |
| 131 | /* actor data non-existent or stale: get from the net */ | 131 | /* actor data non-existent: get from the net */ |
| 132 | status = activitypub_request(user, actor, &payload); | 132 | status = activitypub_request(user, actor, &payload); |
| 133 | 133 | ||
| 134 | if (valid_status(status)) { | 134 | if (valid_status(status)) { |
| @@ -149,8 +149,6 @@ int actor_request(snac *user, const char *actor, xs_dict **data) | |||
| 149 | if (valid_status(status) && data && *data) | 149 | if (valid_status(status) && data && *data) |
| 150 | inbox_add_by_actor(*data); | 150 | inbox_add_by_actor(*data); |
| 151 | } | 151 | } |
| 152 | else | ||
| 153 | srv_debug(2, xs_fmt("NOT collected")); | ||
| 154 | 152 | ||
| 155 | return status; | 153 | return status; |
| 156 | } | 154 | } |
| @@ -313,6 +311,12 @@ int timeline_request(snac *snac, char **id, xs_str **wrk, int level) | |||
| 313 | if (level < MAX_CONVERSATION_LEVELS && !xs_is_null(*id)) { | 311 | if (level < MAX_CONVERSATION_LEVELS && !xs_is_null(*id)) { |
| 314 | xs *msg = NULL; | 312 | xs *msg = NULL; |
| 315 | 313 | ||
| 314 | /* from a blocked instance? discard and break */ | ||
| 315 | if (is_instance_blocked(*id)) { | ||
| 316 | snac_debug(snac, 1, xs_fmt("timeline_request blocked instance %s", *id)); | ||
| 317 | return status; | ||
| 318 | } | ||
| 319 | |||
| 316 | /* is the object already there? */ | 320 | /* is the object already there? */ |
| 317 | if (!valid_status(object_get(*id, &msg))) { | 321 | if (!valid_status(object_get(*id, &msg))) { |
| 318 | /* no; download it */ | 322 | /* no; download it */ |
| @@ -354,18 +358,22 @@ int timeline_request(snac *snac, char **id, xs_str **wrk, int level) | |||
| 354 | if (xs_match(type, "Note|Page|Article|Video")) { | 358 | if (xs_match(type, "Note|Page|Article|Video")) { |
| 355 | const char *actor = get_atto(object); | 359 | const char *actor = get_atto(object); |
| 356 | 360 | ||
| 357 | /* request (and drop) the actor for this entry */ | 361 | if (content_check("filter_reject.txt", object)) |
| 358 | if (!xs_is_null(actor)) | 362 | snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid)); |
| 359 | actor_request(snac, actor, NULL); | 363 | else { |
| 364 | /* request (and drop) the actor for this entry */ | ||
| 365 | if (!xs_is_null(actor)) | ||
| 366 | actor_request(snac, actor, NULL); | ||
| 360 | 367 | ||
| 361 | /* does it have an ancestor? */ | 368 | /* does it have an ancestor? */ |
| 362 | char *in_reply_to = xs_dict_get(object, "inReplyTo"); | 369 | char *in_reply_to = xs_dict_get(object, "inReplyTo"); |
| 363 | 370 | ||
| 364 | /* store */ | 371 | /* store */ |
| 365 | timeline_add(snac, nid, object); | 372 | timeline_add(snac, nid, object); |
| 366 | 373 | ||
| 367 | /* recurse! */ | 374 | /* recurse! */ |
| 368 | timeline_request(snac, &in_reply_to, NULL, level + 1); | 375 | timeline_request(snac, &in_reply_to, NULL, level + 1); |
| 376 | } | ||
| 369 | } | 377 | } |
| 370 | } | 378 | } |
| 371 | } | 379 | } |
| @@ -623,6 +631,12 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 623 | const char *type = xs_dict_get(c_msg, "type"); | 631 | const char *type = xs_dict_get(c_msg, "type"); |
| 624 | const char *actor = xs_dict_get(c_msg, "actor"); | 632 | const char *actor = xs_dict_get(c_msg, "actor"); |
| 625 | 633 | ||
| 634 | if (strcmp(actor, snac->actor) == 0) { | ||
| 635 | /* message by myself? (most probably via the shared-inbox) reject */ | ||
| 636 | snac_debug(snac, 1, xs_fmt("ignoring message by myself")); | ||
| 637 | return 0; | ||
| 638 | } | ||
| 639 | |||
| 626 | if (xs_match(type, "Like|Announce")) { | 640 | if (xs_match(type, "Like|Announce")) { |
| 627 | const char *object = xs_dict_get(c_msg, "object"); | 641 | const char *object = xs_dict_get(c_msg, "object"); |
| 628 | 642 | ||
| @@ -657,6 +671,12 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 657 | return !xs_is_null(object) && strcmp(snac->actor, object) == 0; | 671 | return !xs_is_null(object) && strcmp(snac->actor, object) == 0; |
| 658 | } | 672 | } |
| 659 | 673 | ||
| 674 | /* only accept Ping directed to us */ | ||
| 675 | if (xs_match(type, "Ping")) { | ||
| 676 | char *dest = xs_dict_get(c_msg, "to"); | ||
| 677 | return !xs_is_null(dest) && strcmp(snac->actor, dest) == 0; | ||
| 678 | } | ||
| 679 | |||
| 660 | /* if it's not a Create or Update, allow as is */ | 680 | /* if it's not a Create or Update, allow as is */ |
| 661 | if (!xs_match(type, "Create|Update")) { | 681 | if (!xs_match(type, "Create|Update")) { |
| 662 | return 1; | 682 | return 1; |
| @@ -1072,7 +1092,7 @@ xs_dict *msg_collection(snac *snac, char *id) | |||
| 1072 | 1092 | ||
| 1073 | msg = xs_dict_append(msg, "attributedTo", snac->actor); | 1093 | msg = xs_dict_append(msg, "attributedTo", snac->actor); |
| 1074 | msg = xs_dict_append(msg, "orderedItems", ol); | 1094 | msg = xs_dict_append(msg, "orderedItems", ol); |
| 1075 | msg = xs_dict_append(msg, "totalItems", xs_stock_0); | 1095 | msg = xs_dict_append(msg, "totalItems", xs_stock(0)); |
| 1076 | 1096 | ||
| 1077 | return msg; | 1097 | return msg; |
| 1078 | } | 1098 | } |
| @@ -1129,8 +1149,10 @@ xs_dict *msg_admiration(snac *snac, char *object, char *type) | |||
| 1129 | 1149 | ||
| 1130 | if (valid_status(object_get(object, &a_msg))) { | 1150 | if (valid_status(object_get(object, &a_msg))) { |
| 1131 | xs *rcpts = xs_list_new(); | 1151 | xs *rcpts = xs_list_new(); |
| 1152 | xs *o_md5 = xs_md5_hex(object, strlen(object)); | ||
| 1153 | xs *id = xs_fmt("%s/%s/%s", snac->actor, *type == 'L' ? "l" : "a", o_md5); | ||
| 1132 | 1154 | ||
| 1133 | msg = msg_base(snac, type, "@dummy", snac->actor, "@now", object); | 1155 | msg = msg_base(snac, type, id, snac->actor, "@now", object); |
| 1134 | 1156 | ||
| 1135 | if (is_msg_public(a_msg)) | 1157 | if (is_msg_public(a_msg)) |
| 1136 | rcpts = xs_list_append(rcpts, public_address); | 1158 | rcpts = xs_list_append(rcpts, public_address); |
| @@ -1146,6 +1168,33 @@ xs_dict *msg_admiration(snac *snac, char *object, char *type) | |||
| 1146 | } | 1168 | } |
| 1147 | 1169 | ||
| 1148 | 1170 | ||
| 1171 | xs_dict *msg_repulsion(snac *user, char *id, char *type) | ||
| 1172 | /* creates an Undo + admiration message */ | ||
| 1173 | { | ||
| 1174 | xs *a_msg = NULL; | ||
| 1175 | xs_dict *msg = NULL; | ||
| 1176 | |||
| 1177 | if (valid_status(object_get(id, &a_msg))) { | ||
| 1178 | /* create a clone of the original admiration message */ | ||
| 1179 | xs *object = msg_admiration(user, id, type); | ||
| 1180 | |||
| 1181 | /* delete the published date */ | ||
| 1182 | object = xs_dict_del(object, "published"); | ||
| 1183 | |||
| 1184 | /* create an undo message for this object */ | ||
| 1185 | msg = msg_undo(user, object); | ||
| 1186 | |||
| 1187 | /* copy the 'to' field */ | ||
| 1188 | msg = xs_dict_set(msg, "to", xs_dict_get(object, "to")); | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | /* now we despise this */ | ||
| 1192 | object_unadmire(id, user->actor, *type == 'L' ? 1 : 0); | ||
| 1193 | |||
| 1194 | return msg; | ||
| 1195 | } | ||
| 1196 | |||
| 1197 | |||
| 1149 | xs_dict *msg_actor(snac *snac) | 1198 | xs_dict *msg_actor(snac *snac) |
| 1150 | /* create a Person message for this actor */ | 1199 | /* create a Person message for this actor */ |
| 1151 | { | 1200 | { |
| @@ -1170,7 +1219,7 @@ xs_dict *msg_actor(snac *snac) | |||
| 1170 | msg = xs_dict_set(msg, "preferredUsername", snac->uid); | 1219 | msg = xs_dict_set(msg, "preferredUsername", snac->uid); |
| 1171 | msg = xs_dict_set(msg, "published", xs_dict_get(snac->config, "published")); | 1220 | msg = xs_dict_set(msg, "published", xs_dict_get(snac->config, "published")); |
| 1172 | 1221 | ||
| 1173 | xs *f_bio_2 = not_really_markdown(xs_dict_get(snac->config, "bio"), NULL); | 1222 | xs *f_bio_2 = not_really_markdown(xs_dict_get(snac->config, "bio"), NULL, NULL); |
| 1174 | f_bio = process_tags(snac, f_bio_2, &tags); | 1223 | f_bio = process_tags(snac, f_bio_2, &tags); |
| 1175 | msg = xs_dict_set(msg, "summary", f_bio); | 1224 | msg = xs_dict_set(msg, "summary", f_bio); |
| 1176 | msg = xs_dict_set(msg, "tag", tags); | 1225 | msg = xs_dict_set(msg, "tag", tags); |
| @@ -1378,7 +1427,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1378 | } | 1427 | } |
| 1379 | 1428 | ||
| 1380 | /* format the content */ | 1429 | /* format the content */ |
| 1381 | fc2 = not_really_markdown(content, &atls); | 1430 | fc2 = not_really_markdown(content, &atls, &tag); |
| 1382 | 1431 | ||
| 1383 | if (in_reply_to != NULL && *in_reply_to) { | 1432 | if (in_reply_to != NULL && *in_reply_to) { |
| 1384 | xs *p_msg = NULL; | 1433 | xs *p_msg = NULL; |
| @@ -1556,6 +1605,7 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | |||
| 1556 | } | 1605 | } |
| 1557 | 1606 | ||
| 1558 | if (xs_set_add(&seen, v2) == 1) { | 1607 | if (xs_set_add(&seen, v2) == 1) { |
| 1608 | d = xs_dict_append(d, "type", "Note"); | ||
| 1559 | d = xs_dict_append(d, "name", v2); | 1609 | d = xs_dict_append(d, "name", v2); |
| 1560 | d = xs_dict_append(d, "replies", replies); | 1610 | d = xs_dict_append(d, "replies", replies); |
| 1561 | o = xs_list_append(o, d); | 1611 | o = xs_list_append(o, d); |
| @@ -1608,7 +1658,7 @@ int update_question(snac *user, const char *id) | |||
| 1608 | const char *name = xs_dict_get(v, "name"); | 1658 | const char *name = xs_dict_get(v, "name"); |
| 1609 | if (name) { | 1659 | if (name) { |
| 1610 | lopts = xs_list_append(lopts, name); | 1660 | lopts = xs_list_append(lopts, name); |
| 1611 | rcnt = xs_dict_set(rcnt, name, xs_stock_0); | 1661 | rcnt = xs_dict_set(rcnt, name, xs_stock(0)); |
| 1612 | } | 1662 | } |
| 1613 | } | 1663 | } |
| 1614 | 1664 | ||
| @@ -1891,6 +1941,8 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1891 | } | 1941 | } |
| 1892 | else | 1942 | else |
| 1893 | if (strcmp(type, "Undo") == 0) { /** **/ | 1943 | if (strcmp(type, "Undo") == 0) { /** **/ |
| 1944 | char *id = xs_dict_get(object, "object"); | ||
| 1945 | |||
| 1894 | if (xs_type(object) != XSTYPE_DICT) | 1946 | if (xs_type(object) != XSTYPE_DICT) |
| 1895 | utype = "Follow"; | 1947 | utype = "Follow"; |
| 1896 | 1948 | ||
| @@ -1903,6 +1955,23 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1903 | snac_log(snac, xs_fmt("error deleting follower %s", actor)); | 1955 | snac_log(snac, xs_fmt("error deleting follower %s", actor)); |
| 1904 | } | 1956 | } |
| 1905 | else | 1957 | else |
| 1958 | if (strcmp(utype, "Like") == 0) { /** **/ | ||
| 1959 | int status = object_unadmire(id, actor, 1); | ||
| 1960 | |||
| 1961 | snac_log(snac, xs_fmt("Unlike for %s %d", id, status)); | ||
| 1962 | } | ||
| 1963 | else | ||
| 1964 | if (strcmp(utype, "Announce") == 0) { /** **/ | ||
| 1965 | int status = 200; | ||
| 1966 | |||
| 1967 | /* commented out: if a followed user boosts something that | ||
| 1968 | is requested and then unboosts, the post remains here, | ||
| 1969 | but with no apparent reason, and that is confusing */ | ||
| 1970 | //status = object_unadmire(id, actor, 0); | ||
| 1971 | |||
| 1972 | snac_log(snac, xs_fmt("Unboost for %s %d", id, status)); | ||
| 1973 | } | ||
| 1974 | else | ||
| 1906 | snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype)); | 1975 | snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype)); |
| 1907 | } | 1976 | } |
| 1908 | else | 1977 | else |
| @@ -1912,7 +1981,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1912 | return 1; | 1981 | return 1; |
| 1913 | } | 1982 | } |
| 1914 | 1983 | ||
| 1915 | if (strcmp(utype, "Note") == 0) { /** **/ | 1984 | if (xs_match(utype, "Note|Article")) { /** **/ |
| 1916 | char *id = xs_dict_get(object, "id"); | 1985 | char *id = xs_dict_get(object, "id"); |
| 1917 | char *in_reply_to = xs_dict_get(object, "inReplyTo"); | 1986 | char *in_reply_to = xs_dict_get(object, "inReplyTo"); |
| 1918 | xs *wrk = NULL; | 1987 | xs *wrk = NULL; |
| @@ -1921,10 +1990,15 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1921 | snac_debug(snac, 0, xs_fmt("dropped reply %s to hidden post %s", id, in_reply_to)); | 1990 | snac_debug(snac, 0, xs_fmt("dropped reply %s to hidden post %s", id, in_reply_to)); |
| 1922 | } | 1991 | } |
| 1923 | else { | 1992 | else { |
| 1993 | if (content_check("filter_reject.txt", object)) { | ||
| 1994 | snac_log(snac, xs_fmt("rejected by content %s", id)); | ||
| 1995 | return 1; | ||
| 1996 | } | ||
| 1997 | |||
| 1924 | timeline_request(snac, &in_reply_to, &wrk, 0); | 1998 | timeline_request(snac, &in_reply_to, &wrk, 0); |
| 1925 | 1999 | ||
| 1926 | if (timeline_add(snac, id, object)) { | 2000 | if (timeline_add(snac, id, object)) { |
| 1927 | snac_log(snac, xs_fmt("new 'Note' %s %s", actor, id)); | 2001 | snac_log(snac, xs_fmt("new '%s' %s %s", utype, actor, id)); |
| 1928 | do_notify = 1; | 2002 | do_notify = 1; |
| 1929 | } | 2003 | } |
| 1930 | 2004 | ||
| @@ -1988,12 +2062,12 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1988 | if (xs_type(object) == XSTYPE_DICT) | 2062 | if (xs_type(object) == XSTYPE_DICT) |
| 1989 | object = xs_dict_get(object, "id"); | 2063 | object = xs_dict_get(object, "id"); |
| 1990 | 2064 | ||
| 1991 | if (timeline_admire(snac, object, actor, 1) == 201) { | 2065 | if (timeline_admire(snac, object, actor, 1) == 201) |
| 1992 | snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object)); | 2066 | snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object)); |
| 1993 | do_notify = 1; | ||
| 1994 | } | ||
| 1995 | else | 2067 | else |
| 1996 | snac_log(snac, xs_fmt("repeated 'Like' from %s to %s", actor, object)); | 2068 | snac_log(snac, xs_fmt("repeated 'Like' from %s to %s", actor, object)); |
| 2069 | |||
| 2070 | do_notify = 1; | ||
| 1997 | } | 2071 | } |
| 1998 | else | 2072 | else |
| 1999 | if (strcmp(type, "Announce") == 0) { /** **/ | 2073 | if (strcmp(type, "Announce") == 0) { /** **/ |
| @@ -2019,13 +2093,13 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 2019 | xs *who_o = NULL; | 2093 | xs *who_o = NULL; |
| 2020 | 2094 | ||
| 2021 | if (valid_status(actor_request(snac, who, &who_o))) { | 2095 | if (valid_status(actor_request(snac, who, &who_o))) { |
| 2022 | if (timeline_admire(snac, object, actor, 0) == 201) { | 2096 | if (timeline_admire(snac, object, actor, 0) == 201) |
| 2023 | snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); | 2097 | snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); |
| 2024 | do_notify = 1; | ||
| 2025 | } | ||
| 2026 | else | 2098 | else |
| 2027 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", | 2099 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", |
| 2028 | actor, object)); | 2100 | actor, object)); |
| 2101 | |||
| 2102 | do_notify = 1; | ||
| 2029 | } | 2103 | } |
| 2030 | else | 2104 | else |
| 2031 | snac_debug(snac, 1, xs_fmt("dropped 'Announce' on actor request error %s", who)); | 2105 | snac_debug(snac, 1, xs_fmt("dropped 'Announce' on actor request error %s", who)); |
| @@ -2236,6 +2310,24 @@ void process_user_queue_item(snac *snac, xs_dict *q_item) | |||
| 2236 | verify_links(snac); | 2310 | verify_links(snac); |
| 2237 | } | 2311 | } |
| 2238 | else | 2312 | else |
| 2313 | if (strcmp(type, "actor_refresh") == 0) { | ||
| 2314 | const char *actor = xs_dict_get(q_item, "actor"); | ||
| 2315 | double mtime = object_mtime(actor); | ||
| 2316 | |||
| 2317 | /* only refresh if it was refreshed more than an hour ago */ | ||
| 2318 | if (mtime + 3600.0 < (double) time(NULL)) { | ||
| 2319 | xs *actor_o = NULL; | ||
| 2320 | int status; | ||
| 2321 | |||
| 2322 | if (valid_status((status = activitypub_request(snac, actor, &actor_o)))) | ||
| 2323 | actor_add(actor, actor_o); | ||
| 2324 | else | ||
| 2325 | object_touch(actor); | ||
| 2326 | |||
| 2327 | snac_log(snac, xs_fmt("actor_refresh %s %d", actor, status)); | ||
| 2328 | } | ||
| 2329 | } | ||
| 2330 | else | ||
| 2239 | snac_log(snac, xs_fmt("unexpected user q_item type '%s'", type)); | 2331 | snac_log(snac, xs_fmt("unexpected user q_item type '%s'", type)); |
| 2240 | } | 2332 | } |
| 2241 | 2333 | ||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "xs_glob.h" | 9 | #include "xs_glob.h" |
| 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 | 13 | ||
| 13 | #include "snac.h" | 14 | #include "snac.h" |
| 14 | 15 | ||
| @@ -116,21 +117,33 @@ int srv_open(char *basedir, int auto_upgrade) | |||
| 116 | srv_debug(1, xs_dup("OpenBSD security disabled by admin")); | 117 | srv_debug(1, xs_dup("OpenBSD security disabled by admin")); |
| 117 | } | 118 | } |
| 118 | else { | 119 | else { |
| 120 | int smail = xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE; | ||
| 121 | |||
| 119 | srv_debug(1, xs_fmt("Calling unveil()")); | 122 | srv_debug(1, xs_fmt("Calling unveil()")); |
| 120 | unveil(basedir, "rwc"); | 123 | unveil(basedir, "rwc"); |
| 121 | unveil("/tmp", "rwc"); | 124 | unveil("/tmp", "rwc"); |
| 122 | unveil("/usr/sbin/sendmail", "x"); | ||
| 123 | unveil("/etc/resolv.conf", "r"); | 125 | unveil("/etc/resolv.conf", "r"); |
| 124 | unveil("/etc/hosts", "r"); | 126 | unveil("/etc/hosts", "r"); |
| 125 | unveil("/etc/ssl/openssl.cnf", "r"); | 127 | unveil("/etc/ssl/openssl.cnf", "r"); |
| 126 | unveil("/etc/ssl/cert.pem", "r"); | 128 | unveil("/etc/ssl/cert.pem", "r"); |
| 127 | unveil("/usr/share/zoneinfo", "r"); | 129 | unveil("/usr/share/zoneinfo", "r"); |
| 130 | |||
| 131 | if (smail) | ||
| 132 | unveil("/usr/sbin/sendmail", "x"); | ||
| 133 | |||
| 128 | unveil(NULL, NULL); | 134 | unveil(NULL, NULL); |
| 129 | srv_debug(1, xs_fmt("Calling pledge()")); | 135 | srv_debug(1, xs_fmt("Calling pledge()")); |
| 130 | pledge("stdio rpath wpath cpath flock inet proc exec dns fattr", NULL); | 136 | |
| 137 | if (smail) | ||
| 138 | pledge("stdio rpath wpath cpath flock inet proc exec dns fattr", NULL); | ||
| 139 | else | ||
| 140 | pledge("stdio rpath wpath cpath flock inet proc dns fattr", NULL); | ||
| 131 | } | 141 | } |
| 132 | #endif /* __OpenBSD__ */ | 142 | #endif /* __OpenBSD__ */ |
| 133 | 143 | ||
| 144 | /* read (and drop) emojis.json, possibly creating it */ | ||
| 145 | xs_free(emojis()); | ||
| 146 | |||
| 134 | return ret; | 147 | return ret; |
| 135 | } | 148 | } |
| 136 | 149 | ||
| @@ -393,7 +406,7 @@ int index_del_md5(const char *fn, const char *md5) | |||
| 393 | fclose(f); | 406 | fclose(f); |
| 394 | } | 407 | } |
| 395 | else | 408 | else |
| 396 | status = 500; | 409 | status = 410; |
| 397 | 410 | ||
| 398 | pthread_mutex_unlock(&data_mutex); | 411 | pthread_mutex_unlock(&data_mutex); |
| 399 | 412 | ||
| @@ -796,6 +809,30 @@ double object_ctime(const char *id) | |||
| 796 | } | 809 | } |
| 797 | 810 | ||
| 798 | 811 | ||
| 812 | double object_mtime_by_md5(const char *md5) | ||
| 813 | { | ||
| 814 | xs *fn = _object_fn_by_md5(md5, "object_mtime_by_md5"); | ||
| 815 | return mtime(fn); | ||
| 816 | } | ||
| 817 | |||
| 818 | |||
| 819 | double object_mtime(const char *id) | ||
| 820 | { | ||
| 821 | xs *md5 = xs_md5_hex(id, strlen(id)); | ||
| 822 | return object_mtime_by_md5(md5); | ||
| 823 | } | ||
| 824 | |||
| 825 | |||
| 826 | void object_touch(const char *id) | ||
| 827 | { | ||
| 828 | xs *md5 = xs_md5_hex(id, strlen(id)); | ||
| 829 | xs *fn = _object_fn_by_md5(md5, "object_touch"); | ||
| 830 | |||
| 831 | if (mtime(fn)) | ||
| 832 | utimes(fn, NULL); | ||
| 833 | } | ||
| 834 | |||
| 835 | |||
| 799 | xs_str *_object_index_fn(const char *id, const char *idxsfx) | 836 | xs_str *_object_index_fn(const char *id, const char *idxsfx) |
| 800 | /* returns the filename of an object's index */ | 837 | /* returns the filename of an object's index */ |
| 801 | { | 838 | { |
| @@ -880,6 +917,9 @@ int object_unadmire(const char *id, const char *actor, int like) | |||
| 880 | 917 | ||
| 881 | status = index_del(fn, actor); | 918 | status = index_del(fn, actor); |
| 882 | 919 | ||
| 920 | if (valid_status(status)) | ||
| 921 | index_gc(fn); | ||
| 922 | |||
| 883 | srv_debug(0, | 923 | srv_debug(0, |
| 884 | xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); | 924 | xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); |
| 885 | 925 | ||
| @@ -1551,7 +1591,6 @@ int actor_get(const char *actor, xs_dict **data) | |||
| 1551 | else | 1591 | else |
| 1552 | d = xs_free(d); | 1592 | d = xs_free(d); |
| 1553 | 1593 | ||
| 1554 | #ifdef STALE_ACTORS | ||
| 1555 | xs *fn = _object_fn(actor); | 1594 | xs *fn = _object_fn(actor); |
| 1556 | double max_time; | 1595 | double max_time; |
| 1557 | 1596 | ||
| @@ -1560,13 +1599,20 @@ int actor_get(const char *actor, xs_dict **data) | |||
| 1560 | 1599 | ||
| 1561 | if (mtime(fn) + max_time < (double) time(NULL)) { | 1600 | if (mtime(fn) + max_time < (double) time(NULL)) { |
| 1562 | /* actor data exists but also stinks */ | 1601 | /* actor data exists but also stinks */ |
| 1563 | |||
| 1564 | /* touch the file */ | ||
| 1565 | utimes(fn, NULL); | ||
| 1566 | |||
| 1567 | status = 205; /* "205: Reset Content" "110: Response Is Stale" */ | 1602 | status = 205; /* "205: Reset Content" "110: Response Is Stale" */ |
| 1568 | } | 1603 | } |
| 1569 | #endif /* STALE_ACTORS */ | 1604 | |
| 1605 | return status; | ||
| 1606 | } | ||
| 1607 | |||
| 1608 | |||
| 1609 | int actor_get_refresh(snac *user, const char *actor, xs_dict **data) | ||
| 1610 | /* gets an actor and requests a refresh if it's stale */ | ||
| 1611 | { | ||
| 1612 | int status = actor_get(actor, data); | ||
| 1613 | |||
| 1614 | if (status == 205 && user && !xs_startswith(actor, srv_baseurl)) | ||
| 1615 | enqueue_actor_refresh(user, actor); | ||
| 1570 | 1616 | ||
| 1571 | return status; | 1617 | return status; |
| 1572 | } | 1618 | } |
| @@ -2007,6 +2053,47 @@ int instance_unblock(const char *instance) | |||
| 2007 | } | 2053 | } |
| 2008 | 2054 | ||
| 2009 | 2055 | ||
| 2056 | /** content filtering **/ | ||
| 2057 | |||
| 2058 | int content_check(const char *file, const xs_dict *msg) | ||
| 2059 | /* checks if a message's content matches any of the regexes in file */ | ||
| 2060 | /* file format: one regex per line */ | ||
| 2061 | { | ||
| 2062 | xs *fn = xs_fmt("%s/%s", srv_basedir, file); | ||
| 2063 | FILE *f; | ||
| 2064 | int r = 0; | ||
| 2065 | char *v = xs_dict_get(msg, "content"); | ||
| 2066 | |||
| 2067 | if (xs_type(v) == XSTYPE_STRING && *v) { | ||
| 2068 | if ((f = fopen(fn, "r")) != NULL) { | ||
| 2069 | srv_debug(1, xs_fmt("content_check: loading regexes from %s", fn)); | ||
| 2070 | |||
| 2071 | /* massage content (strip HTML tags, etc.) */ | ||
| 2072 | xs *c = xs_regex_replace(v, "<[^>]+>", " "); | ||
| 2073 | c = xs_regex_replace_i(c, " {2,}", " "); | ||
| 2074 | c = xs_tolower_i(c); | ||
| 2075 | |||
| 2076 | while (!r && !feof(f)) { | ||
| 2077 | xs *rx = xs_strip_i(xs_readline(f)); | ||
| 2078 | |||
| 2079 | if (*rx) { | ||
| 2080 | xs *l = xs_regex_select_n(c, rx, 1); | ||
| 2081 | |||
| 2082 | if (xs_list_len(l)) { | ||
| 2083 | srv_debug(1, xs_fmt("content_check: match for '%s'", rx)); | ||
| 2084 | r = 1; | ||
| 2085 | } | ||
| 2086 | } | ||
| 2087 | } | ||
| 2088 | |||
| 2089 | fclose(f); | ||
| 2090 | } | ||
| 2091 | } | ||
| 2092 | |||
| 2093 | return r; | ||
| 2094 | } | ||
| 2095 | |||
| 2096 | |||
| 2010 | /** notifications **/ | 2097 | /** notifications **/ |
| 2011 | 2098 | ||
| 2012 | xs_str *notify_check_time(snac *snac, int reset) | 2099 | xs_str *notify_check_time(snac *snac, int reset) |
| @@ -2388,6 +2475,21 @@ void enqueue_verify_links(snac *user) | |||
| 2388 | } | 2475 | } |
| 2389 | 2476 | ||
| 2390 | 2477 | ||
| 2478 | void enqueue_actor_refresh(snac *user, const char *actor) | ||
| 2479 | /* enqueues an actor refresh */ | ||
| 2480 | { | ||
| 2481 | xs *qmsg = _new_qmsg("actor_refresh", "", 0); | ||
| 2482 | char *ntid = xs_dict_get(qmsg, "ntid"); | ||
| 2483 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); | ||
| 2484 | |||
| 2485 | qmsg = xs_dict_append(qmsg, "actor", actor); | ||
| 2486 | |||
| 2487 | qmsg = _enqueue_put(fn, qmsg); | ||
| 2488 | |||
| 2489 | snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor)); | ||
| 2490 | } | ||
| 2491 | |||
| 2492 | |||
| 2391 | void enqueue_request_replies(snac *user, const char *id) | 2493 | void enqueue_request_replies(snac *user, const char *id) |
| 2392 | /* enqueues a request for the replies of a message */ | 2494 | /* enqueues a request for the replies of a message */ |
| 2393 | { | 2495 | { |
| @@ -2645,6 +2747,16 @@ void purge_server(void) | |||
| 2645 | } | 2747 | } |
| 2646 | } | 2748 | } |
| 2647 | } | 2749 | } |
| 2750 | |||
| 2751 | /* delete index backups */ | ||
| 2752 | xs *specb = xs_fmt("%s/" "*.bak", v); | ||
| 2753 | xs *bakfs = xs_glob(specb, 0, 0); | ||
| 2754 | |||
| 2755 | p2 = bakfs; | ||
| 2756 | while (xs_list_iter(&p2, &v2)) { | ||
| 2757 | unlink(v2); | ||
| 2758 | srv_debug(1, xs_fmt("purged %s", v2)); | ||
| 2759 | } | ||
| 2648 | } | 2760 | } |
| 2649 | } | 2761 | } |
| 2650 | 2762 | ||
| @@ -221,12 +221,16 @@ Sends a Follow message for the specified actor URL. | |||
| 221 | .It Cm request Ar basedir Ar uid Ar url | 221 | .It Cm request Ar basedir Ar uid Ar url |
| 222 | Requests an object and dumps it to stdout. This is a very low | 222 | Requests an object and dumps it to stdout. This is a very low |
| 223 | level command that is not very useful to you. | 223 | level command that is not very useful to you. |
| 224 | .It Cm note Ar basedir Ar uid Ar text | 224 | .It Cm announce Ar basedir Ar uid Ar url |
| 225 | Announces (boosts) a post via its URL. | ||
| 226 | .It Cm note Ar basedir Ar uid Ar text Op file file ... | ||
| 225 | Enqueues a Create + Note message to all followers. If the | 227 | Enqueues a Create + Note message to all followers. If the |
| 226 | .Ar text | 228 | .Ar text |
| 227 | argument is -e, the external editor defined by the EDITOR | 229 | argument is -e, the external editor defined by the EDITOR |
| 228 | environment variable will be invoked to prepare a message; if | 230 | environment variable will be invoked to prepare a message; if |
| 229 | it's - (a lonely hyphen), the post content will be read from stdin. | 231 | it's - (a lonely hyphen), the post content will be read from stdin. |
| 232 | The rest of command line arguments are treated as media files to be | ||
| 233 | attached to the post. | ||
| 230 | .It Cm block Ar basedir Ar instance_url | 234 | .It Cm block Ar basedir Ar instance_url |
| 231 | Blocks a full instance, given its URL or domain name. All subsequent | 235 | Blocks a full instance, given its URL or domain name. All subsequent |
| 232 | incoming activities with identifiers from that instance will be immediately | 236 | incoming activities with identifiers from that instance will be immediately |
| @@ -46,6 +46,9 @@ Strings in the format @user@host are requested using the Webfinger | |||
| 46 | protocol and converted to links and mentions if something reasonable | 46 | protocol and converted to links and mentions if something reasonable |
| 47 | is found. | 47 | is found. |
| 48 | .It Emoticons / Smileys / Silly Symbols | 48 | .It Emoticons / Smileys / Silly Symbols |
| 49 | (Note: from version 2.51, these symbols are configurable by the | ||
| 50 | instance administrator, so the available ones may differ). | ||
| 51 | .Pp | ||
| 49 | The following traditional ASCII emoticons or special strings are | 52 | The following traditional ASCII emoticons or special strings are |
| 50 | converted to related emojis: | 53 | converted to related emojis: |
| 51 | .Bd -literal | 54 | .Bd -literal |
| @@ -106,6 +109,13 @@ This file is served when the server base URL is requested from a web browser. Se | |||
| 106 | for more information about the customization options. | 109 | for more information about the customization options. |
| 107 | .It Pa public.idx | 110 | .It Pa public.idx |
| 108 | This file contains the list of public posts from all users in the server. | 111 | This file contains the list of public posts from all users in the server. |
| 112 | .It Pa filter_reject.txt | ||
| 113 | This (optional) file contains a list of regular expressions, one per line, to be | ||
| 114 | applied to the content of all incoming posts; if any of them match, the post is | ||
| 115 | rejected. This brings the flexibility and destruction power of regular expressions | ||
| 116 | to your Fediverse experience. To be used wisely (see | ||
| 117 | .Xr snac 8 | ||
| 118 | for more information). | ||
| 109 | .El | 119 | .El |
| 110 | .Pp | 120 | .Pp |
| 111 | Each user directory is a subdirectory of | 121 | Each user directory is a subdirectory of |
| @@ -230,9 +230,56 @@ for details. Further, every user can have a private CSS file in their | |||
| 230 | that will be served instead of the server-wide one. | 230 | that will be served instead of the server-wide one. |
| 231 | It's not modifiable from the web interface to avoid users | 231 | It's not modifiable from the web interface to avoid users |
| 232 | shooting themselves in the foot by destroying everything. | 232 | shooting themselves in the foot by destroying everything. |
| 233 | .Ss Old Data Purging | 233 | .Ss Custom Emojis |
| 234 | From version 2.06, there is no longer a need to add a special | 234 | From version 2.51, support for customized Emojis in posts is available |
| 235 | cron job for purging old data, as this is managed internally. | 235 | (previously, they were hardcoded). Emojis are read from the |
| 236 | .Pa emojis.json | ||
| 237 | file in the instance base directory, as a JSON object of key / value | ||
| 238 | pairs (if this file does not exist, it will be created with | ||
| 239 | the predefined set). Each key in the object contains the text to be found (e.g., | ||
| 240 | the :-) for a smiling face), and its associated value, the text string that | ||
| 241 | will replace it (in this example case, the HTML entity for the Unicode codepoint | ||
| 242 | for the smiley or the Emoji itself as text). | ||
| 243 | .Pp | ||
| 244 | Emoji values can also be URLs to image files; in this case, they will not be | ||
| 245 | substituted in the post content, but added to the 'tag' array as an ActivityPub | ||
| 246 | standard 'Emoji' object (it's recommendable that the Emoji key be enclosed in | ||
| 247 | colons for maximum compatilibity with other ActivityPub implementations, like | ||
| 248 | e.g. :happydoggo:). These images can be served from an external source or from the | ||
| 249 | .Pa static | ||
| 250 | directory of the instance admin. | ||
| 251 | .Pp | ||
| 252 | If you want to disable any Emoji substitution, change the file to contain | ||
| 253 | just an empty JSON object ({}). | ||
| 254 | .Ss SPAM Mitigation | ||
| 255 | There have been some SPAM attacks on the Fediverse and, as too many | ||
| 256 | instances and server implementations out there still allow automatic | ||
| 257 | account creation, it will only get worse. | ||
| 258 | .Nm | ||
| 259 | includes some (not very strong) tools for trying to survive the SPAM | ||
| 260 | flood that will eventually happen. | ||
| 261 | .Pp | ||
| 262 | The | ||
| 263 | .Ic min_account_age | ||
| 264 | field in the main configuration file allows setting a minimum age (in | ||
| 265 | seconds) to consider too recently created accounts suspicious of being | ||
| 266 | a potential source of SPAM. This is a naïve assumption, because spammers | ||
| 267 | can create accounts, let them dormant for a while and then start to use | ||
| 268 | them. Also, some ActivityPub implementations don't even bother to return | ||
| 269 | a creation date for their accounts, so this is not very useful. | ||
| 270 | .Pp | ||
| 271 | From version 2.50, post content can be filtered out by regular expressions. | ||
| 272 | These weapons of mass destruction can be written into the | ||
| 273 | .Ic filter_reject.txt | ||
| 274 | file in the server base directory, one per line; if this file exists, | ||
| 275 | all posts' content will be matched (after being stripped of HTML tags) | ||
| 276 | against these regexes, one by one, and any match will make the post to | ||
| 277 | be rejected. If you don't know about regular expressions, don't use this | ||
| 278 | option (or learn about them in some tutorial, there are gazillions of | ||
| 279 | them out there), as you and your users may start missing posts. Also, | ||
| 280 | given that every regular expression implementation supports a different | ||
| 281 | set of features, consider reading the documentation about the one | ||
| 282 | implemented in your system. | ||
| 236 | .Ss ActivityPub Support | 283 | .Ss ActivityPub Support |
| 237 | These are the following activities and objects that | 284 | These are the following activities and objects that |
| 238 | .Nm | 285 | .Nm |
| @@ -421,7 +468,7 @@ This is an example of a similar configuration for the Apache2 web server: | |||
| 421 | ProxyPreserveHost On | 468 | ProxyPreserveHost On |
| 422 | 469 | ||
| 423 | # Main web access point | 470 | # Main web access point |
| 424 | <Location /social> | 471 | <Location /fedi> |
| 425 | ProxyPass http://127.0.0.1:8001/social | 472 | ProxyPass http://127.0.0.1:8001/social |
| 426 | </Location> | 473 | </Location> |
| 427 | 474 | ||
| @@ -481,7 +528,7 @@ an example: | |||
| 481 | # other server configuration | 528 | # other server configuration |
| 482 | [...] | 529 | [...] |
| 483 | 530 | ||
| 484 | location "/fedi*" { | 531 | location "/fedi/*" { |
| 485 | fastcgi socket tcp "127.0.0.1" 8001 | 532 | fastcgi socket tcp "127.0.0.1" 8001 |
| 486 | } | 533 | } |
| 487 | 534 | ||
| @@ -5,6 +5,8 @@ | |||
| 5 | #include "xs_regex.h" | 5 | #include "xs_regex.h" |
| 6 | #include "xs_mime.h" | 6 | #include "xs_mime.h" |
| 7 | #include "xs_html.h" | 7 | #include "xs_html.h" |
| 8 | #include "xs_json.h" | ||
| 9 | #include "xs_time.h" | ||
| 8 | 10 | ||
| 9 | #include "snac.h" | 11 | #include "snac.h" |
| 10 | 12 | ||
| @@ -36,6 +38,46 @@ const char *smileys[] = { | |||
| 36 | }; | 38 | }; |
| 37 | 39 | ||
| 38 | 40 | ||
| 41 | xs_dict *emojis(void) | ||
| 42 | /* returns a dict with the emojis */ | ||
| 43 | { | ||
| 44 | xs *fn = xs_fmt("%s/emojis.json", srv_basedir); | ||
| 45 | FILE *f; | ||
| 46 | |||
| 47 | if (mtime(fn) == 0) { | ||
| 48 | /* file does not exist; create it with the defaults */ | ||
| 49 | xs *d = xs_dict_new(); | ||
| 50 | const char **emo = smileys; | ||
| 51 | |||
| 52 | while (*emo) { | ||
| 53 | d = xs_dict_append(d, emo[0], emo[1]); | ||
| 54 | emo += 2; | ||
| 55 | } | ||
| 56 | |||
| 57 | if ((f = fopen(fn, "w")) != NULL) { | ||
| 58 | xs_json_dump(d, 4, f); | ||
| 59 | fclose(f); | ||
| 60 | } | ||
| 61 | else | ||
| 62 | srv_log(xs_fmt("Error creating '%s'", fn)); | ||
| 63 | } | ||
| 64 | |||
| 65 | xs_dict *d = NULL; | ||
| 66 | |||
| 67 | if ((f = fopen(fn, "r")) != NULL) { | ||
| 68 | d = xs_json_load(f); | ||
| 69 | fclose(f); | ||
| 70 | |||
| 71 | if (d == NULL) | ||
| 72 | srv_log(xs_fmt("JSON parse error in '%s'", fn)); | ||
| 73 | } | ||
| 74 | else | ||
| 75 | srv_log(xs_fmt("Error opening '%s'", fn)); | ||
| 76 | |||
| 77 | return d; | ||
| 78 | } | ||
| 79 | |||
| 80 | |||
| 39 | static xs_str *format_line(const char *line, xs_list **attach) | 81 | static xs_str *format_line(const char *line, xs_list **attach) |
| 40 | /* formats a line */ | 82 | /* formats a line */ |
| 41 | { | 83 | { |
| @@ -106,7 +148,7 @@ static xs_str *format_line(const char *line, xs_list **attach) | |||
| 106 | } | 148 | } |
| 107 | 149 | ||
| 108 | 150 | ||
| 109 | xs_str *not_really_markdown(const char *content, xs_list **attach) | 151 | xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag) |
| 110 | /* formats a content using some Markdown rules */ | 152 | /* formats a content using some Markdown rules */ |
| 111 | { | 153 | { |
| 112 | xs_str *s = xs_str_new(NULL); | 154 | xs_str *s = xs_str_new(NULL); |
| @@ -190,11 +232,36 @@ xs_str *not_really_markdown(const char *content, xs_list **attach) | |||
| 190 | 232 | ||
| 191 | { | 233 | { |
| 192 | /* traditional emoticons */ | 234 | /* traditional emoticons */ |
| 193 | const char **emo = smileys; | 235 | xs *d = emojis(); |
| 194 | 236 | int c = 0; | |
| 195 | while (*emo) { | 237 | char *k, *v; |
| 196 | s = xs_replace_i(s, emo[0], emo[1]); | 238 | |
| 197 | emo += 2; | 239 | while (xs_dict_next(d, &k, &v, &c)) { |
| 240 | const char *t = NULL; | ||
| 241 | |||
| 242 | /* is it an URL to an image? */ | ||
| 243 | if (xs_startswith(v, "https:/" "/") && xs_startswith((t = xs_mime_by_ext(v)), "image/")) { | ||
| 244 | if (tag) { | ||
| 245 | /* add the emoji to the tag list */ | ||
| 246 | xs *e = xs_dict_new(); | ||
| 247 | xs *i = xs_dict_new(); | ||
| 248 | xs *u = xs_str_utctime(0, ISO_DATE_SPEC); | ||
| 249 | |||
| 250 | e = xs_dict_append(e, "id", v); | ||
| 251 | e = xs_dict_append(e, "type", "Emoji"); | ||
| 252 | e = xs_dict_append(e, "name", k); | ||
| 253 | e = xs_dict_append(e, "updated", u); | ||
| 254 | |||
| 255 | i = xs_dict_append(i, "type", "Image"); | ||
| 256 | i = xs_dict_append(i, "mediaType", t); | ||
| 257 | i = xs_dict_append(i, "url", v); | ||
| 258 | e = xs_dict_append(e, "icon", i); | ||
| 259 | |||
| 260 | *tag = xs_list_append(*tag, e); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | else | ||
| 264 | s = xs_replace_i(s, k, v); | ||
| 198 | } | 265 | } |
| 199 | } | 266 | } |
| 200 | 267 | ||
| @@ -113,9 +113,13 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 113 | xs *name = actor_name(actor); | 113 | xs *name = actor_name(actor); |
| 114 | 114 | ||
| 115 | /* get the avatar */ | 115 | /* get the avatar */ |
| 116 | if ((v = xs_dict_get(actor, "icon")) != NULL && | 116 | if ((v = xs_dict_get(actor, "icon")) != NULL) { |
| 117 | (v = xs_dict_get(v, "url")) != NULL) { | 117 | /* if it's a list (Peertube), get the first one */ |
| 118 | avatar = xs_dup(v); | 118 | if (xs_type(v) == XSTYPE_LIST) |
| 119 | v = xs_list_get(v, 0); | ||
| 120 | |||
| 121 | if ((v = xs_dict_get(v, "url")) != NULL) | ||
| 122 | avatar = xs_dup(v); | ||
| 119 | } | 123 | } |
| 120 | 124 | ||
| 121 | if (avatar == NULL) | 125 | if (avatar == NULL) |
| @@ -245,7 +249,7 @@ xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg) | |||
| 245 | xs *actor = NULL; | 249 | xs *actor = NULL; |
| 246 | xs_html *actor_icon = NULL; | 250 | xs_html *actor_icon = NULL; |
| 247 | 251 | ||
| 248 | if (actor_id && valid_status(actor_get(actor_id, &actor))) { | 252 | if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) { |
| 249 | char *date = NULL; | 253 | char *date = NULL; |
| 250 | char *udate = NULL; | 254 | char *udate = NULL; |
| 251 | char *url = NULL; | 255 | char *url = NULL; |
| @@ -273,7 +277,8 @@ xs_html *html_note(snac *user, char *summary, | |||
| 273 | char *edit_id, char *actor_id, | 277 | char *edit_id, char *actor_id, |
| 274 | xs_val *cw_yn, char *cw_text, | 278 | xs_val *cw_yn, char *cw_text, |
| 275 | xs_val *mnt_only, char *redir, | 279 | xs_val *mnt_only, char *redir, |
| 276 | char *in_reply_to, int poll) | 280 | char *in_reply_to, int poll, |
| 281 | char *att_file, char *att_alt_text) | ||
| 277 | { | 282 | { |
| 278 | xs *action = xs_fmt("%s/admin/note", user->actor); | 283 | xs *action = xs_fmt("%s/admin/note", user->actor); |
| 279 | 284 | ||
| @@ -359,19 +364,37 @@ xs_html *html_note(snac *user, char *summary, | |||
| 359 | xs_html_attr("name", "edit_id"), | 364 | xs_html_attr("name", "edit_id"), |
| 360 | xs_html_attr("value", edit_id))); | 365 | xs_html_attr("value", edit_id))); |
| 361 | 366 | ||
| 367 | /* attachment controls */ | ||
| 368 | xs_html *att; | ||
| 369 | |||
| 362 | xs_html_add(form, | 370 | xs_html_add(form, |
| 363 | xs_html_tag("p", NULL), | 371 | xs_html_tag("p", NULL), |
| 364 | xs_html_tag("details", | 372 | att = xs_html_tag("details", |
| 365 | xs_html_tag("summary", | 373 | xs_html_tag("summary", |
| 366 | xs_html_text(L("Attachment..."))), | 374 | xs_html_text(L("Attachment..."))), |
| 367 | xs_html_tag("p", NULL), | 375 | xs_html_tag("p", NULL))); |
| 368 | xs_html_sctag("input", | 376 | |
| 369 | xs_html_attr("type", "file"), | 377 | if (att_file && *att_file) |
| 370 | xs_html_attr("name", "attach")), | 378 | xs_html_add(att, |
| 371 | xs_html_sctag("input", | 379 | xs_html_text(L("File:")), |
| 372 | xs_html_attr("type", "text"), | 380 | xs_html_sctag("input", |
| 373 | xs_html_attr("name", "alt_text"), | 381 | xs_html_attr("type", "text"), |
| 374 | xs_html_attr("placeholder", L("Attachment description"))))); | 382 | xs_html_attr("name", "attach_url"), |
| 383 | xs_html_attr("title", L("Clear this field to delete the attachment")), | ||
| 384 | xs_html_attr("value", att_file))); | ||
| 385 | else | ||
| 386 | xs_html_add(att, | ||
| 387 | xs_html_sctag("input", | ||
| 388 | xs_html_attr("type", "file"), | ||
| 389 | xs_html_attr("name", "attach"))); | ||
| 390 | |||
| 391 | xs_html_add(att, | ||
| 392 | xs_html_text(" "), | ||
| 393 | xs_html_sctag("input", | ||
| 394 | xs_html_attr("type", "text"), | ||
| 395 | xs_html_attr("name", "alt_text"), | ||
| 396 | xs_html_attr("value", att_alt_text), | ||
| 397 | xs_html_attr("placeholder", L("Attachment description")))); | ||
| 375 | 398 | ||
| 376 | /* add poll controls */ | 399 | /* add poll controls */ |
| 377 | if (poll) { | 400 | if (poll) { |
| @@ -551,7 +574,7 @@ static xs_html *html_instance_body(char *tag) | |||
| 551 | } | 574 | } |
| 552 | 575 | ||
| 553 | 576 | ||
| 554 | xs_html *html_user_head(snac *user, char *desc) | 577 | xs_html *html_user_head(snac *user, char *desc, char *url) |
| 555 | { | 578 | { |
| 556 | xs_html *head = html_base_head(); | 579 | xs_html *head = html_base_head(); |
| 557 | 580 | ||
| @@ -641,6 +664,13 @@ xs_html *html_user_head(snac *user, char *desc) | |||
| 641 | xs_html_attr("title", "RSS"), | 664 | xs_html_attr("title", "RSS"), |
| 642 | xs_html_attr("href", rss_url))); | 665 | xs_html_attr("href", rss_url))); |
| 643 | 666 | ||
| 667 | /* ActivityPub alternate link (actor id) */ | ||
| 668 | xs_html_add(head, | ||
| 669 | xs_html_sctag("link", | ||
| 670 | xs_html_attr("rel", "alternate"), | ||
| 671 | xs_html_attr("type", "application/activity+json"), | ||
| 672 | xs_html_attr("href", url ? url : user->actor))); | ||
| 673 | |||
| 644 | return head; | 674 | return head; |
| 645 | } | 675 | } |
| 646 | 676 | ||
| @@ -756,7 +786,7 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 756 | 786 | ||
| 757 | if (read_only) { | 787 | if (read_only) { |
| 758 | xs *es1 = encode_html(xs_dict_get(user->config, "bio")); | 788 | xs *es1 = encode_html(xs_dict_get(user->config, "bio")); |
| 759 | xs *bio1 = not_really_markdown(es1, NULL); | 789 | xs *bio1 = not_really_markdown(es1, NULL, NULL); |
| 760 | xs *tags = xs_list_new(); | 790 | xs *tags = xs_list_new(); |
| 761 | xs *bio2 = process_tags(user, bio1, &tags); | 791 | xs *bio2 = process_tags(user, bio1, &tags); |
| 762 | 792 | ||
| @@ -774,7 +804,7 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 774 | 804 | ||
| 775 | xs_dict *val_links = user->links; | 805 | xs_dict *val_links = user->links; |
| 776 | if (xs_is_null(val_links)) | 806 | if (xs_is_null(val_links)) |
| 777 | val_links = xs_stock_dict; | 807 | val_links = xs_stock(XSTYPE_DICT); |
| 778 | 808 | ||
| 779 | xs_html *snac_metadata = xs_html_tag("div", | 809 | xs_html *snac_metadata = xs_html_tag("div", |
| 780 | xs_html_attr("class", "snac-metadata")); | 810 | xs_html_attr("class", "snac-metadata")); |
| @@ -852,9 +882,9 @@ xs_html *html_top_controls(snac *snac) | |||
| 852 | "new_post_div", "new_post_form", | 882 | "new_post_div", "new_post_form", |
| 853 | L("What's on your mind?"), "", | 883 | L("What's on your mind?"), "", |
| 854 | NULL, NULL, | 884 | NULL, NULL, |
| 855 | xs_stock_false, "", | 885 | xs_stock(XSTYPE_FALSE), "", |
| 856 | xs_stock_false, NULL, | 886 | xs_stock(XSTYPE_FALSE), NULL, |
| 857 | NULL, 1), | 887 | NULL, 1, "", ""), |
| 858 | 888 | ||
| 859 | /** operations **/ | 889 | /** operations **/ |
| 860 | xs_html_tag("details", | 890 | xs_html_tag("details", |
| @@ -1219,6 +1249,11 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1219 | xs_html_add(form, | 1249 | xs_html_add(form, |
| 1220 | html_button("like", L("Like"), L("Say you like this post"))); | 1250 | html_button("like", L("Like"), L("Say you like this post"))); |
| 1221 | } | 1251 | } |
| 1252 | else { | ||
| 1253 | /* not like it anymore */ | ||
| 1254 | xs_html_add(form, | ||
| 1255 | html_button("unlike", L("Unlike"), L("Nah don't like it that much"))); | ||
| 1256 | } | ||
| 1222 | } | 1257 | } |
| 1223 | else { | 1258 | else { |
| 1224 | if (is_pinned(snac, id)) | 1259 | if (is_pinned(snac, id)) |
| @@ -1235,6 +1270,11 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1235 | xs_html_add(form, | 1270 | xs_html_add(form, |
| 1236 | html_button("boost", L("Boost"), L("Announce this post to your followers"))); | 1271 | html_button("boost", L("Boost"), L("Announce this post to your followers"))); |
| 1237 | } | 1272 | } |
| 1273 | else { | ||
| 1274 | /* already boosted; add button to regret */ | ||
| 1275 | xs_html_add(form, | ||
| 1276 | html_button("unboost", L("Unboost"), L("I regret I boosted this"))); | ||
| 1277 | } | ||
| 1238 | } | 1278 | } |
| 1239 | 1279 | ||
| 1240 | if (strcmp(actor, snac->actor) != 0) { | 1280 | if (strcmp(actor, snac->actor) != 0) { |
| @@ -1278,6 +1318,20 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1278 | xs *form_id = xs_fmt("%s_edit_form", md5); | 1318 | xs *form_id = xs_fmt("%s_edit_form", md5); |
| 1279 | xs *redir = xs_fmt("%s_entry", md5); | 1319 | xs *redir = xs_fmt("%s_entry", md5); |
| 1280 | 1320 | ||
| 1321 | char *att_file = ""; | ||
| 1322 | char *att_alt_text = ""; | ||
| 1323 | xs_list *att_list = xs_dict_get(msg, "attachment"); | ||
| 1324 | |||
| 1325 | /* does it have an attachment? */ | ||
| 1326 | if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) { | ||
| 1327 | xs_dict *d = xs_list_get(att_list, 0); | ||
| 1328 | |||
| 1329 | if (xs_type(d) == XSTYPE_DICT) { | ||
| 1330 | att_file = xs_dict_get_def(d, "url", ""); | ||
| 1331 | att_alt_text = xs_dict_get_def(d, "name", ""); | ||
| 1332 | } | ||
| 1333 | } | ||
| 1334 | |||
| 1281 | xs_html_add(controls, xs_html_tag("div", | 1335 | xs_html_add(controls, xs_html_tag("div", |
| 1282 | xs_html_tag("p", NULL), | 1336 | xs_html_tag("p", NULL), |
| 1283 | html_note(snac, L("Edit..."), | 1337 | html_note(snac, L("Edit..."), |
| @@ -1285,8 +1339,8 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1285 | "", prev_src, | 1339 | "", prev_src, |
| 1286 | id, NULL, | 1340 | id, NULL, |
| 1287 | xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), | 1341 | xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), |
| 1288 | xs_stock_false, redir, | 1342 | xs_stock(XSTYPE_FALSE), redir, |
| 1289 | NULL, 0)), | 1343 | NULL, 0, att_file, att_alt_text)), |
| 1290 | xs_html_tag("p", NULL)); | 1344 | xs_html_tag("p", NULL)); |
| 1291 | } | 1345 | } |
| 1292 | 1346 | ||
| @@ -1304,8 +1358,8 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1304 | "", ct, | 1358 | "", ct, |
| 1305 | NULL, NULL, | 1359 | NULL, NULL, |
| 1306 | xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), | 1360 | xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), |
| 1307 | xs_stock_false, redir, | 1361 | xs_stock(XSTYPE_FALSE), redir, |
| 1308 | id, 0)), | 1362 | id, 0, "", "")), |
| 1309 | xs_html_tag("p", NULL)); | 1363 | xs_html_tag("p", NULL)); |
| 1310 | } | 1364 | } |
| 1311 | 1365 | ||
| @@ -1858,6 +1912,9 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1858 | 1912 | ||
| 1859 | xs_list *p = children; | 1913 | xs_list *p = children; |
| 1860 | char *cmd5; | 1914 | char *cmd5; |
| 1915 | int cnt = 0; | ||
| 1916 | int o_cnt = 0; | ||
| 1917 | |||
| 1861 | while (xs_list_iter(&p, &cmd5)) { | 1918 | while (xs_list_iter(&p, &cmd5)) { |
| 1862 | xs *chd = NULL; | 1919 | xs *chd = NULL; |
| 1863 | 1920 | ||
| @@ -1866,23 +1923,40 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1866 | else | 1923 | else |
| 1867 | object_get_by_md5(cmd5, &chd); | 1924 | object_get_by_md5(cmd5, &chd); |
| 1868 | 1925 | ||
| 1869 | if (chd != NULL && xs_is_null(xs_dict_get(chd, "name"))) { | 1926 | if (chd != NULL) { |
| 1870 | xs_html *che = html_entry(user, chd, read_only, level + 1, cmd5, hide_children); | 1927 | if (xs_is_null(xs_dict_get(chd, "name"))) { |
| 1928 | xs_html *che = html_entry(user, chd, read_only, | ||
| 1929 | level + 1, cmd5, hide_children); | ||
| 1930 | |||
| 1931 | if (che != NULL) { | ||
| 1932 | if (left > 3) { | ||
| 1933 | xs_html_add(ch_older, | ||
| 1934 | che); | ||
| 1935 | |||
| 1936 | o_cnt++; | ||
| 1937 | } | ||
| 1938 | else | ||
| 1939 | xs_html_add(ch_container, | ||
| 1940 | che); | ||
| 1871 | 1941 | ||
| 1872 | if (che != NULL) { | 1942 | cnt++; |
| 1873 | if (left > 3) | 1943 | } |
| 1874 | xs_html_add(ch_older, | ||
| 1875 | che); | ||
| 1876 | else | ||
| 1877 | xs_html_add(ch_container, | ||
| 1878 | che); | ||
| 1879 | } | 1944 | } |
| 1945 | |||
| 1946 | left--; | ||
| 1880 | } | 1947 | } |
| 1881 | else | 1948 | else |
| 1882 | srv_debug(2, xs_fmt("cannot read child %s", cmd5)); | 1949 | srv_debug(2, xs_fmt("cannot read child %s", cmd5)); |
| 1883 | |||
| 1884 | left--; | ||
| 1885 | } | 1950 | } |
| 1951 | |||
| 1952 | /* if no children were finally added, hide the details */ | ||
| 1953 | if (cnt == 0) | ||
| 1954 | xs_html_add(ch_details, | ||
| 1955 | xs_html_attr("style", "display: none")); | ||
| 1956 | |||
| 1957 | if (o_cnt == 0 && ch_older) | ||
| 1958 | xs_html_add(ch_older, | ||
| 1959 | xs_html_attr("style", "display: none")); | ||
| 1886 | } | 1960 | } |
| 1887 | } | 1961 | } |
| 1888 | 1962 | ||
| @@ -1917,6 +1991,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 1917 | double t = ftime(); | 1991 | double t = ftime(); |
| 1918 | 1992 | ||
| 1919 | xs *desc = NULL; | 1993 | xs *desc = NULL; |
| 1994 | xs *alternate = NULL; | ||
| 1920 | 1995 | ||
| 1921 | if (xs_list_len(list) == 1) { | 1996 | if (xs_list_len(list) == 1) { |
| 1922 | /* only one element? pick the description from the source */ | 1997 | /* only one element? pick the description from the source */ |
| @@ -1925,13 +2000,15 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 1925 | object_get_by_md5(id, &d); | 2000 | object_get_by_md5(id, &d); |
| 1926 | if (d && (v = xs_dict_get(d, "sourceContent")) != NULL) | 2001 | if (d && (v = xs_dict_get(d, "sourceContent")) != NULL) |
| 1927 | desc = xs_dup(v); | 2002 | desc = xs_dup(v); |
| 2003 | |||
| 2004 | alternate = xs_dup(xs_dict_get(d, "id")); | ||
| 1928 | } | 2005 | } |
| 1929 | 2006 | ||
| 1930 | xs_html *head; | 2007 | xs_html *head; |
| 1931 | xs_html *body; | 2008 | xs_html *body; |
| 1932 | 2009 | ||
| 1933 | if (user) { | 2010 | if (user) { |
| 1934 | head = html_user_head(user, desc); | 2011 | head = html_user_head(user, desc, alternate); |
| 1935 | body = html_user_body(user, read_only); | 2012 | body = html_user_body(user, read_only); |
| 1936 | } | 2013 | } |
| 1937 | else { | 2014 | else { |
| @@ -1977,9 +2054,16 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 1977 | if (user != NULL && !is_msg_public(msg)) { | 2054 | if (user != NULL && !is_msg_public(msg)) { |
| 1978 | char *irt = xs_dict_get(msg, "inReplyTo"); | 2055 | char *irt = xs_dict_get(msg, "inReplyTo"); |
| 1979 | 2056 | ||
| 2057 | /* is it a reply to something not in the storage? */ | ||
| 1980 | if (!xs_is_null(irt) && !object_here(irt)) { | 2058 | if (!xs_is_null(irt) && !object_here(irt)) { |
| 1981 | snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v)); | 2059 | /* is it for me? */ |
| 1982 | continue; | 2060 | xs_list *to = xs_dict_get_def(msg, "to", xs_stock(XSTYPE_LIST)); |
| 2061 | xs_list *cc = xs_dict_get_def(msg, "cc", xs_stock(XSTYPE_LIST)); | ||
| 2062 | |||
| 2063 | if (xs_list_in(to, user->actor) == -1 && xs_list_in(cc, user->actor) == -1) { | ||
| 2064 | snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v)); | ||
| 2065 | continue; | ||
| 2066 | } | ||
| 1983 | } | 2067 | } |
| 1984 | } | 2068 | } |
| 1985 | 2069 | ||
| @@ -2077,9 +2161,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t) | |||
| 2077 | snac_posts = xs_html_tag("details", | 2161 | snac_posts = xs_html_tag("details", |
| 2078 | xs_html_attr("open", NULL), | 2162 | xs_html_attr("open", NULL), |
| 2079 | xs_html_tag("summary", | 2163 | xs_html_tag("summary", |
| 2080 | xs_html_text("...")), | 2164 | xs_html_text("...")))); |
| 2081 | xs_html_tag("div", | ||
| 2082 | xs_html_attr("class", "snac-posts")))); | ||
| 2083 | 2165 | ||
| 2084 | xs_list *p = list; | 2166 | xs_list *p = list; |
| 2085 | char *actor_id; | 2167 | char *actor_id; |
| @@ -2181,9 +2263,9 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t) | |||
| 2181 | dm_div_id, dm_form_id, | 2263 | dm_div_id, dm_form_id, |
| 2182 | "", "", | 2264 | "", "", |
| 2183 | NULL, actor_id, | 2265 | NULL, actor_id, |
| 2184 | xs_stock_false, "", | 2266 | xs_stock(XSTYPE_FALSE), "", |
| 2185 | xs_stock_false, NULL, | 2267 | xs_stock(XSTYPE_FALSE), NULL, |
| 2186 | NULL, 0), | 2268 | NULL, 0, "", ""), |
| 2187 | xs_html_tag("p", NULL)); | 2269 | xs_html_tag("p", NULL)); |
| 2188 | 2270 | ||
| 2189 | xs_html_add(snac_post, snac_controls); | 2271 | xs_html_add(snac_post, snac_controls); |
| @@ -2202,10 +2284,12 @@ xs_str *html_people(snac *user) | |||
| 2202 | xs *wers = follower_list(user); | 2284 | xs *wers = follower_list(user); |
| 2203 | 2285 | ||
| 2204 | xs_html *html = xs_html_tag("html", | 2286 | xs_html *html = xs_html_tag("html", |
| 2205 | html_user_head(user, NULL), | 2287 | html_user_head(user, NULL, NULL), |
| 2206 | xs_html_add(html_user_body(user, 0), | 2288 | xs_html_add(html_user_body(user, 0), |
| 2207 | html_people_list(user, wing, L("People you follow"), "i"), | 2289 | xs_html_tag("div", |
| 2208 | html_people_list(user, wers, L("People that follow you"), "e"), | 2290 | xs_html_attr("class", "snac-posts"), |
| 2291 | html_people_list(user, wing, L("People you follow"), "i"), | ||
| 2292 | html_people_list(user, wers, L("People that follow you"), "e")), | ||
| 2209 | html_footer())); | 2293 | html_footer())); |
| 2210 | 2294 | ||
| 2211 | return xs_html_render_s(html, "<!DOCTYPE html>\n"); | 2295 | return xs_html_render_s(html, "<!DOCTYPE html>\n"); |
| @@ -2220,7 +2304,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2220 | xs_html *body = html_user_body(user, 0); | 2304 | xs_html *body = html_user_body(user, 0); |
| 2221 | 2305 | ||
| 2222 | xs_html *html = xs_html_tag("html", | 2306 | xs_html *html = xs_html_tag("html", |
| 2223 | html_user_head(user, NULL), | 2307 | html_user_head(user, NULL, NULL), |
| 2224 | body); | 2308 | body); |
| 2225 | 2309 | ||
| 2226 | xs *clear_all_action = xs_fmt("%s/admin/clear-notifications", user->actor); | 2310 | xs *clear_all_action = xs_fmt("%s/admin/clear-notifications", user->actor); |
| @@ -2604,7 +2688,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2604 | return 403; | 2688 | return 403; |
| 2605 | 2689 | ||
| 2606 | xs *elems = timeline_simple_list(&snac, "public", 0, 20); | 2690 | xs *elems = timeline_simple_list(&snac, "public", 0, 20); |
| 2607 | xs *bio = not_really_markdown(xs_dict_get(snac.config, "bio"), NULL); | 2691 | xs *bio = not_really_markdown(xs_dict_get(snac.config, "bio"), NULL, NULL); |
| 2608 | 2692 | ||
| 2609 | xs *rss_title = xs_fmt("%s (@%s@%s)", | 2693 | xs *rss_title = xs_fmt("%s (@%s@%s)", |
| 2610 | xs_dict_get(snac.config, "name"), | 2694 | xs_dict_get(snac.config, "name"), |
| @@ -2802,7 +2886,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2802 | msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, priv); | 2886 | msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, priv); |
| 2803 | 2887 | ||
| 2804 | if (sensitive != NULL) { | 2888 | if (sensitive != NULL) { |
| 2805 | msg = xs_dict_set(msg, "sensitive", xs_stock_true); | 2889 | msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_TRUE)); |
| 2806 | msg = xs_dict_set(msg, "summary", xs_is_null(summary) ? "..." : summary); | 2890 | msg = xs_dict_set(msg, "summary", xs_is_null(summary) ? "..." : summary); |
| 2807 | } | 2891 | } |
| 2808 | 2892 | ||
| @@ -2881,6 +2965,22 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2881 | } | 2965 | } |
| 2882 | } | 2966 | } |
| 2883 | else | 2967 | else |
| 2968 | if (strcmp(action, L("Unlike")) == 0) { /** **/ | ||
| 2969 | xs *msg = msg_repulsion(&snac, id, "Like"); | ||
| 2970 | |||
| 2971 | if (msg != NULL) { | ||
| 2972 | enqueue_message(&snac, msg); | ||
| 2973 | } | ||
| 2974 | } | ||
| 2975 | else | ||
| 2976 | if (strcmp(action, L("Unboost")) == 0) { /** **/ | ||
| 2977 | xs *msg = msg_repulsion(&snac, id, "Announce"); | ||
| 2978 | |||
| 2979 | if (msg != NULL) { | ||
| 2980 | enqueue_message(&snac, msg); | ||
| 2981 | } | ||
| 2982 | } | ||
| 2983 | else | ||
| 2884 | if (strcmp(action, L("MUTE")) == 0) { /** **/ | 2984 | if (strcmp(action, L("MUTE")) == 0) { /** **/ |
| 2885 | mute(&snac, actor); | 2985 | mute(&snac, actor); |
| 2886 | } | 2986 | } |
| @@ -3036,17 +3136,17 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3036 | snac.config = xs_dict_set(snac.config, "purge_days", days); | 3136 | snac.config = xs_dict_set(snac.config, "purge_days", days); |
| 3037 | } | 3137 | } |
| 3038 | if ((v = xs_dict_get(p_vars, "drop_dm_from_unknown")) != NULL && strcmp(v, "on") == 0) | 3138 | if ((v = xs_dict_get(p_vars, "drop_dm_from_unknown")) != NULL && strcmp(v, "on") == 0) |
| 3039 | snac.config = xs_dict_set(snac.config, "drop_dm_from_unknown", xs_stock_true); | 3139 | snac.config = xs_dict_set(snac.config, "drop_dm_from_unknown", xs_stock(XSTYPE_TRUE)); |
| 3040 | else | 3140 | else |
| 3041 | snac.config = xs_dict_set(snac.config, "drop_dm_from_unknown", xs_stock_false); | 3141 | snac.config = xs_dict_set(snac.config, "drop_dm_from_unknown", xs_stock(XSTYPE_FALSE)); |
| 3042 | if ((v = xs_dict_get(p_vars, "bot")) != NULL && strcmp(v, "on") == 0) | 3142 | if ((v = xs_dict_get(p_vars, "bot")) != NULL && strcmp(v, "on") == 0) |
| 3043 | snac.config = xs_dict_set(snac.config, "bot", xs_stock_true); | 3143 | snac.config = xs_dict_set(snac.config, "bot", xs_stock(XSTYPE_TRUE)); |
| 3044 | else | 3144 | else |
| 3045 | snac.config = xs_dict_set(snac.config, "bot", xs_stock_false); | 3145 | snac.config = xs_dict_set(snac.config, "bot", xs_stock(XSTYPE_FALSE)); |
| 3046 | if ((v = xs_dict_get(p_vars, "private")) != NULL && strcmp(v, "on") == 0) | 3146 | if ((v = xs_dict_get(p_vars, "private")) != NULL && strcmp(v, "on") == 0) |
| 3047 | snac.config = xs_dict_set(snac.config, "private", xs_stock_true); | 3147 | snac.config = xs_dict_set(snac.config, "private", xs_stock(XSTYPE_TRUE)); |
| 3048 | else | 3148 | else |
| 3049 | snac.config = xs_dict_set(snac.config, "private", xs_stock_false); | 3149 | snac.config = xs_dict_set(snac.config, "private", xs_stock(XSTYPE_FALSE)); |
| 3050 | if ((v = xs_dict_get(p_vars, "metadata")) != NULL) { | 3150 | if ((v = xs_dict_get(p_vars, "metadata")) != NULL) { |
| 3051 | /* split the metadata and store it as a dict */ | 3151 | /* split the metadata and store it as a dict */ |
| 3052 | xs_dict *md = xs_dict_new(); | 3152 | xs_dict *md = xs_dict_new(); |
| @@ -388,6 +388,7 @@ void httpd_connection(FILE *f) | |||
| 388 | body, xs_dict_get(srv_config, "host")); | 388 | body, xs_dict_get(srv_config, "host")); |
| 389 | 389 | ||
| 390 | headers = xs_dict_append(headers, "WWW-Authenticate", www_auth); | 390 | headers = xs_dict_append(headers, "WWW-Authenticate", www_auth); |
| 391 | headers = xs_dict_append(headers, "Cache-Control", "no-cache, must-revalidate, max-age=0"); | ||
| 391 | } | 392 | } |
| 392 | 393 | ||
| 393 | if (ctype == NULL) | 394 | if (ctype == NULL) |
| @@ -814,7 +815,7 @@ void httpd(void) | |||
| 814 | 815 | ||
| 815 | /* send as many exit jobs as working threads */ | 816 | /* send as many exit jobs as working threads */ |
| 816 | for (n = 1; n < p_state->n_threads; n++) | 817 | for (n = 1; n < p_state->n_threads; n++) |
| 817 | job_post(xs_stock_false, 0); | 818 | job_post(xs_stock(XSTYPE_FALSE), 0); |
| 818 | 819 | ||
| 819 | /* wait for all the threads to exit */ | 820 | /* wait for all the threads to exit */ |
| 820 | for (n = 0; n < p_state->n_threads; n++) | 821 | for (n = 0; n < p_state->n_threads; n++) |
| @@ -5,6 +5,7 @@ | |||
| 5 | #include "xs_io.h" | 5 | #include "xs_io.h" |
| 6 | #include "xs_json.h" | 6 | #include "xs_json.h" |
| 7 | #include "xs_time.h" | 7 | #include "xs_time.h" |
| 8 | #include "xs_openssl.h" | ||
| 8 | 9 | ||
| 9 | #include "snac.h" | 10 | #include "snac.h" |
| 10 | 11 | ||
| @@ -17,30 +18,32 @@ int usage(void) | |||
| 17 | printf("\n"); | 18 | printf("\n"); |
| 18 | printf("Commands:\n"); | 19 | printf("Commands:\n"); |
| 19 | printf("\n"); | 20 | printf("\n"); |
| 20 | printf("init [{basedir}] Initializes the data storage\n"); | 21 | printf("init [{basedir}] Initializes the data storage\n"); |
| 21 | printf("upgrade {basedir} Upgrade to a new version\n"); | 22 | printf("upgrade {basedir} Upgrade to a new version\n"); |
| 22 | printf("adduser {basedir} [{uid}] Adds a new user\n"); | 23 | printf("adduser {basedir} [{uid}] Adds a new user\n"); |
| 23 | printf("deluser {basedir} {uid} Deletes a user\n"); | 24 | printf("deluser {basedir} {uid} Deletes a user\n"); |
| 24 | printf("httpd {basedir} Starts the HTTPD daemon\n"); | 25 | printf("httpd {basedir} Starts the HTTPD daemon\n"); |
| 25 | printf("purge {basedir} Purges old data\n"); | 26 | printf("purge {basedir} Purges old data\n"); |
| 26 | printf("state {basedir} Prints server state\n"); | 27 | printf("state {basedir} Prints server state\n"); |
| 27 | printf("webfinger {basedir} {actor} Queries about an actor (@user@host or actor url)\n"); | 28 | printf("webfinger {basedir} {actor} Queries about an actor (@user@host or actor url)\n"); |
| 28 | printf("queue {basedir} {uid} Processes a user queue\n"); | 29 | printf("queue {basedir} {uid} Processes a user queue\n"); |
| 29 | printf("follow {basedir} {uid} {actor} Follows an actor\n"); | 30 | printf("follow {basedir} {uid} {actor} Follows an actor\n"); |
| 30 | printf("unfollow {basedir} {uid} {actor} Unfollows an actor\n"); | 31 | printf("unfollow {basedir} {uid} {actor} Unfollows an actor\n"); |
| 31 | printf("request {basedir} {uid} {url} Requests an object\n"); | 32 | printf("request {basedir} {uid} {url} Requests an object\n"); |
| 32 | printf("actor {basedir} [{uid}] {url} Requests an actor\n"); | 33 | printf("actor {basedir} [{uid}] {url} Requests an actor\n"); |
| 33 | printf("note {basedir} {uid} {'text'} Sends a note to followers\n"); | 34 | printf("note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n"); |
| 34 | printf("resetpwd {basedir} {uid} Resets the password of a user\n"); | 35 | printf("boost|announce {basedir} {uid} {url} Boosts (announces) a post\n"); |
| 35 | printf("ping {basedir} {uid} {actor} Pings an actor\n"); | 36 | printf("unboost {basedir} {uid} {url} Unboosts a post\n"); |
| 36 | printf("webfinger_s {basedir} {uid} {actor} Queries about an actor (@user@host or actor url)\n"); | 37 | printf("resetpwd {basedir} {uid} Resets the password of a user\n"); |
| 37 | printf("pin {basedir} {uid} {msg_url} Pins a message\n"); | 38 | printf("ping {basedir} {uid} {actor} Pings an actor\n"); |
| 38 | printf("unpin {basedir} {uid} {msg_url} Unpins a message\n"); | 39 | printf("webfinger_s {basedir} {uid} {actor} Queries about an actor (@user@host or actor url)\n"); |
| 39 | printf("block {basedir} {instance_url} Blocks a full instance\n"); | 40 | printf("pin {basedir} {uid} {msg_url} Pins a message\n"); |
| 40 | printf("unblock {basedir} {instance_url} Unblocks a full instance\n"); | 41 | printf("unpin {basedir} {uid} {msg_url} Unpins a message\n"); |
| 41 | printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\n"); | 42 | printf("block {basedir} {instance_url} Blocks a full instance\n"); |
| 42 | printf("unlimit {basedir} {uid} {actor} Unlimits an actor\n"); | 43 | printf("unblock {basedir} {instance_url} Unblocks a full instance\n"); |
| 43 | printf("verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n"); | 44 | printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\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"); | ||
| 44 | 47 | ||
| 45 | return 1; | 48 | return 1; |
| 46 | } | 49 | } |
| @@ -96,7 +99,7 @@ int main(int argc, char *argv[]) | |||
| 96 | if (strcmp(cmd, "markdown") == 0) { /** **/ | 99 | if (strcmp(cmd, "markdown") == 0) { /** **/ |
| 97 | /* undocumented, for testing only */ | 100 | /* undocumented, for testing only */ |
| 98 | xs *c = xs_readall(stdin); | 101 | xs *c = xs_readall(stdin); |
| 99 | xs *fc = not_really_markdown(c, NULL); | 102 | xs *fc = not_really_markdown(c, NULL, NULL); |
| 100 | 103 | ||
| 101 | printf("<html>\n%s\n</html>\n", fc); | 104 | printf("<html>\n%s\n</html>\n", fc); |
| 102 | return 0; | 105 | return 0; |
| @@ -279,7 +282,7 @@ int main(int argc, char *argv[]) | |||
| 279 | return 0; | 282 | return 0; |
| 280 | } | 283 | } |
| 281 | 284 | ||
| 282 | if (strcmp(cmd, "announce") == 0) { /** **/ | 285 | if (strcmp(cmd, "boost") == 0 || strcmp(cmd, "announce") == 0) { /** **/ |
| 283 | xs *msg = msg_admiration(&snac, url, "Announce"); | 286 | xs *msg = msg_admiration(&snac, url, "Announce"); |
| 284 | 287 | ||
| 285 | if (msg != NULL) { | 288 | if (msg != NULL) { |
| @@ -293,6 +296,20 @@ int main(int argc, char *argv[]) | |||
| 293 | return 0; | 296 | return 0; |
| 294 | } | 297 | } |
| 295 | 298 | ||
| 299 | if (strcmp(cmd, "unboost") == 0) { /** **/ | ||
| 300 | xs *msg = msg_repulsion(&snac, url, "Announce"); | ||
| 301 | |||
| 302 | if (msg != NULL) { | ||
| 303 | enqueue_message(&snac, msg); | ||
| 304 | |||
| 305 | if (dbglevel) { | ||
| 306 | xs_json_dump(msg, 4, stdout); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | return 0; | ||
| 311 | } | ||
| 312 | |||
| 296 | if (strcmp(cmd, "follow") == 0) { /** **/ | 313 | if (strcmp(cmd, "follow") == 0) { /** **/ |
| 297 | xs *msg = msg_follow(&snac, url); | 314 | xs *msg = msg_follow(&snac, url); |
| 298 | 315 | ||
| @@ -360,6 +377,14 @@ int main(int argc, char *argv[]) | |||
| 360 | if (strcmp(cmd, "ping") == 0) { /** **/ | 377 | if (strcmp(cmd, "ping") == 0) { /** **/ |
| 361 | xs *actor_o = NULL; | 378 | xs *actor_o = NULL; |
| 362 | 379 | ||
| 380 | if (!xs_startswith(url, "https:/")) { | ||
| 381 | /* try to resolve via webfinger */ | ||
| 382 | if (!valid_status(webfinger_request(url, &url, NULL))) { | ||
| 383 | srv_log(xs_fmt("cannot resolve %s via webfinger", url)); | ||
| 384 | return 1; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | |||
| 363 | if (valid_status(actor_request(&snac, url, &actor_o))) { | 388 | if (valid_status(actor_request(&snac, url, &actor_o))) { |
| 364 | xs *msg = msg_ping(&snac, url); | 389 | xs *msg = msg_ping(&snac, url); |
| 365 | 390 | ||
| @@ -368,6 +393,8 @@ int main(int argc, char *argv[]) | |||
| 368 | if (dbglevel) { | 393 | if (dbglevel) { |
| 369 | xs_json_dump(msg, 4, stdout); | 394 | xs_json_dump(msg, 4, stdout); |
| 370 | } | 395 | } |
| 396 | |||
| 397 | srv_log(xs_fmt("Ping sent to %s -- see log for Pong reply", url)); | ||
| 371 | } | 398 | } |
| 372 | else { | 399 | else { |
| 373 | srv_log(xs_fmt("Error getting actor %s", url)); | 400 | srv_log(xs_fmt("Error getting actor %s", url)); |
| @@ -450,7 +477,39 @@ int main(int argc, char *argv[]) | |||
| 450 | xs *content = NULL; | 477 | xs *content = NULL; |
| 451 | xs *msg = NULL; | 478 | xs *msg = NULL; |
| 452 | xs *c_msg = NULL; | 479 | xs *c_msg = NULL; |
| 453 | char *in_reply_to = GET_ARGV(); | 480 | xs *attl = xs_list_new(); |
| 481 | char *fn = NULL; | ||
| 482 | |||
| 483 | /* iterate possible attachments */ | ||
| 484 | while ((fn = GET_ARGV())) { | ||
| 485 | FILE *f; | ||
| 486 | |||
| 487 | if ((f = fopen(fn, "rb")) != NULL) { | ||
| 488 | /* get the file size and content */ | ||
| 489 | fseek(f, 0, SEEK_END); | ||
| 490 | int sz = ftell(f); | ||
| 491 | fseek(f, 0, SEEK_SET); | ||
| 492 | xs *atc = xs_readall(f); | ||
| 493 | fclose(f); | ||
| 494 | |||
| 495 | char *ext = strrchr(fn, '.'); | ||
| 496 | xs *hash = xs_md5_hex(fn, strlen(fn)); | ||
| 497 | xs *id = xs_fmt("%s%s", hash, ext); | ||
| 498 | xs *url = xs_fmt("%s/s/%s", snac.actor, id); | ||
| 499 | |||
| 500 | /* store */ | ||
| 501 | static_put(&snac, id, atc, sz); | ||
| 502 | |||
| 503 | xs *l = xs_list_new(); | ||
| 504 | |||
| 505 | l = xs_list_append(l, url); | ||
| 506 | l = xs_list_append(l, ""); /* alt text */ | ||
| 507 | |||
| 508 | attl = xs_list_append(attl, l); | ||
| 509 | } | ||
| 510 | else | ||
| 511 | fprintf(stderr, "Error opening '%s' as attachment\n", fn); | ||
| 512 | } | ||
| 454 | 513 | ||
| 455 | if (strcmp(url, "-e") == 0) { | 514 | if (strcmp(url, "-e") == 0) { |
| 456 | /* get the content from an editor */ | 515 | /* get the content from an editor */ |
| @@ -478,7 +537,7 @@ int main(int argc, char *argv[]) | |||
| 478 | else | 537 | else |
| 479 | content = xs_dup(url); | 538 | content = xs_dup(url); |
| 480 | 539 | ||
| 481 | msg = msg_note(&snac, content, NULL, in_reply_to, NULL, 0); | 540 | msg = msg_note(&snac, content, NULL, NULL, attl, 0); |
| 482 | 541 | ||
| 483 | c_msg = msg_create(&snac, msg); | 542 | c_msg = msg_create(&snac, msg); |
| 484 | 543 | ||
| @@ -289,7 +289,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 289 | *body = xs_dup(code); | 289 | *body = xs_dup(code); |
| 290 | } | 290 | } |
| 291 | else { | 291 | else { |
| 292 | *body = xs_fmt("%s?code=%s", redir, code); | 292 | if (xs_str_in(redir, "?")) |
| 293 | *body = xs_fmt("%s&code=%s", redir, code); | ||
| 294 | else | ||
| 295 | *body = xs_fmt("%s?code=%s", redir, code); | ||
| 296 | |||
| 293 | status = 303; | 297 | status = 303; |
| 294 | } | 298 | } |
| 295 | 299 | ||
| @@ -335,8 +339,8 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 335 | /* FIXME: this 'scope' parameter is mandatory for the official Mastodon API, | 339 | /* FIXME: this 'scope' parameter is mandatory for the official Mastodon API, |
| 336 | but if it's enabled, it makes it crash after some more steps, which | 340 | but if it's enabled, it makes it crash after some more steps, which |
| 337 | is FAR WORSE */ | 341 | is FAR WORSE */ |
| 338 | // const char *scope = xs_dict_get(args, "scope"); | ||
| 339 | const char *scope = NULL; | 342 | const char *scope = NULL; |
| 343 | // scope = xs_dict_get(args, "scope"); | ||
| 340 | 344 | ||
| 341 | /* no client_secret? check if it's inside an authorization header | 345 | /* no client_secret? check if it's inside an authorization header |
| 342 | (AndStatus does it this way) */ | 346 | (AndStatus does it this way) */ |
| @@ -359,9 +363,9 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 359 | } | 363 | } |
| 360 | } | 364 | } |
| 361 | 365 | ||
| 362 | /* no code? | 366 | /* no code? |
| 363 | I'm not sure of the impacts of this right now, but Subway Tooter does not | 367 | I'm not sure of the impacts of this right now, but Subway Tooter does not |
| 364 | provide a code so one must be generated */ | 368 | provide a code so one must be generated */ |
| 365 | if (xs_is_null(code)){ | 369 | if (xs_is_null(code)){ |
| 366 | code = random_str(); | 370 | code = random_str(); |
| 367 | } | 371 | } |
| @@ -522,6 +526,12 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 522 | acct = xs_dict_append(acct, "id", acct_md5); | 526 | acct = xs_dict_append(acct, "id", acct_md5); |
| 523 | acct = xs_dict_append(acct, "username", prefu); | 527 | acct = xs_dict_append(acct, "username", prefu); |
| 524 | acct = xs_dict_append(acct, "display_name", display_name); | 528 | acct = xs_dict_append(acct, "display_name", display_name); |
| 529 | acct = xs_dict_append(acct, "discoverable", xs_stock(XSTYPE_TRUE)); | ||
| 530 | acct = xs_dict_append(acct, "group", xs_stock(XSTYPE_FALSE)); | ||
| 531 | acct = xs_dict_append(acct, "hide_collections", xs_stock(XSTYPE_FALSE)); | ||
| 532 | acct = xs_dict_append(acct, "indexable", xs_stock(XSTYPE_TRUE)); | ||
| 533 | acct = xs_dict_append(acct, "noindex", xs_stock(XSTYPE_FALSE)); | ||
| 534 | acct = xs_dict_append(acct, "roles", xs_stock(XSTYPE_LIST)); | ||
| 525 | 535 | ||
| 526 | { | 536 | { |
| 527 | /* create the acct field as user@host */ | 537 | /* create the acct field as user@host */ |
| @@ -543,13 +553,14 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 543 | note = ""; | 553 | note = ""; |
| 544 | 554 | ||
| 545 | if (strcmp(xs_dict_get(actor, "type"), "Service") == 0) | 555 | if (strcmp(xs_dict_get(actor, "type"), "Service") == 0) |
| 546 | acct = xs_dict_append(acct, "bot", xs_stock_true); | 556 | acct = xs_dict_append(acct, "bot", xs_stock(XSTYPE_TRUE)); |
| 547 | else | 557 | else |
| 548 | acct = xs_dict_append(acct, "bot", xs_stock_false); | 558 | acct = xs_dict_append(acct, "bot", xs_stock(XSTYPE_FALSE)); |
| 549 | 559 | ||
| 550 | acct = xs_dict_append(acct, "note", note); | 560 | acct = xs_dict_append(acct, "note", note); |
| 551 | 561 | ||
| 552 | acct = xs_dict_append(acct, "url", id); | 562 | acct = xs_dict_append(acct, "url", id); |
| 563 | acct = xs_dict_append(acct, "uri", id); | ||
| 553 | 564 | ||
| 554 | xs *avatar = NULL; | 565 | xs *avatar = NULL; |
| 555 | xs_dict *av = xs_dict_get(actor, "icon"); | 566 | xs_dict *av = xs_dict_get(actor, "icon"); |
| @@ -574,7 +585,7 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 574 | header = xs_dup(xs_dict_get(hd, "url")); | 585 | header = xs_dup(xs_dict_get(hd, "url")); |
| 575 | 586 | ||
| 576 | if (xs_is_null(header)) | 587 | if (xs_is_null(header)) |
| 577 | header = xs_dup(""); | 588 | header = xs_fmt("%s/header.png", srv_baseurl); |
| 578 | 589 | ||
| 579 | acct = xs_dict_append(acct, "header", header); | 590 | acct = xs_dict_append(acct, "header", header); |
| 580 | acct = xs_dict_append(acct, "header_static", header); | 591 | acct = xs_dict_append(acct, "header_static", header); |
| @@ -602,7 +613,7 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 602 | d1 = xs_dict_append(d1, "shortcode", nm); | 613 | d1 = xs_dict_append(d1, "shortcode", nm); |
| 603 | d1 = xs_dict_append(d1, "url", url); | 614 | d1 = xs_dict_append(d1, "url", url); |
| 604 | d1 = xs_dict_append(d1, "static_url", url); | 615 | d1 = xs_dict_append(d1, "static_url", url); |
| 605 | d1 = xs_dict_append(d1, "visible_in_picker", xs_stock_true); | 616 | d1 = xs_dict_append(d1, "visible_in_picker", xs_stock(XSTYPE_TRUE)); |
| 606 | 617 | ||
| 607 | eml = xs_list_append(eml, d1); | 618 | eml = xs_list_append(eml, d1); |
| 608 | } | 619 | } |
| @@ -613,10 +624,10 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 613 | acct = xs_dict_append(acct, "emojis", eml); | 624 | acct = xs_dict_append(acct, "emojis", eml); |
| 614 | } | 625 | } |
| 615 | 626 | ||
| 616 | acct = xs_dict_append(acct, "locked", xs_stock_false); | 627 | acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); |
| 617 | acct = xs_dict_append(acct, "followers_count", xs_stock_0); | 628 | acct = xs_dict_append(acct, "followers_count", xs_stock(0)); |
| 618 | acct = xs_dict_append(acct, "following_count", xs_stock_0); | 629 | acct = xs_dict_append(acct, "following_count", xs_stock(0)); |
| 619 | acct = xs_dict_append(acct, "statuses_count", xs_stock_0); | 630 | acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); |
| 620 | 631 | ||
| 621 | xs *fields = xs_list_new(); | 632 | xs *fields = xs_list_new(); |
| 622 | p = xs_dict_get(actor, "attachment"); | 633 | p = xs_dict_get(actor, "attachment"); |
| @@ -624,19 +635,19 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 624 | 635 | ||
| 625 | /* dict of validated links */ | 636 | /* dict of validated links */ |
| 626 | xs_dict *val_links = NULL; | 637 | xs_dict *val_links = NULL; |
| 627 | xs_dict *metadata = xs_stock_dict; | 638 | xs_dict *metadata = xs_stock(XSTYPE_DICT); |
| 628 | snac user = {0}; | 639 | snac user = {0}; |
| 629 | 640 | ||
| 630 | if (xs_startswith(id, srv_baseurl)) { | 641 | if (xs_startswith(id, srv_baseurl)) { |
| 631 | /* if it's a local user, open it and pick its validated links */ | 642 | /* if it's a local user, open it and pick its validated links */ |
| 632 | if (user_open(&user, prefu)) { | 643 | if (user_open(&user, prefu)) { |
| 633 | val_links = user.links; | 644 | val_links = user.links; |
| 634 | metadata = xs_dict_get_def(user.config, "metadata", xs_stock_dict); | 645 | metadata = xs_dict_get_def(user.config, "metadata", xs_stock(XSTYPE_DICT)); |
| 635 | } | 646 | } |
| 636 | } | 647 | } |
| 637 | 648 | ||
| 638 | if (xs_is_null(val_links)) | 649 | if (xs_is_null(val_links)) |
| 639 | val_links = xs_stock_dict; | 650 | val_links = xs_stock(XSTYPE_DICT); |
| 640 | 651 | ||
| 641 | while (xs_list_iter(&p, &v)) { | 652 | while (xs_list_iter(&p, &v)) { |
| 642 | char *type = xs_dict_get(v, "type"); | 653 | char *type = xs_dict_get(v, "type"); |
| @@ -665,7 +676,7 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 665 | d = xs_dict_append(d, "value", value); | 676 | d = xs_dict_append(d, "value", value); |
| 666 | d = xs_dict_append(d, "verified_at", | 677 | d = xs_dict_append(d, "verified_at", |
| 667 | xs_type(val_date) == XSTYPE_STRING && *val_date ? | 678 | xs_type(val_date) == XSTYPE_STRING && *val_date ? |
| 668 | val_date : xs_stock_null); | 679 | val_date : xs_stock(XSTYPE_NULL)); |
| 669 | 680 | ||
| 670 | fields = xs_list_append(fields, d); | 681 | fields = xs_list_append(fields, d); |
| 671 | } | 682 | } |
| @@ -703,13 +714,13 @@ xs_dict *mastoapi_poll(snac *snac, const xs_dict *msg) | |||
| 703 | xs *fd = mastoapi_date(xs_dict_get(msg, "endTime")); | 714 | xs *fd = mastoapi_date(xs_dict_get(msg, "endTime")); |
| 704 | poll = xs_dict_append(poll, "expires_at", fd); | 715 | poll = xs_dict_append(poll, "expires_at", fd); |
| 705 | poll = xs_dict_append(poll, "expired", | 716 | poll = xs_dict_append(poll, "expired", |
| 706 | xs_dict_get(msg, "closed") != NULL ? xs_stock_true : xs_stock_false); | 717 | xs_dict_get(msg, "closed") != NULL ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 707 | 718 | ||
| 708 | if ((opts = xs_dict_get(msg, "oneOf")) != NULL) | 719 | if ((opts = xs_dict_get(msg, "oneOf")) != NULL) |
| 709 | poll = xs_dict_append(poll, "multiple", xs_stock_false); | 720 | poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_FALSE)); |
| 710 | else { | 721 | else { |
| 711 | opts = xs_dict_get(msg, "anyOf"); | 722 | opts = xs_dict_get(msg, "anyOf"); |
| 712 | poll = xs_dict_append(poll, "multiple", xs_stock_true); | 723 | poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_TRUE)); |
| 713 | } | 724 | } |
| 714 | 725 | ||
| 715 | while (xs_list_iter(&opts, &v)) { | 726 | while (xs_list_iter(&opts, &v)) { |
| @@ -736,7 +747,7 @@ xs_dict *mastoapi_poll(snac *snac, const xs_dict *msg) | |||
| 736 | 747 | ||
| 737 | poll = xs_dict_append(poll, "voted", | 748 | poll = xs_dict_append(poll, "voted", |
| 738 | (snac && was_question_voted(snac, xs_dict_get(msg, "id"))) ? | 749 | (snac && was_question_voted(snac, xs_dict_get(msg, "id"))) ? |
| 739 | xs_stock_true : xs_stock_false); | 750 | xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 740 | 751 | ||
| 741 | return poll; | 752 | return poll; |
| 742 | } | 753 | } |
| @@ -746,7 +757,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 746 | /* converts an ActivityPub note to a Mastodon status */ | 757 | /* converts an ActivityPub note to a Mastodon status */ |
| 747 | { | 758 | { |
| 748 | xs *actor = NULL; | 759 | xs *actor = NULL; |
| 749 | actor_get(get_atto(msg), &actor); | 760 | actor_get_refresh(snac, get_atto(msg), &actor); |
| 750 | 761 | ||
| 751 | /* if the author is not here, discard */ | 762 | /* if the author is not here, discard */ |
| 752 | if (actor == NULL) | 763 | if (actor == NULL) |
| @@ -802,7 +813,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 802 | 813 | ||
| 803 | tmp = xs_dict_get(msg, "sensitive"); | 814 | tmp = xs_dict_get(msg, "sensitive"); |
| 804 | if (xs_is_null(tmp)) | 815 | if (xs_is_null(tmp)) |
| 805 | tmp = xs_stock_false; | 816 | tmp = xs_stock(XSTYPE_FALSE); |
| 806 | 817 | ||
| 807 | st = xs_dict_append(st, "sensitive", tmp); | 818 | st = xs_dict_append(st, "sensitive", tmp); |
| 808 | 819 | ||
| @@ -921,7 +932,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 921 | d1 = xs_dict_append(d1, "shortcode", nm); | 932 | d1 = xs_dict_append(d1, "shortcode", nm); |
| 922 | d1 = xs_dict_append(d1, "url", url); | 933 | d1 = xs_dict_append(d1, "url", url); |
| 923 | d1 = xs_dict_append(d1, "static_url", url); | 934 | d1 = xs_dict_append(d1, "static_url", url); |
| 924 | d1 = xs_dict_append(d1, "visible_in_picker", xs_stock_true); | 935 | d1 = xs_dict_append(d1, "visible_in_picker", xs_stock(XSTYPE_TRUE)); |
| 925 | d1 = xs_dict_append(d1, "category", "Emojis"); | 936 | d1 = xs_dict_append(d1, "category", "Emojis"); |
| 926 | 937 | ||
| 927 | eml = xs_list_append(eml, d1); | 938 | eml = xs_list_append(eml, d1); |
| @@ -942,7 +953,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 942 | 953 | ||
| 943 | st = xs_dict_append(st, "favourites_count", ixc); | 954 | st = xs_dict_append(st, "favourites_count", ixc); |
| 944 | st = xs_dict_append(st, "favourited", | 955 | st = xs_dict_append(st, "favourited", |
| 945 | (snac && xs_list_in(idx, snac->md5) != -1) ? xs_stock_true : xs_stock_false); | 956 | (snac && xs_list_in(idx, snac->md5) != -1) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 946 | 957 | ||
| 947 | xs_free(idx); | 958 | xs_free(idx); |
| 948 | xs_free(ixc); | 959 | xs_free(ixc); |
| @@ -951,7 +962,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 951 | 962 | ||
| 952 | st = xs_dict_append(st, "reblogs_count", ixc); | 963 | st = xs_dict_append(st, "reblogs_count", ixc); |
| 953 | st = xs_dict_append(st, "reblogged", | 964 | st = xs_dict_append(st, "reblogged", |
| 954 | (snac && xs_list_in(idx, snac->md5) != -1) ? xs_stock_true : xs_stock_false); | 965 | (snac && xs_list_in(idx, snac->md5) != -1) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 955 | 966 | ||
| 956 | /* get the last person who boosted this */ | 967 | /* get the last person who boosted this */ |
| 957 | xs *boosted_by_md5 = NULL; | 968 | xs *boosted_by_md5 = NULL; |
| @@ -966,8 +977,8 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 966 | st = xs_dict_append(st, "replies_count", ixc); | 977 | st = xs_dict_append(st, "replies_count", ixc); |
| 967 | 978 | ||
| 968 | /* default in_reply_to values */ | 979 | /* default in_reply_to values */ |
| 969 | st = xs_dict_append(st, "in_reply_to_id", xs_stock_null); | 980 | st = xs_dict_append(st, "in_reply_to_id", xs_stock(XSTYPE_NULL)); |
| 970 | st = xs_dict_append(st, "in_reply_to_account_id", xs_stock_null); | 981 | st = xs_dict_append(st, "in_reply_to_account_id", xs_stock(XSTYPE_NULL)); |
| 971 | 982 | ||
| 972 | tmp = xs_dict_get(msg, "inReplyTo"); | 983 | tmp = xs_dict_get(msg, "inReplyTo"); |
| 973 | if (!xs_is_null(tmp)) { | 984 | if (!xs_is_null(tmp)) { |
| @@ -985,9 +996,9 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 985 | } | 996 | } |
| 986 | } | 997 | } |
| 987 | 998 | ||
| 988 | st = xs_dict_append(st, "reblog", xs_stock_null); | 999 | st = xs_dict_append(st, "reblog", xs_stock(XSTYPE_NULL)); |
| 989 | st = xs_dict_append(st, "card", xs_stock_null); | 1000 | st = xs_dict_append(st, "card", xs_stock(XSTYPE_NULL)); |
| 990 | st = xs_dict_append(st, "language", xs_stock_null); | 1001 | st = xs_dict_append(st, "language", xs_stock(XSTYPE_NULL)); |
| 991 | 1002 | ||
| 992 | tmp = xs_dict_get(msg, "sourceContent"); | 1003 | tmp = xs_dict_get(msg, "sourceContent"); |
| 993 | if (xs_is_null(tmp)) | 1004 | if (xs_is_null(tmp)) |
| @@ -998,7 +1009,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 998 | tmp = xs_dict_get(msg, "updated"); | 1009 | tmp = xs_dict_get(msg, "updated"); |
| 999 | xs *fd2 = NULL; | 1010 | xs *fd2 = NULL; |
| 1000 | if (xs_is_null(tmp)) | 1011 | if (xs_is_null(tmp)) |
| 1001 | tmp = xs_stock_null; | 1012 | tmp = xs_stock(XSTYPE_NULL); |
| 1002 | else { | 1013 | else { |
| 1003 | fd2 = mastoapi_date(tmp); | 1014 | fd2 = mastoapi_date(tmp); |
| 1004 | tmp = fd2; | 1015 | tmp = fd2; |
| @@ -1011,12 +1022,12 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 1011 | st = xs_dict_append(st, "poll", poll); | 1022 | st = xs_dict_append(st, "poll", poll); |
| 1012 | } | 1023 | } |
| 1013 | else | 1024 | else |
| 1014 | st = xs_dict_append(st, "poll", xs_stock_null); | 1025 | st = xs_dict_append(st, "poll", xs_stock(XSTYPE_NULL)); |
| 1015 | 1026 | ||
| 1016 | st = xs_dict_append(st, "bookmarked", xs_stock_false); | 1027 | st = xs_dict_append(st, "bookmarked", xs_stock(XSTYPE_FALSE)); |
| 1017 | 1028 | ||
| 1018 | st = xs_dict_append(st, "pinned", | 1029 | st = xs_dict_append(st, "pinned", |
| 1019 | (snac && is_pinned(snac, id)) ? xs_stock_true : xs_stock_false); | 1030 | (snac && is_pinned(snac, id)) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 1020 | 1031 | ||
| 1021 | /* is it a boost? */ | 1032 | /* is it a boost? */ |
| 1022 | if (!xs_is_null(boosted_by_md5)) { | 1033 | if (!xs_is_null(boosted_by_md5)) { |
| @@ -1060,21 +1071,21 @@ xs_dict *mastoapi_relationship(snac *snac, const char *md5) | |||
| 1060 | 1071 | ||
| 1061 | rel = xs_dict_append(rel, "id", md5); | 1072 | rel = xs_dict_append(rel, "id", md5); |
| 1062 | rel = xs_dict_append(rel, "following", | 1073 | rel = xs_dict_append(rel, "following", |
| 1063 | following_check(snac, actor) ? xs_stock_true : xs_stock_false); | 1074 | following_check(snac, actor) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 1064 | 1075 | ||
| 1065 | rel = xs_dict_append(rel, "showing_reblogs", xs_stock_true); | 1076 | rel = xs_dict_append(rel, "showing_reblogs", xs_stock(XSTYPE_TRUE)); |
| 1066 | rel = xs_dict_append(rel, "notifying", xs_stock_false); | 1077 | rel = xs_dict_append(rel, "notifying", xs_stock(XSTYPE_FALSE)); |
| 1067 | rel = xs_dict_append(rel, "followed_by", | 1078 | rel = xs_dict_append(rel, "followed_by", |
| 1068 | follower_check(snac, actor) ? xs_stock_true : xs_stock_false); | 1079 | follower_check(snac, actor) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 1069 | 1080 | ||
| 1070 | rel = xs_dict_append(rel, "blocking", | 1081 | rel = xs_dict_append(rel, "blocking", |
| 1071 | is_muted(snac, actor) ? xs_stock_true : xs_stock_false); | 1082 | is_muted(snac, actor) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); |
| 1072 | 1083 | ||
| 1073 | rel = xs_dict_append(rel, "muting", xs_stock_false); | 1084 | rel = xs_dict_append(rel, "muting", xs_stock(XSTYPE_FALSE)); |
| 1074 | rel = xs_dict_append(rel, "muting_notifications", xs_stock_false); | 1085 | rel = xs_dict_append(rel, "muting_notifications", xs_stock(XSTYPE_FALSE)); |
| 1075 | rel = xs_dict_append(rel, "requested", xs_stock_false); | 1086 | rel = xs_dict_append(rel, "requested", xs_stock(XSTYPE_FALSE)); |
| 1076 | rel = xs_dict_append(rel, "domain_blocking", xs_stock_false); | 1087 | rel = xs_dict_append(rel, "domain_blocking", xs_stock(XSTYPE_FALSE)); |
| 1077 | rel = xs_dict_append(rel, "endorsed", xs_stock_false); | 1088 | rel = xs_dict_append(rel, "endorsed", xs_stock(XSTYPE_FALSE)); |
| 1078 | rel = xs_dict_append(rel, "note", ""); | 1089 | rel = xs_dict_append(rel, "note", ""); |
| 1079 | } | 1090 | } |
| 1080 | 1091 | ||
| @@ -1142,9 +1153,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1142 | acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac1.config, "published")); | 1153 | acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac1.config, "published")); |
| 1143 | acct = xs_dict_append(acct, "note", xs_dict_get(snac1.config, "bio")); | 1154 | acct = xs_dict_append(acct, "note", xs_dict_get(snac1.config, "bio")); |
| 1144 | acct = xs_dict_append(acct, "url", snac1.actor); | 1155 | acct = xs_dict_append(acct, "url", snac1.actor); |
| 1145 | acct = xs_dict_append(acct, "header", ""); | 1156 | acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); |
| 1146 | acct = xs_dict_append(acct, "header_static", ""); | ||
| 1147 | acct = xs_dict_append(acct, "locked", xs_stock_false); | ||
| 1148 | acct = xs_dict_append(acct, "bot", xs_dict_get(snac1.config, "bot")); | 1157 | acct = xs_dict_append(acct, "bot", xs_dict_get(snac1.config, "bot")); |
| 1149 | 1158 | ||
| 1150 | xs *src = xs_json_loads("{\"privacy\":\"public\"," | 1159 | xs *src = xs_json_loads("{\"privacy\":\"public\"," |
| @@ -1162,6 +1171,17 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1162 | acct = xs_dict_append(acct, "avatar", avatar); | 1171 | acct = xs_dict_append(acct, "avatar", avatar); |
| 1163 | acct = xs_dict_append(acct, "avatar_static", avatar); | 1172 | acct = xs_dict_append(acct, "avatar_static", avatar); |
| 1164 | 1173 | ||
| 1174 | xs *header = NULL; | ||
| 1175 | char *hd = xs_dict_get(snac1.config, "header"); | ||
| 1176 | |||
| 1177 | if (!xs_is_null(hd)) | ||
| 1178 | header = xs_dup(hd); | ||
| 1179 | else | ||
| 1180 | header = xs_fmt("%s/header.png", srv_baseurl); | ||
| 1181 | |||
| 1182 | acct = xs_dict_append(acct, "header", header); | ||
| 1183 | acct = xs_dict_append(acct, "header_static", header); | ||
| 1184 | |||
| 1165 | xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); | 1185 | xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); |
| 1166 | if (xs_type(metadata) == XSTYPE_DICT) { | 1186 | if (xs_type(metadata) == XSTYPE_DICT) { |
| 1167 | xs *fields = xs_list_new(); | 1187 | xs *fields = xs_list_new(); |
| @@ -1170,7 +1190,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1170 | 1190 | ||
| 1171 | xs_dict *val_links = snac1.links; | 1191 | xs_dict *val_links = snac1.links; |
| 1172 | if (xs_is_null(val_links)) | 1192 | if (xs_is_null(val_links)) |
| 1173 | val_links = xs_stock_dict; | 1193 | val_links = xs_stock(XSTYPE_DICT); |
| 1174 | 1194 | ||
| 1175 | int c = 0; | 1195 | int c = 0; |
| 1176 | while (xs_dict_next(metadata, &k, &v, &c)) { | 1196 | while (xs_dict_next(metadata, &k, &v, &c)) { |
| @@ -1190,7 +1210,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1190 | d = xs_dict_append(d, "value", v); | 1210 | d = xs_dict_append(d, "value", v); |
| 1191 | d = xs_dict_append(d, "verified_at", | 1211 | d = xs_dict_append(d, "verified_at", |
| 1192 | xs_type(val_date) == XSTYPE_STRING && *val_date ? | 1212 | xs_type(val_date) == XSTYPE_STRING && *val_date ? |
| 1193 | val_date : xs_stock_null); | 1213 | val_date : xs_stock(XSTYPE_NULL)); |
| 1194 | 1214 | ||
| 1195 | fields = xs_list_append(fields, d); | 1215 | fields = xs_list_append(fields, d); |
| 1196 | } | 1216 | } |
| @@ -1198,9 +1218,9 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1198 | acct = xs_dict_set(acct, "fields", fields); | 1218 | acct = xs_dict_set(acct, "fields", fields); |
| 1199 | } | 1219 | } |
| 1200 | 1220 | ||
| 1201 | acct = xs_dict_append(acct, "followers_count", xs_stock_0); | 1221 | acct = xs_dict_append(acct, "followers_count", xs_stock(0)); |
| 1202 | acct = xs_dict_append(acct, "following_count", xs_stock_0); | 1222 | acct = xs_dict_append(acct, "following_count", xs_stock(0)); |
| 1203 | acct = xs_dict_append(acct, "statuses_count", xs_stock_0); | 1223 | acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); |
| 1204 | 1224 | ||
| 1205 | *body = xs_json_dumps(acct, 4); | 1225 | *body = xs_json_dumps(acct, 4); |
| 1206 | *ctype = "application/json"; | 1226 | *ctype = "application/json"; |
| @@ -1716,8 +1736,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1716 | else | 1736 | else |
| 1717 | if (strcmp(cmd, "/v2/filters") == 0) { /** **/ | 1737 | if (strcmp(cmd, "/v2/filters") == 0) { /** **/ |
| 1718 | /* snac will never have filters | 1738 | /* snac will never have filters |
| 1719 | * but still, without a v2 endpoint a short delay is introduced | 1739 | * but still, without a v2 endpoint a short delay is introduced |
| 1720 | * in some apps */ | 1740 | * in some apps */ |
| 1721 | *body = xs_dup("[]"); | 1741 | *body = xs_dup("[]"); |
| 1722 | *ctype = "application/json"; | 1742 | *ctype = "application/json"; |
| 1723 | status = 200; | 1743 | status = 200; |
| @@ -1797,7 +1817,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1797 | 1817 | ||
| 1798 | ins = xs_dict_append(ins, "email", v); | 1818 | ins = xs_dict_append(ins, "email", v); |
| 1799 | 1819 | ||
| 1800 | ins = xs_dict_append(ins, "rules", xs_stock_list); | 1820 | ins = xs_dict_append(ins, "rules", xs_stock(XSTYPE_LIST)); |
| 1801 | 1821 | ||
| 1802 | xs *l1 = xs_list_append(xs_list_new(), "en"); | 1822 | xs *l1 = xs_list_append(xs_list_new(), "en"); |
| 1803 | ins = xs_dict_append(ins, "languages", l1); | 1823 | ins = xs_dict_append(ins, "languages", l1); |
| @@ -1808,14 +1828,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1808 | 1828 | ||
| 1809 | ins = xs_dict_append(ins, "urls", urls); | 1829 | ins = xs_dict_append(ins, "urls", urls); |
| 1810 | 1830 | ||
| 1811 | xs *d2 = xs_dict_append(xs_dict_new(), "user_count", xs_stock_0); | 1831 | xs *d2 = xs_dict_append(xs_dict_new(), "user_count", xs_stock(0)); |
| 1812 | d2 = xs_dict_append(d2, "status_count", xs_stock_0); | 1832 | d2 = xs_dict_append(d2, "status_count", xs_stock(0)); |
| 1813 | d2 = xs_dict_append(d2, "domain_count", xs_stock_0); | 1833 | d2 = xs_dict_append(d2, "domain_count", xs_stock(0)); |
| 1814 | ins = xs_dict_append(ins, "stats", d2); | 1834 | ins = xs_dict_append(ins, "stats", d2); |
| 1815 | 1835 | ||
| 1816 | ins = xs_dict_append(ins, "registrations", xs_stock_false); | 1836 | ins = xs_dict_append(ins, "registrations", xs_stock(XSTYPE_FALSE)); |
| 1817 | ins = xs_dict_append(ins, "approval_required", xs_stock_false); | 1837 | ins = xs_dict_append(ins, "approval_required", xs_stock(XSTYPE_FALSE)); |
| 1818 | ins = xs_dict_append(ins, "invites_enabled", xs_stock_false); | 1838 | ins = xs_dict_append(ins, "invites_enabled", xs_stock(XSTYPE_FALSE)); |
| 1819 | 1839 | ||
| 1820 | xs *cfg = xs_dict_new(); | 1840 | xs *cfg = xs_dict_new(); |
| 1821 | 1841 | ||
| @@ -2063,7 +2083,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2063 | d = xs_dict_append(d, "name", q); | 2083 | d = xs_dict_append(d, "name", q); |
| 2064 | xs *url = xs_fmt("%s?t=%s", srv_baseurl, q); | 2084 | xs *url = xs_fmt("%s?t=%s", srv_baseurl, q); |
| 2065 | d = xs_dict_append(d, "url", url); | 2085 | d = xs_dict_append(d, "url", url); |
| 2066 | d = xs_dict_append(d, "history", xs_stock_list); | 2086 | d = xs_dict_append(d, "history", xs_stock(XSTYPE_LIST)); |
| 2067 | 2087 | ||
| 2068 | htl = xs_list_append(htl, d); | 2088 | htl = xs_list_append(htl, d); |
| 2069 | } | 2089 | } |
| @@ -2103,8 +2123,6 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2103 | if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) | 2123 | if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) |
| 2104 | return 0; | 2124 | return 0; |
| 2105 | 2125 | ||
| 2106 | srv_debug(1, xs_fmt("mastoapi_post_handler %s", q_path)); | ||
| 2107 | |||
| 2108 | int status = 404; | 2126 | int status = 404; |
| 2109 | xs *args = NULL; | 2127 | xs *args = NULL; |
| 2110 | char *i_ctype = xs_dict_get(req, "content-type"); | 2128 | char *i_ctype = xs_dict_get(req, "content-type"); |
| @@ -2115,7 +2133,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2115 | } | 2133 | } |
| 2116 | else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded")) | 2134 | else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded")) |
| 2117 | { | 2135 | { |
| 2118 | // Some apps send form data instead of json so we should cater for those | 2136 | // Some apps send form data instead of json so we should cater for those |
| 2119 | if (!xs_is_null(payload)) { | 2137 | if (!xs_is_null(payload)) { |
| 2120 | xs *upl = xs_url_dec(payload); | 2138 | xs *upl = xs_url_dec(payload); |
| 2121 | args = xs_url_vars(upl); | 2139 | args = xs_url_vars(upl); |
| @@ -2241,7 +2259,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2241 | strcmp(visibility, "public") == 0 ? 0 : 1); | 2259 | strcmp(visibility, "public") == 0 ? 0 : 1); |
| 2242 | 2260 | ||
| 2243 | if (!xs_is_null(summary) && *summary) { | 2261 | if (!xs_is_null(summary) && *summary) { |
| 2244 | msg = xs_dict_set(msg, "sensitive", xs_stock_true); | 2262 | msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_TRUE)); |
| 2245 | msg = xs_dict_set(msg, "summary", summary); | 2263 | msg = xs_dict_set(msg, "summary", summary); |
| 2246 | } | 2264 | } |
| 2247 | 2265 | ||
| @@ -2298,11 +2316,13 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2298 | } | 2316 | } |
| 2299 | else | 2317 | else |
| 2300 | if (strcmp(op, "unfavourite") == 0) { /** **/ | 2318 | if (strcmp(op, "unfavourite") == 0) { /** **/ |
| 2301 | /* partial support: as the original Like message | 2319 | xs *n_msg = msg_repulsion(&snac, id, "Like"); |
| 2302 | is not stored anywhere here, it's not possible | 2320 | |
| 2303 | to send an Undo + Like; the only thing done here | 2321 | if (n_msg != NULL) { |
| 2304 | is to delete the actor from the list of likes */ | 2322 | enqueue_message(&snac, n_msg); |
| 2305 | object_unadmire(id, snac.actor, 1); | 2323 | |
| 2324 | out = mastoapi_status(&snac, msg); | ||
| 2325 | } | ||
| 2306 | } | 2326 | } |
| 2307 | else | 2327 | else |
| 2308 | if (strcmp(op, "reblog") == 0) { /** **/ | 2328 | if (strcmp(op, "reblog") == 0) { /** **/ |
| @@ -2317,8 +2337,13 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2317 | } | 2337 | } |
| 2318 | else | 2338 | else |
| 2319 | if (strcmp(op, "unreblog") == 0) { /** **/ | 2339 | if (strcmp(op, "unreblog") == 0) { /** **/ |
| 2320 | /* partial support: see comment in 'unfavourite' */ | 2340 | xs *n_msg = msg_repulsion(&snac, id, "Announce"); |
| 2321 | object_unadmire(id, snac.actor, 0); | 2341 | |
| 2342 | if (n_msg != NULL) { | ||
| 2343 | enqueue_message(&snac, n_msg); | ||
| 2344 | |||
| 2345 | out = mastoapi_status(&snac, msg); | ||
| 2346 | } | ||
| 2322 | } | 2347 | } |
| 2323 | else | 2348 | else |
| 2324 | if (strcmp(op, "bookmark") == 0) { /** **/ | 2349 | if (strcmp(op, "bookmark") == 0) { /** **/ |
| @@ -2603,6 +2628,8 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2603 | if (logged_in) | 2628 | if (logged_in) |
| 2604 | user_free(&snac); | 2629 | user_free(&snac); |
| 2605 | 2630 | ||
| 2631 | srv_debug(1, xs_fmt("mastoapi_post_handler %s %d", q_path, status)); | ||
| 2632 | |||
| 2606 | return status; | 2633 | return status; |
| 2607 | } | 2634 | } |
| 2608 | 2635 | ||
| @@ -2701,7 +2728,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path, | |||
| 2701 | if (valid_status(timeline_get_by_md5(&snac, md5, &msg))) { | 2728 | if (valid_status(timeline_get_by_md5(&snac, md5, &msg))) { |
| 2702 | const char *content = xs_dict_get(args, "status"); | 2729 | const char *content = xs_dict_get(args, "status"); |
| 2703 | xs *atls = xs_list_new(); | 2730 | xs *atls = xs_list_new(); |
| 2704 | xs *f_content = not_really_markdown(content, &atls); | 2731 | xs *f_content = not_really_markdown(content, &atls, NULL); |
| 2705 | 2732 | ||
| 2706 | /* replace fields with new content */ | 2733 | /* replace fields with new content */ |
| 2707 | msg = xs_dict_set(msg, "sourceContent", content); | 2734 | msg = xs_dict_set(msg, "sourceContent", content); |
| @@ -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.50-dev" | 4 | #define VERSION "2.52-dev" |
| 5 | 5 | ||
| 6 | #define USER_AGENT "snac/" VERSION | 6 | #define USER_AGENT "snac/" VERSION |
| 7 | 7 | ||
| @@ -110,6 +110,10 @@ int object_del(const char *id); | |||
| 110 | int object_del_if_unref(const char *id); | 110 | int object_del_if_unref(const char *id); |
| 111 | double object_ctime_by_md5(const char *md5); | 111 | double object_ctime_by_md5(const char *md5); |
| 112 | double object_ctime(const char *id); | 112 | double object_ctime(const char *id); |
| 113 | double object_mtime_by_md5(const char *md5); | ||
| 114 | double object_mtime(const char *id); | ||
| 115 | void object_touch(const char *id); | ||
| 116 | |||
| 113 | int object_admire(const char *id, const char *actor, int like); | 117 | int object_admire(const char *id, const char *actor, int like); |
| 114 | int object_unadmire(const char *id, const char *actor, int like); | 118 | int object_unadmire(const char *id, const char *actor, int like); |
| 115 | 119 | ||
| @@ -172,6 +176,7 @@ xs_list *tag_search(char *tag, int skip, int show); | |||
| 172 | 176 | ||
| 173 | int actor_add(const char *actor, xs_dict *msg); | 177 | int actor_add(const char *actor, xs_dict *msg); |
| 174 | int actor_get(const char *actor, xs_dict **data); | 178 | int actor_get(const char *actor, xs_dict **data); |
| 179 | int actor_get_refresh(snac *user, const char *actor, xs_dict **data); | ||
| 175 | 180 | ||
| 176 | int static_get(snac *snac, const char *id, xs_val **data, int *size, const char *inm, xs_str **etag); | 181 | int static_get(snac *snac, const char *id, xs_val **data, int *size, const char *inm, xs_str **etag); |
| 177 | void static_put(snac *snac, const char *id, const char *data, int size); | 182 | void static_put(snac *snac, const char *id, const char *data, int size); |
| @@ -204,6 +209,8 @@ int is_instance_blocked(const char *instance); | |||
| 204 | int instance_block(const char *instance); | 209 | int instance_block(const char *instance); |
| 205 | int instance_unblock(const char *instance); | 210 | int instance_unblock(const char *instance); |
| 206 | 211 | ||
| 212 | int content_check(const char *file, const xs_dict *msg); | ||
| 213 | |||
| 207 | void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries); | 214 | void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries); |
| 208 | void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries); | 215 | void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries); |
| 209 | void enqueue_output_raw(const char *keyid, const char *seckey, | 216 | void enqueue_output_raw(const char *keyid, const char *seckey, |
| @@ -216,6 +223,7 @@ void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_t | |||
| 216 | void enqueue_message(snac *snac, const xs_dict *msg); | 223 | void enqueue_message(snac *snac, const xs_dict *msg); |
| 217 | void enqueue_close_question(snac *user, const char *id, int end_secs); | 224 | void enqueue_close_question(snac *user, const char *id, int end_secs); |
| 218 | void enqueue_verify_links(snac *user); | 225 | void enqueue_verify_links(snac *user); |
| 226 | void enqueue_actor_refresh(snac *user, const char *actor); | ||
| 219 | void enqueue_request_replies(snac *user, const char *id); | 227 | void enqueue_request_replies(snac *user, const char *id); |
| 220 | int was_question_voted(snac *user, const char *id); | 228 | int was_question_voted(snac *user, const char *id); |
| 221 | 229 | ||
| @@ -256,6 +264,7 @@ char *get_atto(const xs_dict *msg); | |||
| 256 | xs_list *get_attachments(const xs_dict *msg); | 264 | xs_list *get_attachments(const xs_dict *msg); |
| 257 | 265 | ||
| 258 | xs_dict *msg_admiration(snac *snac, char *object, char *type); | 266 | xs_dict *msg_admiration(snac *snac, char *object, char *type); |
| 267 | xs_dict *msg_repulsion(snac *user, char *id, char *type); | ||
| 259 | xs_dict *msg_create(snac *snac, const xs_dict *object); | 268 | xs_dict *msg_create(snac *snac, const xs_dict *object); |
| 260 | xs_dict *msg_follow(snac *snac, const char *actor); | 269 | xs_dict *msg_follow(snac *snac, const char *actor); |
| 261 | 270 | ||
| @@ -296,7 +305,8 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path, | |||
| 296 | char *payload, int p_size, | 305 | char *payload, int p_size, |
| 297 | char **body, int *b_size, char **ctype); | 306 | char **body, int *b_size, char **ctype); |
| 298 | 307 | ||
| 299 | xs_str *not_really_markdown(const char *content, xs_list **attach); | 308 | xs_dict *emojis(void); |
| 309 | xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag); | ||
| 300 | xs_str *sanitize(const char *content); | 310 | xs_str *sanitize(const char *content); |
| 301 | xs_str *encode_html(const char *str); | 311 | xs_str *encode_html(const char *str); |
| 302 | 312 | ||
| @@ -45,6 +45,10 @@ typedef char xs_data; | |||
| 45 | /* not really all, just very much */ | 45 | /* not really all, just very much */ |
| 46 | #define XS_ALL 0xfffffff | 46 | #define XS_ALL 0xfffffff |
| 47 | 47 | ||
| 48 | #ifndef xs_countof | ||
| 49 | #define xs_countof(a) (sizeof((a)) / sizeof((*a))) | ||
| 50 | #endif | ||
| 51 | |||
| 48 | void *xs_free(void *ptr); | 52 | void *xs_free(void *ptr); |
| 49 | void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func); | 53 | void *_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__) | 54 | #define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__) |
| @@ -61,6 +65,7 @@ xs_val *xs_collapse(xs_val *data, int offset, int size); | |||
| 61 | xs_val *xs_insert_m(xs_val *data, int offset, const char *mem, int size); | 65 | xs_val *xs_insert_m(xs_val *data, int offset, const char *mem, int size); |
| 62 | #define xs_insert(data, offset, data2) xs_insert_m(data, offset, data2, xs_size(data2)) | 66 | #define xs_insert(data, offset, data2) xs_insert_m(data, offset, data2, xs_size(data2)) |
| 63 | #define xs_append_m(data, mem, size) xs_insert_m(data, xs_size(data) - 1, mem, size) | 67 | #define xs_append_m(data, mem, size) xs_insert_m(data, xs_size(data) - 1, mem, size) |
| 68 | xs_val *xs_stock(int type); | ||
| 64 | 69 | ||
| 65 | xs_str *xs_str_new(const char *str); | 70 | xs_str *xs_str_new(const char *str); |
| 66 | xs_str *xs_str_new_sz(const char *mem, int sz); | 71 | xs_str *xs_str_new_sz(const char *mem, int sz); |
| @@ -137,34 +142,11 @@ unsigned int xs_hash_func(const char *data, int size); | |||
| 137 | #define XS_ASSERT_TYPE_NULL(v, t) (void)(0) | 142 | #define XS_ASSERT_TYPE_NULL(v, t) (void)(0) |
| 138 | #endif | 143 | #endif |
| 139 | 144 | ||
| 140 | extern xs_val xs_stock_null[]; | ||
| 141 | extern xs_val xs_stock_true[]; | ||
| 142 | extern xs_val xs_stock_false[]; | ||
| 143 | extern xs_val xs_stock_0[]; | ||
| 144 | extern xs_val xs_stock_1[]; | ||
| 145 | extern xs_val xs_stock_list[]; | ||
| 146 | extern xs_val xs_stock_dict[]; | ||
| 147 | |||
| 148 | #define xs_return(v) xs_val *__r = v; v = NULL; return __r | 145 | #define xs_return(v) xs_val *__r = v; v = NULL; return __r |
| 149 | 146 | ||
| 150 | 147 | ||
| 151 | #ifdef XS_IMPLEMENTATION | 148 | #ifdef XS_IMPLEMENTATION |
| 152 | 149 | ||
| 153 | xs_val xs_stock_null[] = { XSTYPE_NULL }; | ||
| 154 | xs_val xs_stock_true[] = { XSTYPE_TRUE }; | ||
| 155 | xs_val xs_stock_false[] = { XSTYPE_FALSE }; | ||
| 156 | xs_val xs_stock_0[] = { XSTYPE_NUMBER, '0', '\0' }; | ||
| 157 | xs_val xs_stock_1[] = { XSTYPE_NUMBER, '1', '\0' }; | ||
| 158 | |||
| 159 | #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ | ||
| 160 | xs_val xs_stock_list[] = { XSTYPE_LIST, 0, 0, 0, 1 + _XS_TYPE_SIZE + 1, XSTYPE_EOM }; | ||
| 161 | xs_val xs_stock_dict[] = { XSTYPE_DICT, 0, 0, 0, 1 + _XS_TYPE_SIZE + 1, XSTYPE_EOM }; | ||
| 162 | #else | ||
| 163 | xs_val xs_stock_list[] = { XSTYPE_LIST, 1 + _XS_TYPE_SIZE + 1, 0, 0, 0, XSTYPE_EOM }; | ||
| 164 | xs_val xs_stock_dict[] = { XSTYPE_DICT, 1 + _XS_TYPE_SIZE + 1, 0, 0, 0, XSTYPE_EOM }; | ||
| 165 | #endif | ||
| 166 | |||
| 167 | |||
| 168 | void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func) | 150 | void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func) |
| 169 | { | 151 | { |
| 170 | xs_val *ndata = realloc(ptr, size); | 152 | xs_val *ndata = realloc(ptr, size); |
| @@ -369,10 +351,14 @@ int xs_cmp(const xs_val *v1, const xs_val *v2) | |||
| 369 | xs_val *xs_dup(const xs_val *data) | 351 | xs_val *xs_dup(const xs_val *data) |
| 370 | /* creates a duplicate of data */ | 352 | /* creates a duplicate of data */ |
| 371 | { | 353 | { |
| 372 | int sz = xs_size(data); | 354 | xs_val *s = NULL; |
| 373 | xs_val *s = xs_realloc(NULL, _xs_blk_size(sz)); | 355 | |
| 356 | if (data) { | ||
| 357 | int sz = xs_size(data); | ||
| 358 | s = xs_realloc(NULL, _xs_blk_size(sz)); | ||
| 374 | 359 | ||
| 375 | memcpy(s, data, sz); | 360 | memcpy(s, data, sz); |
| 361 | } | ||
| 376 | 362 | ||
| 377 | return s; | 363 | return s; |
| 378 | } | 364 | } |
| @@ -437,6 +423,39 @@ xs_val *xs_insert_m(xs_val *data, int offset, const char *mem, int size) | |||
| 437 | } | 423 | } |
| 438 | 424 | ||
| 439 | 425 | ||
| 426 | xs_val *xs_stock(int type) | ||
| 427 | /* returns stock values */ | ||
| 428 | { | ||
| 429 | static xs_val stock_null[] = { XSTYPE_NULL }; | ||
| 430 | static xs_val stock_true[] = { XSTYPE_TRUE }; | ||
| 431 | static xs_val stock_false[] = { XSTYPE_FALSE }; | ||
| 432 | static xs_val stock_0[] = { XSTYPE_NUMBER, '0', '\0' }; | ||
| 433 | static xs_val stock_1[] = { XSTYPE_NUMBER, '1', '\0' }; | ||
| 434 | static xs_list *stock_list = NULL; | ||
| 435 | static xs_dict *stock_dict = NULL; | ||
| 436 | |||
| 437 | switch (type) { | ||
| 438 | case 0: return stock_0; | ||
| 439 | case 1: return stock_1; | ||
| 440 | case XSTYPE_NULL: return stock_null; | ||
| 441 | case XSTYPE_TRUE: return stock_true; | ||
| 442 | case XSTYPE_FALSE: return stock_false; | ||
| 443 | |||
| 444 | case XSTYPE_LIST: | ||
| 445 | if (stock_list == NULL) | ||
| 446 | stock_list = xs_list_new(); | ||
| 447 | return stock_list; | ||
| 448 | |||
| 449 | case XSTYPE_DICT: | ||
| 450 | if (stock_dict == NULL) | ||
| 451 | stock_dict = xs_dict_new(); | ||
| 452 | return stock_dict; | ||
| 453 | } | ||
| 454 | |||
| 455 | return NULL; | ||
| 456 | } | ||
| 457 | |||
| 458 | |||
| 440 | /** strings **/ | 459 | /** strings **/ |
| 441 | 460 | ||
| 442 | xs_str *xs_str_new(const char *str) | 461 | xs_str *xs_str_new(const char *str) |
| @@ -647,10 +666,14 @@ xs_str *xs_tolower_i(xs_str *str) | |||
| 647 | xs_list *xs_list_new(void) | 666 | xs_list *xs_list_new(void) |
| 648 | /* creates a new list */ | 667 | /* creates a new list */ |
| 649 | { | 668 | { |
| 650 | return memcpy( | 669 | int sz = 1 + _XS_TYPE_SIZE + 1; |
| 651 | xs_realloc(NULL, _xs_blk_size(sizeof(xs_stock_list))), | 670 | xs_list *l = xs_realloc(NULL, sz); |
| 652 | xs_stock_list, sizeof(xs_stock_list) | 671 | memset(l, '\0', sz); |
| 653 | ); | 672 | |
| 673 | l[0] = XSTYPE_LIST; | ||
| 674 | _xs_put_size(&l[1], sz); | ||
| 675 | |||
| 676 | return l; | ||
| 654 | } | 677 | } |
| 655 | 678 | ||
| 656 | 679 | ||
| @@ -660,8 +683,8 @@ xs_list *_xs_list_write_litem(xs_list *list, int offset, const char *mem, int ds | |||
| 660 | XS_ASSERT_TYPE(list, XSTYPE_LIST); | 683 | XS_ASSERT_TYPE(list, XSTYPE_LIST); |
| 661 | 684 | ||
| 662 | if (mem == NULL) { | 685 | if (mem == NULL) { |
| 663 | mem = xs_stock_null; | 686 | mem = xs_stock(XSTYPE_NULL); |
| 664 | dsz = sizeof(xs_stock_null); | 687 | dsz = xs_size(mem); |
| 665 | } | 688 | } |
| 666 | 689 | ||
| 667 | list = xs_expand(list, offset, dsz + 1); | 690 | list = xs_expand(list, offset, dsz + 1); |
| @@ -947,10 +970,14 @@ xs_list *xs_list_cat(xs_list *l1, const xs_list *l2) | |||
| 947 | xs_dict *xs_dict_new(void) | 970 | xs_dict *xs_dict_new(void) |
| 948 | /* creates a new dict */ | 971 | /* creates a new dict */ |
| 949 | { | 972 | { |
| 950 | return memcpy( | 973 | int sz = 1 + _XS_TYPE_SIZE + 1; |
| 951 | xs_realloc(NULL, _xs_blk_size(sizeof(xs_stock_dict))), | 974 | xs_dict *d = xs_realloc(NULL, sz); |
| 952 | xs_stock_dict, sizeof(xs_stock_dict) | 975 | memset(d, '\0', sz); |
| 953 | ); | 976 | |
| 977 | d[0] = XSTYPE_DICT; | ||
| 978 | _xs_put_size(&d[1], sz); | ||
| 979 | |||
| 980 | return d; | ||
| 954 | } | 981 | } |
| 955 | 982 | ||
| 956 | 983 | ||
| @@ -962,8 +989,8 @@ xs_dict *_xs_dict_write_ditem(xs_dict *dict, int offset, const xs_str *key, | |||
| 962 | XS_ASSERT_TYPE(key, XSTYPE_STRING); | 989 | XS_ASSERT_TYPE(key, XSTYPE_STRING); |
| 963 | 990 | ||
| 964 | if (data == NULL) { | 991 | if (data == NULL) { |
| 965 | data = xs_stock_null; | 992 | data = xs_stock(XSTYPE_NULL); |
| 966 | dsz = sizeof(xs_stock_null); | 993 | dsz = xs_size(data); |
| 967 | } | 994 | } |
| 968 | 995 | ||
| 969 | int ksz = xs_size(key); | 996 | int ksz = xs_size(key); |
| @@ -11,8 +11,10 @@ xs_val *xs_json_load(FILE *f); | |||
| 11 | xs_val *xs_json_loads(const xs_str *json); | 11 | xs_val *xs_json_loads(const xs_str *json); |
| 12 | 12 | ||
| 13 | xstype xs_json_load_type(FILE *f); | 13 | xstype xs_json_load_type(FILE *f); |
| 14 | int xs_json_load_array_iter(FILE *f, xs_val **value, int *c); | 14 | int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c); |
| 15 | int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, int *c); | 15 | int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c); |
| 16 | xs_list *xs_json_load_array(FILE *f); | ||
| 17 | xs_dict *xs_json_load_object(FILE *f); | ||
| 16 | 18 | ||
| 17 | 19 | ||
| 18 | #ifdef XS_IMPLEMENTATION | 20 | #ifdef XS_IMPLEMENTATION |
| @@ -324,10 +326,9 @@ static xs_val *_xs_json_load_lexer(FILE *f, js_type *t) | |||
| 324 | } | 326 | } |
| 325 | 327 | ||
| 326 | 328 | ||
| 327 | static xs_list *_xs_json_load_array(FILE *f); | 329 | int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c) |
| 328 | static xs_dict *_xs_json_load_object(FILE *f); | 330 | /* loads the next scalar value from the JSON stream */ |
| 329 | 331 | /* if the value ahead is compound, value is NULL and pt is set */ | |
| 330 | int xs_json_load_array_iter(FILE *f, xs_val **value, int *c) | ||
| 331 | { | 332 | { |
| 332 | js_type t; | 333 | js_type t; |
| 333 | 334 | ||
| @@ -346,14 +347,16 @@ int xs_json_load_array_iter(FILE *f, xs_val **value, int *c) | |||
| 346 | return -1; | 347 | return -1; |
| 347 | } | 348 | } |
| 348 | 349 | ||
| 349 | if (t == JS_OBRACK) | 350 | if (*value == NULL) { |
| 350 | *value = _xs_json_load_array(f); | 351 | /* possible compound type ahead */ |
| 351 | else | 352 | if (t == JS_OBRACK) |
| 352 | if (t == JS_OCURLY) | 353 | *pt = XSTYPE_LIST; |
| 353 | *value = _xs_json_load_object(f); | 354 | else |
| 354 | 355 | if (t == JS_OCURLY) | |
| 355 | if (*value == NULL) | 356 | *pt = XSTYPE_DICT; |
| 356 | return -1; | 357 | else |
| 358 | return -1; | ||
| 359 | } | ||
| 357 | 360 | ||
| 358 | *c = *c + 1; | 361 | *c = *c + 1; |
| 359 | 362 | ||
| @@ -361,21 +364,38 @@ int xs_json_load_array_iter(FILE *f, xs_val **value, int *c) | |||
| 361 | } | 364 | } |
| 362 | 365 | ||
| 363 | 366 | ||
| 364 | static xs_list *_xs_json_load_array(FILE *f) | 367 | xs_list *xs_json_load_array(FILE *f) |
| 365 | /* parses a JSON array */ | 368 | /* loads a full JSON array (after the initial OBRACK) */ |
| 366 | { | 369 | { |
| 370 | xstype t; | ||
| 367 | xs_list *l = xs_list_new(); | 371 | xs_list *l = xs_list_new(); |
| 368 | int c = 0; | 372 | int c = 0; |
| 369 | 373 | ||
| 370 | for (;;) { | 374 | for (;;) { |
| 371 | xs *v = NULL; | 375 | xs *v = NULL; |
| 372 | int r = xs_json_load_array_iter(f, &v, &c); | 376 | int r = xs_json_load_array_iter(f, &v, &t, &c); |
| 373 | 377 | ||
| 374 | if (r == -1) | 378 | if (r == -1) |
| 375 | l = xs_free(l); | 379 | l = xs_free(l); |
| 376 | 380 | ||
| 377 | if (r == 1) | 381 | if (r == 1) { |
| 382 | /* partial load? */ | ||
| 383 | if (v == NULL) { | ||
| 384 | if (t == XSTYPE_LIST) | ||
| 385 | v = xs_json_load_array(f); | ||
| 386 | else | ||
| 387 | if (t == XSTYPE_DICT) | ||
| 388 | v = xs_json_load_object(f); | ||
| 389 | } | ||
| 390 | |||
| 391 | /* still null? fail */ | ||
| 392 | if (v == NULL) { | ||
| 393 | l = xs_free(l); | ||
| 394 | break; | ||
| 395 | } | ||
| 396 | |||
| 378 | l = xs_list_append(l, v); | 397 | l = xs_list_append(l, v); |
| 398 | } | ||
| 379 | else | 399 | else |
| 380 | break; | 400 | break; |
| 381 | } | 401 | } |
| @@ -384,7 +404,9 @@ static xs_list *_xs_json_load_array(FILE *f) | |||
| 384 | } | 404 | } |
| 385 | 405 | ||
| 386 | 406 | ||
| 387 | int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, int *c) | 407 | int 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 */ | ||
| 409 | /* if the value ahead is compound, value is NULL and pt is set */ | ||
| 388 | { | 410 | { |
| 389 | js_type t; | 411 | js_type t; |
| 390 | 412 | ||
| @@ -413,14 +435,16 @@ int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, int *c) | |||
| 413 | 435 | ||
| 414 | *value = _xs_json_load_lexer(f, &t); | 436 | *value = _xs_json_load_lexer(f, &t); |
| 415 | 437 | ||
| 416 | if (t == JS_OBRACK) | 438 | if (*value == NULL) { |
| 417 | *value = _xs_json_load_array(f); | 439 | /* possible complex type ahead */ |
| 418 | else | 440 | if (t == JS_OBRACK) |
| 419 | if (t == JS_OCURLY) | 441 | *pt = XSTYPE_LIST; |
| 420 | *value = _xs_json_load_object(f); | 442 | else |
| 421 | 443 | if (t == JS_OCURLY) | |
| 422 | if (*value == NULL) | 444 | *pt = XSTYPE_DICT; |
| 423 | return -1; | 445 | else |
| 446 | return -1; | ||
| 447 | } | ||
| 424 | 448 | ||
| 425 | *c = *c + 1; | 449 | *c = *c + 1; |
| 426 | 450 | ||
| @@ -428,22 +452,39 @@ int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, int *c) | |||
| 428 | } | 452 | } |
| 429 | 453 | ||
| 430 | 454 | ||
| 431 | static xs_dict *_xs_json_load_object(FILE *f) | 455 | xs_dict *xs_json_load_object(FILE *f) |
| 432 | /* parses a JSON object */ | 456 | /* loads a full JSON object (after the initial OCURLY) */ |
| 433 | { | 457 | { |
| 458 | xstype t; | ||
| 434 | xs_dict *d = xs_dict_new(); | 459 | xs_dict *d = xs_dict_new(); |
| 435 | int c = 0; | 460 | int c = 0; |
| 436 | 461 | ||
| 437 | for (;;) { | 462 | for (;;) { |
| 438 | xs *k = NULL; | 463 | xs *k = NULL; |
| 439 | xs *v = NULL; | 464 | xs *v = NULL; |
| 440 | int r = xs_json_load_object_iter(f, &k, &v, &c); | 465 | int r = xs_json_load_object_iter(f, &k, &v, &t, &c); |
| 441 | 466 | ||
| 442 | if (r == -1) | 467 | if (r == -1) |
| 443 | d = xs_free(d); | 468 | d = xs_free(d); |
| 444 | 469 | ||
| 445 | if (r == 1) | 470 | if (r == 1) { |
| 471 | /* partial load? */ | ||
| 472 | if (v == NULL) { | ||
| 473 | if (t == XSTYPE_LIST) | ||
| 474 | v = xs_json_load_array(f); | ||
| 475 | else | ||
| 476 | if (t == XSTYPE_DICT) | ||
| 477 | v = xs_json_load_object(f); | ||
| 478 | } | ||
| 479 | |||
| 480 | /* still null? fail */ | ||
| 481 | if (v == NULL) { | ||
| 482 | d = xs_free(d); | ||
| 483 | break; | ||
| 484 | } | ||
| 485 | |||
| 446 | d = xs_dict_append(d, k, v); | 486 | d = xs_dict_append(d, k, v); |
| 487 | } | ||
| 447 | else | 488 | else |
| 448 | break; | 489 | break; |
| 449 | } | 490 | } |
| @@ -492,10 +533,10 @@ xs_val *xs_json_load(FILE *f) | |||
| 492 | xstype t = xs_json_load_type(f); | 533 | xstype t = xs_json_load_type(f); |
| 493 | 534 | ||
| 494 | if (t == XSTYPE_LIST) | 535 | if (t == XSTYPE_LIST) |
| 495 | v = _xs_json_load_array(f); | 536 | v = xs_json_load_array(f); |
| 496 | else | 537 | else |
| 497 | if (t == XSTYPE_DICT) | 538 | if (t == XSTYPE_DICT) |
| 498 | v = _xs_json_load_object(f); | 539 | v = xs_json_load_object(f); |
| 499 | 540 | ||
| 500 | return v; | 541 | return v; |
| 501 | } | 542 | } |
| @@ -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_unicode.h b/xs_unicode.h index 47e1101..6654da4 100644 --- a/xs_unicode.h +++ b/xs_unicode.h | |||
| @@ -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 | ||
| 34 | int _xs_utf8_enc(char buf[4], unsigned int cpoint) | 34 | int _xs_utf8_enc(char buf[4], unsigned int cpoint) |
| @@ -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; |
| @@ -56,7 +56,7 @@ xs_dict *xs_url_vars(const char *str) | |||
| 56 | 56 | ||
| 57 | l = args; | 57 | l = args; |
| 58 | while (xs_list_iter(&l, &v)) { | 58 | while (xs_list_iter(&l, &v)) { |
| 59 | xs *kv = xs_split_n(v, "=", 2); | 59 | xs *kv = xs_split_n(v, "=", 1); |
| 60 | 60 | ||
| 61 | if (xs_list_len(kv) == 2) { | 61 | if (xs_list_len(kv) == 2) { |
| 62 | const char *key = xs_list_get(kv, 0); | 62 | const char *key = xs_list_get(kv, 0); |
diff --git a/xs_version.h b/xs_version.h index 50dcb5e..f655735 100644 --- a/xs_version.h +++ b/xs_version.h | |||
| @@ -1 +1 @@ | |||
| /* f46d5b29627b20a6e9ec4ef60c01df1d2d778520 2024-03-09T08:26:31+01:00 */ | /* f712d1336ef427c3b56305364b2687578537543f 2024-04-14T19:11:53+02:00 */ | ||