diff options
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | Makefile.NetBSD | 2 | ||||
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | RELEASE_NOTES.md | 20 | ||||
| -rw-r--r-- | TODO.md | 24 | ||||
| -rw-r--r-- | activitypub.c | 420 | ||||
| -rw-r--r-- | data.c | 586 | ||||
| -rw-r--r-- | doc/snac.8 | 15 | ||||
| -rw-r--r-- | format.c | 19 | ||||
| -rw-r--r-- | html.c | 703 | ||||
| -rw-r--r-- | http.c | 36 | ||||
| -rw-r--r-- | httpd.c | 66 | ||||
| -rw-r--r-- | main.c | 26 | ||||
| -rw-r--r-- | mastoapi.c | 495 | ||||
| -rw-r--r-- | snac.h | 69 | ||||
| -rw-r--r-- | upgrade.c | 73 | ||||
| -rw-r--r-- | utils.c | 17 | ||||
| -rw-r--r-- | webfinger.c | 49 | ||||
| -rw-r--r-- | xs.h | 254 | ||||
| -rw-r--r-- | xs_curl.h | 6 | ||||
| -rw-r--r-- | xs_fcgi.h | 4 | ||||
| -rw-r--r-- | xs_html.h | 34 | ||||
| -rw-r--r-- | xs_httpd.h | 9 | ||||
| -rw-r--r-- | xs_json.h | 23 | ||||
| -rw-r--r-- | xs_mime.h | 22 | ||||
| -rw-r--r-- | xs_regex.h | 43 | ||||
| -rw-r--r-- | xs_set.h | 8 | ||||
| -rw-r--r-- | xs_unicode.h | 22 | ||||
| -rw-r--r-- | xs_url.h | 13 | ||||
| -rw-r--r-- | xs_version.h | 2 |
30 files changed, 2075 insertions, 994 deletions
| @@ -36,7 +36,7 @@ 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 xs_regex.h snac.h | 39 | xs_set.h xs_time.h xs_regex.h xs_match.h snac.h |
| 40 | format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.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 | 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 \ | 42 | html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ |
diff --git a/Makefile.NetBSD b/Makefile.NetBSD index 67c77a5..bb2f1bc 100644 --- a/Makefile.NetBSD +++ b/Makefile.NetBSD | |||
| @@ -38,7 +38,7 @@ uninstall: | |||
| 38 | 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 \ |
| 39 | xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h | 39 | xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h snac.h |
| 40 | 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 \ |
| 41 | xs_set.h xs_time.h xs_regex.h snac.h | 41 | xs_set.h xs_time.h xs_regex.h xs_match.h snac.h |
| 42 | format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.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 | 43 | xs_time.h snac.h |
| 44 | 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 \ |
| @@ -58,7 +58,6 @@ Run `make` and then `make install` as root. | |||
| 58 | 58 | ||
| 59 | If you're compiling on NetBSD, you should use the specific provided Makefile and run `make -f Makefile.NetBSD` and then `make -f Makefile.NetBSD install` as root. | 59 | If you're compiling on NetBSD, you should use the specific provided Makefile and run `make -f Makefile.NetBSD` and then `make -f Makefile.NetBSD install` as root. |
| 60 | 60 | ||
| 61 | |||
| 62 | From version 2.27, `snac` includes support for the Mastodon API; if you are not interested on it, you can compile it out by running | 61 | From version 2.27, `snac` includes support for the Mastodon API; if you are not interested on it, you can compile it out by running |
| 63 | 62 | ||
| 64 | ```sh | 63 | ```sh |
| @@ -71,6 +70,12 @@ If your compilation process complains about undefined references to `shm_open()` | |||
| 71 | make LDFLAGS=-lrt | 70 | make LDFLAGS=-lrt |
| 72 | ``` | 71 | ``` |
| 73 | 72 | ||
| 73 | If it still gives compilation errors (because your system does not implement the shared memory functions), you can fix it with | ||
| 74 | |||
| 75 | ```sh | ||
| 76 | make CFLAGS=-DWITHOUT_SHM | ||
| 77 | ``` | ||
| 78 | |||
| 74 | See the administrator manual on how to proceed from here. | 79 | See the administrator manual on how to proceed from here. |
| 75 | 80 | ||
| 76 | ## Testing via Docker | 81 | ## Testing via Docker |
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f82e722..cea217a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -1,9 +1,29 @@ | |||
| 1 | # Release Notes | 1 | # Release Notes |
| 2 | 2 | ||
| 3 | ## 2.53 | ||
| 4 | |||
| 5 | New user feature to search by post content (using regular expressions) or tag. | ||
| 6 | |||
| 7 | Added some (partial) support for `Event` object types. | ||
| 8 | |||
| 9 | Minor fixes: Allow unboosting your own posts (contributed by khm), CSS fixes for the Dillo browser (contributed by kvibber). | ||
| 10 | |||
| 3 | ## 2.52 | 11 | ## 2.52 |
| 4 | 12 | ||
| 5 | Posts that were liked or boosted can now be unliked and unboosted. | 13 | Posts that were liked or boosted can now be unliked and unboosted. |
| 6 | 14 | ||
| 15 | Outgoing message timeouts are no longer hardcoded and can be configured (see `snac(8)` for more information). | ||
| 16 | |||
| 17 | Fixed a bug that caused some incorrect unfollows under special conditions (with shared inboxes enabled and users from the same instance that follow each other, the internal message distributor was confused). | ||
| 18 | |||
| 19 | Mastodon API: Added support for lists. | ||
| 20 | |||
| 21 | Added a header to avoid over-zealous caching in some browsers (contributed by louis77). | ||
| 22 | |||
| 23 | Added support for running and federating inside hidden networks like Tor, I2P or Loki (contributed by iwojima). | ||
| 24 | |||
| 25 | Fixed an error processing polls coming from Pleroma instances. | ||
| 26 | |||
| 7 | ## 2.51 | 27 | ## 2.51 |
| 8 | 28 | ||
| 9 | 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). | 29 | 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). |
| @@ -10,36 +10,34 @@ Mastodon API: fix whatever the fuck is making the official app and Megalodon to | |||
| 10 | 10 | ||
| 11 | 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 |
| 12 | 12 | ||
| 13 | Editing / Updating a post does not index newly added hashtags. | ||
| 14 | |||
| 13 | ## Wishlist | 15 | ## Wishlist |
| 14 | 16 | ||
| 15 | Implement `Group`-like accounts (i.e. an actor that boosts to their followers all posts that mention it). | 17 | Track 'Event' data types standardization; how to add plan-to-attend and similar activities (more info: https://event-federation.eu/) |
| 16 | 18 | ||
| 17 | Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 | 19 | Implement "FEP-3b86: Activity Intents" https://codeberg.org/fediverse/fep/src/branch/main/fep/3b86/fep-3b86.md |
| 20 | |||
| 21 | Track "FEP-ef61: Portable Objects" https://codeberg.org/fediverse/fep/src/branch/main/fep/ef61/fep-ef61.md | ||
| 22 | |||
| 23 | Implement `Group`-like accounts (i.e. an actor that boosts to their followers all posts that mention it). | ||
| 18 | 24 | ||
| 19 | Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 | 25 | Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 |
| 20 | 26 | ||
| 21 | Consider adding Mastodon import functionality (for following_accounts.csv and outbox.json). | 27 | Consider adding Mastodon import functionality (for following_accounts.csv and outbox.json). |
| 22 | 28 | ||
| 23 | Consider adding milter-like support to reject posts to mitigate spam. | ||
| 24 | |||
| 25 | Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`). | 29 | Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`). |
| 26 | 30 | ||
| 27 | Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050 | ||
| 28 | |||
| 29 | Add support for /share?text=tt&website=url (whatever it is, see https://mastodonshare.com/ for details). | 31 | Add support for /share?text=tt&website=url (whatever it is, see https://mastodonshare.com/ for details). |
| 30 | 32 | ||
| 31 | Add support for /authorize_interaction (whatever it is). | 33 | Add support for /authorize_interaction (whatever it is). |
| 32 | 34 | ||
| 33 | Add a list of hashtags to drop. | 35 | Add a list of hashtags to drop. |
| 34 | 36 | ||
| 35 | Add domain/subdomain flexibility according to https://codeberg.org/grunfink/snac2/issues/3 | ||
| 36 | |||
| 37 | The 'history' pages are just monthly HTML snapshots of the local timeline. This is ok and cheap and easy, but is problematic if you e.g. intentionally delete a post because it will remain there in the history forever. If you activate local timeline purging, purged entries will remain in the history as 'ghosts', which may or may not be what the user wants. | 37 | The 'history' pages are just monthly HTML snapshots of the local timeline. This is ok and cheap and easy, but is problematic if you e.g. intentionally delete a post because it will remain there in the history forever. If you activate local timeline purging, purged entries will remain in the history as 'ghosts', which may or may not be what the user wants. |
| 38 | 38 | ||
| 39 | Implement bulleted lists. Mastodon is crap and won't show them, but other implementations (Friendica, Pleroma) will do. | 39 | Implement bulleted lists. Mastodon is crap and won't show them, but other implementations (Friendica, Pleroma) will do. |
| 40 | 40 | ||
| 41 | User request: "will it be possible to click on a link and instead of opening the original instance, we'll be able only to see a list of the posts of this person here in comam?. Something like Mastodon does." | ||
| 42 | |||
| 43 | The actual storage system wastes too much disk space (lots of small files that really consume 4k of storage). Consider alternatives. | 41 | The actual storage system wastes too much disk space (lots of small files that really consume 4k of storage). Consider alternatives. |
| 44 | 42 | ||
| 45 | ## Closed | 43 | ## Closed |
| @@ -311,3 +309,9 @@ Consider implementing the rejection of activities from recently-created accounts | |||
| 311 | Consider discarding posts by content using string or regex to mitigate spam (2024-03-14T10:40:14+0100). | 309 | Consider discarding posts by content using string or regex to mitigate spam (2024-03-14T10:40:14+0100). |
| 312 | 310 | ||
| 313 | Post edits should preserve the image and the image description somewhat (2024-03-22T09:57:18+0100). | 311 | Post edits should preserve the image and the image description somewhat (2024-03-22T09:57:18+0100). |
| 312 | |||
| 313 | Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 | ||
| 314 | |||
| 315 | Consider adding milter-like support to reject posts to mitigate spam (discarded; 2024-04-20T22:46:35+0200). | ||
| 316 | |||
| 317 | Implement support for 'Event' data types. Example: https://fediversity.site/item/e9bdb383-eeb9-4d7d-b2f7-c6401267cae0 (2024-05-12T08:56:27+0200) | ||
diff --git a/activitypub.c b/activitypub.c index 9a23e14..6e40a88 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -67,7 +67,7 @@ int activitypub_request(snac *user, const char *url, xs_dict **data) | |||
| 67 | xs *response = NULL; | 67 | xs *response = NULL; |
| 68 | xs *payload = NULL; | 68 | xs *payload = NULL; |
| 69 | int p_size; | 69 | int p_size; |
| 70 | char *ctype; | 70 | const char *ctype; |
| 71 | 71 | ||
| 72 | *data = NULL; | 72 | *data = NULL; |
| 73 | 73 | ||
| @@ -154,20 +154,21 @@ int actor_request(snac *user, const char *actor, xs_dict **data) | |||
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | 156 | ||
| 157 | char *get_atto(const xs_dict *msg) | 157 | const char *get_atto(const xs_dict *msg) |
| 158 | /* gets the attributedTo field (an actor) */ | 158 | /* gets the attributedTo field (an actor) */ |
| 159 | { | 159 | { |
| 160 | char *actor = xs_dict_get(msg, "attributedTo"); | 160 | const xs_val *actor = xs_dict_get(msg, "attributedTo"); |
| 161 | 161 | ||
| 162 | /* if the actor is a list of objects (like on Peertube videos), pick the Person */ | 162 | /* if the actor is a list of objects (like on Peertube videos), pick the Person */ |
| 163 | if (xs_type(actor) == XSTYPE_LIST) { | 163 | if (xs_type(actor) == XSTYPE_LIST) { |
| 164 | xs_list *p = actor; | 164 | const xs_list *p = actor; |
| 165 | xs_dict *v; | 165 | int c = 0; |
| 166 | const xs_dict *v; | ||
| 166 | actor = NULL; | 167 | actor = NULL; |
| 167 | 168 | ||
| 168 | while (actor == NULL && xs_list_iter(&p, &v)) { | 169 | while (actor == NULL && xs_list_next(p, &v, &c)) { |
| 169 | if (xs_type(v) == XSTYPE_DICT) { | 170 | if (xs_type(v) == XSTYPE_DICT) { |
| 170 | char *type = xs_dict_get(v, "type"); | 171 | const char *type = xs_dict_get(v, "type"); |
| 171 | if (xs_type(type) == XSTYPE_STRING && strcmp(type, "Person") == 0) { | 172 | if (xs_type(type) == XSTYPE_STRING && strcmp(type, "Person") == 0) { |
| 172 | actor = xs_dict_get(v, "id"); | 173 | actor = xs_dict_get(v, "id"); |
| 173 | 174 | ||
| @@ -186,12 +187,12 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 186 | /* unify the garbage fire that are the attachments */ | 187 | /* unify the garbage fire that are the attachments */ |
| 187 | { | 188 | { |
| 188 | xs_list *l = xs_list_new(); | 189 | xs_list *l = xs_list_new(); |
| 189 | xs_list *p; | 190 | const xs_list *p; |
| 190 | 191 | ||
| 191 | /* try first the attachments list */ | 192 | /* try first the attachments list */ |
| 192 | if (!xs_is_null(p = xs_dict_get(msg, "attachment"))) { | 193 | if (!xs_is_null(p = xs_dict_get(msg, "attachment"))) { |
| 193 | xs *attach = NULL; | 194 | xs *attach = NULL; |
| 194 | xs_val *v; | 195 | const xs_val *v; |
| 195 | 196 | ||
| 196 | /* ensure it's a list */ | 197 | /* ensure it's a list */ |
| 197 | if (xs_type(p) == XSTYPE_DICT) { | 198 | if (xs_type(p) == XSTYPE_DICT) { |
| @@ -203,23 +204,24 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 203 | 204 | ||
| 204 | if (xs_type(attach) == XSTYPE_LIST) { | 205 | if (xs_type(attach) == XSTYPE_LIST) { |
| 205 | /* does the message have an image? */ | 206 | /* does the message have an image? */ |
| 206 | if (xs_type(v = xs_dict_get(msg, "image")) == XSTYPE_DICT) { | 207 | const xs_dict *d = xs_dict_get(msg, "image"); |
| 208 | if (xs_type(d) == XSTYPE_DICT) { | ||
| 207 | /* add it to the attachment list */ | 209 | /* add it to the attachment list */ |
| 208 | attach = xs_list_append(attach, v); | 210 | attach = xs_list_append(attach, d); |
| 209 | } | 211 | } |
| 210 | } | 212 | } |
| 211 | 213 | ||
| 212 | /* now iterate the list */ | 214 | /* now iterate the list */ |
| 213 | p = attach; | 215 | int c = 0; |
| 214 | while (xs_list_iter(&p, &v)) { | 216 | while (xs_list_next(attach, &v, &c)) { |
| 215 | char *type = xs_dict_get(v, "mediaType"); | 217 | const char *type = xs_dict_get(v, "mediaType"); |
| 216 | if (xs_is_null(type)) | 218 | if (xs_is_null(type)) |
| 217 | type = xs_dict_get(v, "type"); | 219 | type = xs_dict_get(v, "type"); |
| 218 | 220 | ||
| 219 | if (xs_is_null(type)) | 221 | if (xs_is_null(type)) |
| 220 | continue; | 222 | continue; |
| 221 | 223 | ||
| 222 | char *href = xs_dict_get(v, "url"); | 224 | const char *href = xs_dict_get(v, "url"); |
| 223 | if (xs_is_null(href)) | 225 | if (xs_is_null(href)) |
| 224 | href = xs_dict_get(v, "href"); | 226 | href = xs_dict_get(v, "href"); |
| 225 | if (xs_is_null(href)) | 227 | if (xs_is_null(href)) |
| @@ -233,7 +235,7 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 233 | type = mt; | 235 | type = mt; |
| 234 | } | 236 | } |
| 235 | 237 | ||
| 236 | char *name = xs_dict_get(v, "name"); | 238 | const char *name = xs_dict_get(v, "name"); |
| 237 | if (xs_is_null(name)) | 239 | if (xs_is_null(name)) |
| 238 | name = xs_dict_get(msg, "name"); | 240 | name = xs_dict_get(msg, "name"); |
| 239 | if (xs_is_null(name)) | 241 | if (xs_is_null(name)) |
| @@ -252,29 +254,31 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 252 | p = xs_dict_get(msg, "url"); | 254 | p = xs_dict_get(msg, "url"); |
| 253 | 255 | ||
| 254 | if (xs_type(p) == XSTYPE_LIST) { | 256 | if (xs_type(p) == XSTYPE_LIST) { |
| 255 | char *href = NULL; | 257 | const char *href = NULL; |
| 256 | char *type = NULL; | 258 | const char *type = NULL; |
| 257 | xs_val *v; | 259 | int c = 0; |
| 260 | const xs_val *v; | ||
| 258 | 261 | ||
| 259 | while (href == NULL && xs_list_iter(&p, &v)) { | 262 | while (href == NULL && xs_list_next(p, &v, &c)) { |
| 260 | if (xs_type(v) == XSTYPE_DICT) { | 263 | if (xs_type(v) == XSTYPE_DICT) { |
| 261 | char *mtype = xs_dict_get(v, "type"); | 264 | const char *mtype = xs_dict_get(v, "type"); |
| 262 | 265 | ||
| 263 | if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "Link") == 0) { | 266 | if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "Link") == 0) { |
| 264 | mtype = xs_dict_get(v, "mediaType"); | 267 | mtype = xs_dict_get(v, "mediaType"); |
| 265 | xs_list *tag = xs_dict_get(v, "tag"); | 268 | const xs_list *tag = xs_dict_get(v, "tag"); |
| 266 | 269 | ||
| 267 | if (xs_type(mtype) == XSTYPE_STRING && | 270 | if (xs_type(mtype) == XSTYPE_STRING && |
| 268 | strcmp(mtype, "application/x-mpegURL") == 0 && | 271 | strcmp(mtype, "application/x-mpegURL") == 0 && |
| 269 | xs_type(tag) == XSTYPE_LIST) { | 272 | xs_type(tag) == XSTYPE_LIST) { |
| 270 | /* now iterate the tag list, looking for a video URL */ | 273 | /* now iterate the tag list, looking for a video URL */ |
| 271 | xs_dict *d; | 274 | const xs_dict *d; |
| 275 | int c = 0; | ||
| 272 | 276 | ||
| 273 | while (href == NULL && xs_list_iter(&tag, &d)) { | 277 | while (href == NULL && xs_list_next(tag, &d, &c)) { |
| 274 | if (xs_type(d) == XSTYPE_DICT) { | 278 | if (xs_type(d) == XSTYPE_DICT) { |
| 275 | if (xs_type(mtype = xs_dict_get(d, "mediaType")) == XSTYPE_STRING && | 279 | if (xs_type(mtype = xs_dict_get(d, "mediaType")) == XSTYPE_STRING && |
| 276 | xs_startswith(mtype, "video/")) { | 280 | xs_startswith(mtype, "video/")) { |
| 277 | char *h = xs_dict_get(d, "href"); | 281 | const char *h = xs_dict_get(d, "href"); |
| 278 | 282 | ||
| 279 | /* this is probably it */ | 283 | /* this is probably it */ |
| 280 | if (xs_type(h) == XSTYPE_STRING) { | 284 | if (xs_type(h) == XSTYPE_STRING) { |
| @@ -303,7 +307,7 @@ xs_list *get_attachments(const xs_dict *msg) | |||
| 303 | } | 307 | } |
| 304 | 308 | ||
| 305 | 309 | ||
| 306 | int timeline_request(snac *snac, char **id, xs_str **wrk, int level) | 310 | int timeline_request(snac *snac, const char **id, xs_str **wrk, int level) |
| 307 | /* ensures that an entry and its ancestors are in the timeline */ | 311 | /* ensures that an entry and its ancestors are in the timeline */ |
| 308 | { | 312 | { |
| 309 | int status = 0; | 313 | int status = 0; |
| @@ -323,7 +327,7 @@ int timeline_request(snac *snac, char **id, xs_str **wrk, int level) | |||
| 323 | status = activitypub_request(snac, *id, &msg); | 327 | status = activitypub_request(snac, *id, &msg); |
| 324 | 328 | ||
| 325 | if (valid_status(status)) { | 329 | if (valid_status(status)) { |
| 326 | xs_dict *object = msg; | 330 | const xs_dict *object = msg; |
| 327 | const char *type = xs_dict_get(object, "type"); | 331 | const char *type = xs_dict_get(object, "type"); |
| 328 | 332 | ||
| 329 | /* get the id again from the object, as it may be different */ | 333 | /* get the id again from the object, as it may be different */ |
| @@ -355,102 +359,35 @@ int timeline_request(snac *snac, char **id, xs_str **wrk, int level) | |||
| 355 | type = "(null)"; | 359 | type = "(null)"; |
| 356 | } | 360 | } |
| 357 | 361 | ||
| 358 | if (xs_match(type, "Note|Page|Article|Video")) { | 362 | if (xs_match(type, POSTLIKE_OBJECT_TYPE)) { |
| 359 | const char *actor = get_atto(object); | 363 | if (content_match("filter_reject.txt", object)) |
| 360 | |||
| 361 | if (content_check("filter_reject.txt", object)) | ||
| 362 | snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid)); | 364 | snac_log(snac, xs_fmt("timeline_request rejected by content %s", nid)); |
| 363 | else { | 365 | else { |
| 364 | /* request (and drop) the actor for this entry */ | 366 | const char *actor = get_atto(object); |
| 365 | if (!xs_is_null(actor)) | ||
| 366 | actor_request(snac, actor, NULL); | ||
| 367 | |||
| 368 | /* does it have an ancestor? */ | ||
| 369 | char *in_reply_to = xs_dict_get(object, "inReplyTo"); | ||
| 370 | |||
| 371 | /* store */ | ||
| 372 | timeline_add(snac, nid, object); | ||
| 373 | |||
| 374 | /* recurse! */ | ||
| 375 | timeline_request(snac, &in_reply_to, NULL, level + 1); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | } | ||
| 379 | } | ||
| 380 | |||
| 381 | enqueue_request_replies(snac, *id); | ||
| 382 | } | ||
| 383 | |||
| 384 | return status; | ||
| 385 | } | ||
| 386 | 367 | ||
| 368 | if (!xs_is_null(actor)) { | ||
| 369 | /* request (and drop) the actor for this entry */ | ||
| 370 | if (!valid_status(actor_request(snac, actor, NULL))) { | ||
| 371 | /* failed? retry later */ | ||
| 372 | enqueue_actor_refresh(snac, actor, 60); | ||
| 373 | } | ||
| 387 | 374 | ||
| 388 | void timeline_request_replies(snac *user, const char *id) | 375 | /* does it have an ancestor? */ |
| 389 | /* requests all replies of a message */ | 376 | const char *in_reply_to = xs_dict_get(object, "inReplyTo"); |
| 390 | /* FIXME: experimental -- needs more testing */ | ||
| 391 | { | ||
| 392 | /* FIXME: TEMPORARILY DISABLED */ | ||
| 393 | /* Reason: I've found that many of the posts in the 'replies' Collection | ||
| 394 | do not have an inReplyTo field (why??? aren't they 'replies'???). | ||
| 395 | For this reason, these requested objects are not stored as children | ||
| 396 | of the original post and they are shown as out-of-context, top level posts. | ||
| 397 | This process is disabled until I find an elegant way of providing a parent | ||
| 398 | for these 'stray' children. */ | ||
| 399 | return; | ||
| 400 | |||
| 401 | xs *msg = NULL; | ||
| 402 | |||
| 403 | if (!valid_status(object_get(id, &msg))) | ||
| 404 | return; | ||
| 405 | |||
| 406 | /* does it have a replies collection? */ | ||
| 407 | const xs_dict *replies = xs_dict_get(msg, "replies"); | ||
| 408 | |||
| 409 | if (!xs_is_null(replies)) { | ||
| 410 | const char *type = xs_dict_get(replies, "type"); | ||
| 411 | const char *first = xs_dict_get(replies, "first"); | ||
| 412 | |||
| 413 | if (!xs_is_null(type) && !xs_is_null(first) && strcmp(type, "Collection") == 0) { | ||
| 414 | const char *next = xs_dict_get(first, "next"); | ||
| 415 | |||
| 416 | if (!xs_is_null(next)) { | ||
| 417 | xs *rpls = NULL; | ||
| 418 | int status = activitypub_request(user, next, &rpls); | ||
| 419 | |||
| 420 | /* request the Collection of replies */ | ||
| 421 | if (valid_status(status)) { | ||
| 422 | xs_list *items = xs_dict_get(rpls, "items"); | ||
| 423 | |||
| 424 | if (xs_type(items) == XSTYPE_LIST) { | ||
| 425 | xs_val *v; | ||
| 426 | |||
| 427 | /* request them all */ | ||
| 428 | while (xs_list_iter(&items, &v)) { | ||
| 429 | if (xs_type(v) == XSTYPE_DICT) { | ||
| 430 | /* not an id, but the object itself (!) */ | ||
| 431 | const char *c_id = xs_dict_get(v, "id"); | ||
| 432 | |||
| 433 | if (!xs_is_null(id)) { | ||
| 434 | snac_debug(user, 0, xs_fmt("embedded reply %s", c_id)); | ||
| 435 | 377 | ||
| 436 | object_add(c_id, v); | 378 | /* store */ |
| 379 | timeline_add(snac, nid, object); | ||
| 437 | 380 | ||
| 438 | /* get its own children */ | 381 | /* recurse! */ |
| 439 | timeline_request_replies(user, v); | 382 | timeline_request(snac, &in_reply_to, NULL, level + 1); |
| 440 | } | ||
| 441 | } | ||
| 442 | else { | ||
| 443 | snac_debug(user, 0, xs_fmt("request reply %s", v)); | ||
| 444 | timeline_request(user, &v, NULL, 0); | ||
| 445 | } | ||
| 446 | } | 383 | } |
| 447 | } | 384 | } |
| 448 | } | 385 | } |
| 449 | else | ||
| 450 | snac_debug(user, 0, xs_fmt("replies request error %s %d", next, status)); | ||
| 451 | } | 386 | } |
| 452 | } | 387 | } |
| 453 | } | 388 | } |
| 389 | |||
| 390 | return status; | ||
| 454 | } | 391 | } |
| 455 | 392 | ||
| 456 | 393 | ||
| @@ -476,7 +413,7 @@ int send_to_inbox(snac *snac, const xs_str *inbox, const xs_dict *msg, | |||
| 476 | xs_val **payload, int *p_size, int timeout) | 413 | xs_val **payload, int *p_size, int timeout) |
| 477 | /* sends a message to an Inbox */ | 414 | /* sends a message to an Inbox */ |
| 478 | { | 415 | { |
| 479 | char *seckey = xs_dict_get(snac->key, "secret"); | 416 | const char *seckey = xs_dict_get(snac->key, "secret"); |
| 480 | 417 | ||
| 481 | return send_to_inbox_raw(snac->actor, seckey, inbox, msg, payload, p_size, timeout); | 418 | return send_to_inbox_raw(snac->actor, seckey, inbox, msg, payload, p_size, timeout); |
| 482 | } | 419 | } |
| @@ -486,7 +423,7 @@ xs_str *get_actor_inbox(const char *actor) | |||
| 486 | /* gets an actor's inbox */ | 423 | /* gets an actor's inbox */ |
| 487 | { | 424 | { |
| 488 | xs *data = NULL; | 425 | xs *data = NULL; |
| 489 | char *v = NULL; | 426 | const char *v = NULL; |
| 490 | 427 | ||
| 491 | if (valid_status(actor_request(NULL, actor, &data))) { | 428 | if (valid_status(actor_request(NULL, actor, &data))) { |
| 492 | /* try first endpoints/sharedInbox */ | 429 | /* try first endpoints/sharedInbox */ |
| @@ -535,17 +472,17 @@ void post_message(snac *snac, const char *actor, const xs_dict *msg) | |||
| 535 | xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public) | 472 | xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public) |
| 536 | /* returns the list of recipients for a message */ | 473 | /* returns the list of recipients for a message */ |
| 537 | { | 474 | { |
| 538 | char *to = xs_dict_get(msg, "to"); | 475 | const xs_val *to = xs_dict_get(msg, "to"); |
| 539 | char *cc = xs_dict_get(msg, "cc"); | 476 | const xs_val *cc = xs_dict_get(msg, "cc"); |
| 540 | xs_set rcpts; | 477 | xs_set rcpts; |
| 541 | int n; | 478 | int n; |
| 542 | 479 | ||
| 543 | xs_set_init(&rcpts); | 480 | xs_set_init(&rcpts); |
| 544 | 481 | ||
| 545 | char *lists[] = { to, cc, NULL }; | 482 | const xs_list *lists[] = { to, cc, NULL }; |
| 546 | for (n = 0; lists[n]; n++) { | 483 | for (n = 0; lists[n]; n++) { |
| 547 | char *l = lists[n]; | 484 | xs_list *l = (xs_list *)lists[n]; |
| 548 | char *v; | 485 | const char *v; |
| 549 | xs *tl = NULL; | 486 | xs *tl = NULL; |
| 550 | 487 | ||
| 551 | /* if it's a string, create a list with only one element */ | 488 | /* if it's a string, create a list with only one element */ |
| @@ -560,7 +497,7 @@ xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public) | |||
| 560 | if (expand_public && strcmp(v, public_address) == 0) { | 497 | if (expand_public && strcmp(v, public_address) == 0) { |
| 561 | /* iterate the followers and add them */ | 498 | /* iterate the followers and add them */ |
| 562 | xs *fwers = follower_list(snac); | 499 | xs *fwers = follower_list(snac); |
| 563 | char *actor; | 500 | const char *actor; |
| 564 | 501 | ||
| 565 | char *p = fwers; | 502 | char *p = fwers; |
| 566 | while (xs_list_iter(&p, &actor)) | 503 | while (xs_list_iter(&p, &actor)) |
| @@ -667,13 +604,13 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 667 | 604 | ||
| 668 | /* if it's a Follow, it must be explicitly for us */ | 605 | /* if it's a Follow, it must be explicitly for us */ |
| 669 | if (xs_match(type, "Follow")) { | 606 | if (xs_match(type, "Follow")) { |
| 670 | char *object = xs_dict_get(c_msg, "object"); | 607 | const char *object = xs_dict_get(c_msg, "object"); |
| 671 | return !xs_is_null(object) && strcmp(snac->actor, object) == 0; | 608 | return !xs_is_null(object) && strcmp(snac->actor, object) == 0; |
| 672 | } | 609 | } |
| 673 | 610 | ||
| 674 | /* only accept Ping directed to us */ | 611 | /* only accept Ping directed to us */ |
| 675 | if (xs_match(type, "Ping")) { | 612 | if (xs_match(type, "Ping")) { |
| 676 | char *dest = xs_dict_get(c_msg, "to"); | 613 | const char *dest = xs_dict_get(c_msg, "to"); |
| 677 | return !xs_is_null(dest) && strcmp(snac->actor, dest) == 0; | 614 | return !xs_is_null(dest) && strcmp(snac->actor, dest) == 0; |
| 678 | } | 615 | } |
| 679 | 616 | ||
| @@ -688,10 +625,10 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 688 | if (pub_msg && following_check(snac, actor)) | 625 | if (pub_msg && following_check(snac, actor)) |
| 689 | return 1; | 626 | return 1; |
| 690 | 627 | ||
| 691 | xs_dict *msg = xs_dict_get(c_msg, "object"); | 628 | const xs_dict *msg = xs_dict_get(c_msg, "object"); |
| 692 | xs *rcpts = recipient_list(snac, msg, 0); | 629 | xs *rcpts = recipient_list(snac, msg, 0); |
| 693 | xs_list *p = rcpts; | 630 | xs_list *p = rcpts; |
| 694 | xs_str *v; | 631 | const xs_str *v; |
| 695 | 632 | ||
| 696 | xs *actor_followers = NULL; | 633 | xs *actor_followers = NULL; |
| 697 | 634 | ||
| @@ -700,8 +637,9 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 700 | xs *actor_obj = NULL; | 637 | xs *actor_obj = NULL; |
| 701 | 638 | ||
| 702 | if (valid_status(object_get(actor, &actor_obj))) { | 639 | if (valid_status(object_get(actor, &actor_obj))) { |
| 703 | if ((v = xs_dict_get(actor_obj, "followers"))) | 640 | const xs_val *fw = xs_dict_get(actor_obj, "followers"); |
| 704 | actor_followers = xs_dup(v); | 641 | if (fw) |
| 642 | actor_followers = xs_dup(fw); | ||
| 705 | } | 643 | } |
| 706 | } | 644 | } |
| 707 | 645 | ||
| @@ -724,13 +662,13 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 724 | } | 662 | } |
| 725 | 663 | ||
| 726 | /* accept if it's by someone we follow */ | 664 | /* accept if it's by someone we follow */ |
| 727 | char *atto = get_atto(msg); | 665 | const char *atto = get_atto(msg); |
| 728 | 666 | ||
| 729 | if (pub_msg && !xs_is_null(atto) && following_check(snac, atto)) | 667 | if (pub_msg && !xs_is_null(atto) && following_check(snac, atto)) |
| 730 | return 3; | 668 | return 3; |
| 731 | 669 | ||
| 732 | /* is this message a reply to another? */ | 670 | /* is this message a reply to another? */ |
| 733 | char *irt = xs_dict_get(msg, "inReplyTo"); | 671 | const char *irt = xs_dict_get(msg, "inReplyTo"); |
| 734 | if (!xs_is_null(irt)) { | 672 | if (!xs_is_null(irt)) { |
| 735 | xs *r_msg = NULL; | 673 | xs *r_msg = NULL; |
| 736 | 674 | ||
| @@ -755,7 +693,7 @@ xs_str *process_tags(snac *snac, const char *content, xs_list **tag) | |||
| 755 | xs_list *tl = *tag; | 693 | xs_list *tl = *tag; |
| 756 | xs *split; | 694 | xs *split; |
| 757 | xs_list *p; | 695 | xs_list *p; |
| 758 | xs_val *v; | 696 | const xs_val *v; |
| 759 | int n = 0; | 697 | int n = 0; |
| 760 | 698 | ||
| 761 | /* create a default server for incomplete mentions */ | 699 | /* create a default server for incomplete mentions */ |
| @@ -983,8 +921,8 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor, | |||
| 983 | 921 | ||
| 984 | /* telegram */ | 922 | /* telegram */ |
| 985 | 923 | ||
| 986 | char *bot = xs_dict_get(snac->config, "telegram_bot"); | 924 | const char *bot = xs_dict_get(snac->config, "telegram_bot"); |
| 987 | char *chat_id = xs_dict_get(snac->config, "telegram_chat_id"); | 925 | const char *chat_id = xs_dict_get(snac->config, "telegram_chat_id"); |
| 988 | 926 | ||
| 989 | if (!xs_is_null(bot) && !xs_is_null(chat_id) && *bot && *chat_id) | 927 | if (!xs_is_null(bot) && !xs_is_null(chat_id) && *bot && *chat_id) |
| 990 | enqueue_telegram(body, bot, chat_id); | 928 | enqueue_telegram(body, bot, chat_id); |
| @@ -997,8 +935,8 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor, | |||
| 997 | objid = actor; | 935 | objid = actor; |
| 998 | 936 | ||
| 999 | /* ntfy */ | 937 | /* ntfy */ |
| 1000 | char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); | 938 | const char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); |
| 1001 | char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); | 939 | const char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); |
| 1002 | 940 | ||
| 1003 | if (!xs_is_null(ntfy_server) && *ntfy_server) | 941 | if (!xs_is_null(ntfy_server) && *ntfy_server) |
| 1004 | enqueue_ntfy(body, ntfy_server, ntfy_token); | 942 | enqueue_ntfy(body, ntfy_server, ntfy_token); |
| @@ -1084,7 +1022,7 @@ xs_dict *msg_base(snac *snac, const char *type, const char *id, | |||
| 1084 | } | 1022 | } |
| 1085 | 1023 | ||
| 1086 | 1024 | ||
| 1087 | xs_dict *msg_collection(snac *snac, char *id) | 1025 | xs_dict *msg_collection(snac *snac, const char *id) |
| 1088 | /* creates an empty OrderedCollection message */ | 1026 | /* creates an empty OrderedCollection message */ |
| 1089 | { | 1027 | { |
| 1090 | xs_dict *msg = msg_base(snac, "OrderedCollection", id, NULL, NULL, NULL); | 1028 | xs_dict *msg = msg_base(snac, "OrderedCollection", id, NULL, NULL, NULL); |
| @@ -1098,7 +1036,7 @@ xs_dict *msg_collection(snac *snac, char *id) | |||
| 1098 | } | 1036 | } |
| 1099 | 1037 | ||
| 1100 | 1038 | ||
| 1101 | xs_dict *msg_accept(snac *snac, char *object, char *to) | 1039 | xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to) |
| 1102 | /* creates an Accept message (as a response to a Follow) */ | 1040 | /* creates an Accept message (as a response to a Follow) */ |
| 1103 | { | 1041 | { |
| 1104 | xs_dict *msg = msg_base(snac, "Accept", "@dummy", snac->actor, NULL, object); | 1042 | xs_dict *msg = msg_base(snac, "Accept", "@dummy", snac->actor, NULL, object); |
| @@ -1109,12 +1047,12 @@ xs_dict *msg_accept(snac *snac, char *object, char *to) | |||
| 1109 | } | 1047 | } |
| 1110 | 1048 | ||
| 1111 | 1049 | ||
| 1112 | xs_dict *msg_update(snac *snac, xs_dict *object) | 1050 | xs_dict *msg_update(snac *snac, const xs_dict *object) |
| 1113 | /* creates an Update message */ | 1051 | /* creates an Update message */ |
| 1114 | { | 1052 | { |
| 1115 | xs_dict *msg = msg_base(snac, "Update", "@object", snac->actor, "@now", object); | 1053 | xs_dict *msg = msg_base(snac, "Update", "@object", snac->actor, "@now", object); |
| 1116 | 1054 | ||
| 1117 | char *type = xs_dict_get(object, "type"); | 1055 | const char *type = xs_dict_get(object, "type"); |
| 1118 | 1056 | ||
| 1119 | if (strcmp(type, "Note") == 0) { | 1057 | if (strcmp(type, "Note") == 0) { |
| 1120 | msg = xs_dict_append(msg, "to", xs_dict_get(object, "to")); | 1058 | msg = xs_dict_append(msg, "to", xs_dict_get(object, "to")); |
| @@ -1137,7 +1075,7 @@ xs_dict *msg_update(snac *snac, xs_dict *object) | |||
| 1137 | } | 1075 | } |
| 1138 | 1076 | ||
| 1139 | 1077 | ||
| 1140 | xs_dict *msg_admiration(snac *snac, char *object, char *type) | 1078 | xs_dict *msg_admiration(snac *snac, const char *object, const char *type) |
| 1141 | /* creates a Like or Announce message */ | 1079 | /* creates a Like or Announce message */ |
| 1142 | { | 1080 | { |
| 1143 | xs *a_msg = NULL; | 1081 | xs *a_msg = NULL; |
| @@ -1168,7 +1106,7 @@ xs_dict *msg_admiration(snac *snac, char *object, char *type) | |||
| 1168 | } | 1106 | } |
| 1169 | 1107 | ||
| 1170 | 1108 | ||
| 1171 | xs_dict *msg_repulsion(snac *user, char *id, char *type) | 1109 | xs_dict *msg_repulsion(snac *user, const char *id, const char *type) |
| 1172 | /* creates an Undo + admiration message */ | 1110 | /* creates an Undo + admiration message */ |
| 1173 | { | 1111 | { |
| 1174 | xs *a_msg = NULL; | 1112 | xs *a_msg = NULL; |
| @@ -1206,7 +1144,7 @@ xs_dict *msg_actor(snac *snac) | |||
| 1206 | xs *kid = NULL; | 1144 | xs *kid = NULL; |
| 1207 | xs *f_bio = NULL; | 1145 | xs *f_bio = NULL; |
| 1208 | xs_dict *msg = msg_base(snac, "Person", snac->actor, NULL, NULL, NULL); | 1146 | xs_dict *msg = msg_base(snac, "Person", snac->actor, NULL, NULL, NULL); |
| 1209 | char *p; | 1147 | const char *p; |
| 1210 | int n; | 1148 | int n; |
| 1211 | 1149 | ||
| 1212 | /* change the @context (is this really necessary?) */ | 1150 | /* change the @context (is this really necessary?) */ |
| @@ -1264,11 +1202,11 @@ xs_dict *msg_actor(snac *snac) | |||
| 1264 | } | 1202 | } |
| 1265 | 1203 | ||
| 1266 | /* add the metadata as attachments of PropertyValue */ | 1204 | /* add the metadata as attachments of PropertyValue */ |
| 1267 | xs_dict *metadata = xs_dict_get(snac->config, "metadata"); | 1205 | const xs_dict *metadata = xs_dict_get(snac->config, "metadata"); |
| 1268 | if (xs_type(metadata) == XSTYPE_DICT) { | 1206 | if (xs_type(metadata) == XSTYPE_DICT) { |
| 1269 | xs *attach = xs_list_new(); | 1207 | xs *attach = xs_list_new(); |
| 1270 | xs_str *k; | 1208 | const xs_str *k; |
| 1271 | xs_str *v; | 1209 | const xs_str *v; |
| 1272 | 1210 | ||
| 1273 | int c = 0; | 1211 | int c = 0; |
| 1274 | while (xs_dict_next(metadata, &k, &v, &c)) { | 1212 | while (xs_dict_next(metadata, &k, &v, &c)) { |
| @@ -1277,7 +1215,7 @@ xs_dict *msg_actor(snac *snac) | |||
| 1277 | xs *k2 = encode_html(k); | 1215 | xs *k2 = encode_html(k); |
| 1278 | xs *v2 = NULL; | 1216 | xs *v2 = NULL; |
| 1279 | 1217 | ||
| 1280 | if (xs_startswith(v, "https:")) { | 1218 | if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/")) { |
| 1281 | xs *t = encode_html(v); | 1219 | xs *t = encode_html(v); |
| 1282 | v2 = xs_fmt("<a href=\"%s\" rel=\"me\">%s</a>", t, t); | 1220 | v2 = xs_fmt("<a href=\"%s\" rel=\"me\">%s</a>", t, t); |
| 1283 | } | 1221 | } |
| @@ -1310,7 +1248,7 @@ xs_dict *msg_create(snac *snac, const xs_dict *object) | |||
| 1310 | /* creates a 'Create' message */ | 1248 | /* creates a 'Create' message */ |
| 1311 | { | 1249 | { |
| 1312 | xs_dict *msg = msg_base(snac, "Create", "@wrapper", snac->actor, NULL, object); | 1250 | xs_dict *msg = msg_base(snac, "Create", "@wrapper", snac->actor, NULL, object); |
| 1313 | xs_val *v; | 1251 | const xs_val *v; |
| 1314 | 1252 | ||
| 1315 | if ((v = get_atto(object))) | 1253 | if ((v = get_atto(object))) |
| 1316 | msg = xs_dict_append(msg, "attributedTo", v); | 1254 | msg = xs_dict_append(msg, "attributedTo", v); |
| @@ -1327,7 +1265,7 @@ xs_dict *msg_create(snac *snac, const xs_dict *object) | |||
| 1327 | } | 1265 | } |
| 1328 | 1266 | ||
| 1329 | 1267 | ||
| 1330 | xs_dict *msg_undo(snac *snac, char *object) | 1268 | xs_dict *msg_undo(snac *snac, const xs_val *object) |
| 1331 | /* creates an 'Undo' message */ | 1269 | /* creates an 'Undo' message */ |
| 1332 | { | 1270 | { |
| 1333 | xs_dict *msg = msg_base(snac, "Undo", "@object", snac->actor, "@now", object); | 1271 | xs_dict *msg = msg_base(snac, "Undo", "@object", snac->actor, "@now", object); |
| @@ -1340,7 +1278,7 @@ xs_dict *msg_undo(snac *snac, char *object) | |||
| 1340 | } | 1278 | } |
| 1341 | 1279 | ||
| 1342 | 1280 | ||
| 1343 | xs_dict *msg_delete(snac *snac, char *id) | 1281 | xs_dict *msg_delete(snac *snac, const char *id) |
| 1344 | /* creates a 'Delete' + 'Tombstone' for a local entry */ | 1282 | /* creates a 'Delete' + 'Tombstone' for a local entry */ |
| 1345 | { | 1283 | { |
| 1346 | xs *tomb = xs_dict_new(); | 1284 | xs *tomb = xs_dict_new(); |
| @@ -1369,7 +1307,7 @@ xs_dict *msg_follow(snac *snac, const char *q) | |||
| 1369 | 1307 | ||
| 1370 | xs *url_or_uid = xs_strip_i(xs_str_new(q)); | 1308 | xs *url_or_uid = xs_strip_i(xs_str_new(q)); |
| 1371 | 1309 | ||
| 1372 | if (xs_startswith(url_or_uid, "https:/")) | 1310 | if (xs_startswith(url_or_uid, "https:/") || xs_startswith(url_or_uid, "http:/")) |
| 1373 | actor = xs_dup(url_or_uid); | 1311 | actor = xs_dup(url_or_uid); |
| 1374 | else | 1312 | else |
| 1375 | if (!valid_status(webfinger_request(url_or_uid, &actor, NULL)) || actor == NULL) { | 1313 | if (!valid_status(webfinger_request(url_or_uid, &actor, NULL)) || actor == NULL) { |
| @@ -1382,7 +1320,7 @@ xs_dict *msg_follow(snac *snac, const char *q) | |||
| 1382 | 1320 | ||
| 1383 | if (valid_status(status)) { | 1321 | if (valid_status(status)) { |
| 1384 | /* check if the actor is an alias */ | 1322 | /* check if the actor is an alias */ |
| 1385 | char *r_actor = xs_dict_get(actor_o, "id"); | 1323 | const char *r_actor = xs_dict_get(actor_o, "id"); |
| 1386 | 1324 | ||
| 1387 | if (r_actor && strcmp(actor, r_actor) != 0) { | 1325 | if (r_actor && strcmp(actor, r_actor) != 0) { |
| 1388 | snac_log(snac, xs_fmt("actor to follow is an alias %s -> %s", actor, r_actor)); | 1326 | snac_log(snac, xs_fmt("actor to follow is an alias %s -> %s", actor, r_actor)); |
| @@ -1398,7 +1336,7 @@ xs_dict *msg_follow(snac *snac, const char *q) | |||
| 1398 | 1336 | ||
| 1399 | 1337 | ||
| 1400 | xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | 1338 | xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, |
| 1401 | xs_str *in_reply_to, xs_list *attach, int priv) | 1339 | const xs_str *in_reply_to, const xs_list *attach, int priv) |
| 1402 | /* creates a 'Note' message */ | 1340 | /* creates a 'Note' message */ |
| 1403 | { | 1341 | { |
| 1404 | xs *ntid = tid(0); | 1342 | xs *ntid = tid(0); |
| @@ -1413,7 +1351,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1413 | xs *atls = xs_list_new(); | 1351 | xs *atls = xs_list_new(); |
| 1414 | xs_dict *msg = msg_base(snac, "Note", id, NULL, "@now", NULL); | 1352 | xs_dict *msg = msg_base(snac, "Note", id, NULL, "@now", NULL); |
| 1415 | xs_list *p; | 1353 | xs_list *p; |
| 1416 | xs_val *v; | 1354 | const xs_val *v; |
| 1417 | 1355 | ||
| 1418 | if (rcpts == NULL) | 1356 | if (rcpts == NULL) |
| 1419 | to = xs_list_new(); | 1357 | to = xs_list_new(); |
| @@ -1438,7 +1376,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1438 | 1376 | ||
| 1439 | if (valid_status(object_get(in_reply_to, &p_msg))) { | 1377 | if (valid_status(object_get(in_reply_to, &p_msg))) { |
| 1440 | /* add this author as recipient */ | 1378 | /* add this author as recipient */ |
| 1441 | char *a, *v; | 1379 | const char *a, *v; |
| 1442 | 1380 | ||
| 1443 | if ((a = get_atto(p_msg)) && xs_list_in(to, a) == -1) | 1381 | if ((a = get_atto(p_msg)) && xs_list_in(to, a) == -1) |
| 1444 | to = xs_list_append(to, a); | 1382 | to = xs_list_append(to, a); |
| @@ -1449,7 +1387,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1449 | xs *actor_o = NULL; | 1387 | xs *actor_o = NULL; |
| 1450 | 1388 | ||
| 1451 | if (xs_list_len(l) > 3 && valid_status(object_get(a, &actor_o))) { | 1389 | if (xs_list_len(l) > 3 && valid_status(object_get(a, &actor_o))) { |
| 1452 | char *uname = xs_dict_get(actor_o, "preferredUsername"); | 1390 | const char *uname = xs_dict_get(actor_o, "preferredUsername"); |
| 1453 | 1391 | ||
| 1454 | if (!xs_is_null(uname) && *uname) { | 1392 | if (!xs_is_null(uname) && *uname) { |
| 1455 | xs *handle = xs_fmt("@%s@%s", uname, xs_list_get(l, 2)); | 1393 | xs *handle = xs_fmt("@%s@%s", uname, xs_list_get(l, 2)); |
| @@ -1488,7 +1426,8 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1488 | 1426 | ||
| 1489 | /* create the attachment list, if there are any */ | 1427 | /* create the attachment list, if there are any */ |
| 1490 | if (!xs_is_null(attach)) { | 1428 | if (!xs_is_null(attach)) { |
| 1491 | while (xs_list_iter(&attach, &v)) { | 1429 | int c = 0; |
| 1430 | while (xs_list_next(attach, &v, &c)) { | ||
| 1492 | xs *d = xs_dict_new(); | 1431 | xs *d = xs_dict_new(); |
| 1493 | const char *url = xs_list_get(v, 0); | 1432 | const char *url = xs_list_get(v, 0); |
| 1494 | const char *alt = xs_list_get(v, 1); | 1433 | const char *alt = xs_list_get(v, 1); |
| @@ -1511,7 +1450,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1511 | p = tag; | 1450 | p = tag; |
| 1512 | while (xs_list_iter(&p, &v)) { | 1451 | while (xs_list_iter(&p, &v)) { |
| 1513 | if (xs_type(v) == XSTYPE_DICT) { | 1452 | if (xs_type(v) == XSTYPE_DICT) { |
| 1514 | char *t; | 1453 | const char *t; |
| 1515 | 1454 | ||
| 1516 | if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { | 1455 | if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { |
| 1517 | if (!xs_is_null(t = xs_dict_get(v, "href"))) | 1456 | if (!xs_is_null(t = xs_dict_get(v, "href"))) |
| @@ -1589,7 +1528,7 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | |||
| 1589 | 1528 | ||
| 1590 | xs *o = xs_list_new(); | 1529 | xs *o = xs_list_new(); |
| 1591 | xs_list *p = (xs_list *)opts; | 1530 | xs_list *p = (xs_list *)opts; |
| 1592 | xs_str *v; | 1531 | const xs_str *v; |
| 1593 | xs *replies = xs_json_loads("{\"type\":\"Collection\",\"totalItems\":0}"); | 1532 | xs *replies = xs_json_loads("{\"type\":\"Collection\",\"totalItems\":0}"); |
| 1594 | 1533 | ||
| 1595 | xs_set_init(&seen); | 1534 | xs_set_init(&seen); |
| @@ -1635,9 +1574,9 @@ int update_question(snac *user, const char *id) | |||
| 1635 | xs *msg = NULL; | 1574 | xs *msg = NULL; |
| 1636 | xs *rcnt = xs_dict_new(); | 1575 | xs *rcnt = xs_dict_new(); |
| 1637 | xs *lopts = xs_list_new(); | 1576 | xs *lopts = xs_list_new(); |
| 1638 | xs_list *opts; | 1577 | const xs_list *opts; |
| 1639 | xs_list *p; | 1578 | xs_list *p; |
| 1640 | xs_val *v; | 1579 | const xs_val *v; |
| 1641 | 1580 | ||
| 1642 | /* get the object */ | 1581 | /* get the object */ |
| 1643 | if (!valid_status(object_get(id, &msg))) | 1582 | if (!valid_status(object_get(id, &msg))) |
| @@ -1653,8 +1592,8 @@ int update_question(snac *user, const char *id) | |||
| 1653 | return -3; | 1592 | return -3; |
| 1654 | 1593 | ||
| 1655 | /* fill the initial count */ | 1594 | /* fill the initial count */ |
| 1656 | p = opts; | 1595 | int c = 0; |
| 1657 | while (xs_list_iter(&p, &v)) { | 1596 | while (xs_list_next(opts, &v, &c)) { |
| 1658 | const char *name = xs_dict_get(v, "name"); | 1597 | const char *name = xs_dict_get(v, "name"); |
| 1659 | if (name) { | 1598 | if (name) { |
| 1660 | lopts = xs_list_append(lopts, name); | 1599 | lopts = xs_list_append(lopts, name); |
| @@ -1760,13 +1699,13 @@ int update_question(snac *user, const char *id) | |||
| 1760 | 1699 | ||
| 1761 | /** queues **/ | 1700 | /** queues **/ |
| 1762 | 1701 | ||
| 1763 | int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | 1702 | int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) |
| 1764 | /* processes an ActivityPub message from the input queue */ | 1703 | /* processes an ActivityPub message from the input queue */ |
| 1765 | /* return values: -1, fatal error; 0, transient error, retry; | 1704 | /* return values: -1, fatal error; 0, transient error, retry; |
| 1766 | 1, processed and done; 2, propagate to users (only when no user is set) */ | 1705 | 1, processed and done; 2, propagate to users (only when no user is set) */ |
| 1767 | { | 1706 | { |
| 1768 | char *actor = xs_dict_get(msg, "actor"); | 1707 | const char *actor = xs_dict_get(msg, "actor"); |
| 1769 | char *type = xs_dict_get(msg, "type"); | 1708 | const char *type = xs_dict_get(msg, "type"); |
| 1770 | xs *actor_o = NULL; | 1709 | xs *actor_o = NULL; |
| 1771 | int a_status; | 1710 | int a_status; |
| 1772 | int do_notify = 0; | 1711 | int do_notify = 0; |
| @@ -1786,7 +1725,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1786 | return -1; | 1725 | return -1; |
| 1787 | } | 1726 | } |
| 1788 | 1727 | ||
| 1789 | char *object, *utype; | 1728 | const char *object, *utype; |
| 1790 | 1729 | ||
| 1791 | object = xs_dict_get(msg, "object"); | 1730 | object = xs_dict_get(msg, "object"); |
| 1792 | if (object != NULL && xs_type(object) == XSTYPE_DICT) | 1731 | if (object != NULL && xs_type(object) == XSTYPE_DICT) |
| @@ -1809,7 +1748,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1809 | } | 1748 | } |
| 1810 | 1749 | ||
| 1811 | /* also discard if the object to be deleted is not here */ | 1750 | /* also discard if the object to be deleted is not here */ |
| 1812 | char *obj_id = object; | 1751 | const char *obj_id = object; |
| 1813 | if (xs_type(obj_id) == XSTYPE_DICT) | 1752 | if (xs_type(obj_id) == XSTYPE_DICT) |
| 1814 | obj_id = xs_dict_get(obj_id, "id"); | 1753 | obj_id = xs_dict_get(obj_id, "id"); |
| 1815 | 1754 | ||
| @@ -1881,7 +1820,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1881 | int min_account_age = xs_number_get(xs_dict_get(srv_config, "min_account_age")); | 1820 | int min_account_age = xs_number_get(xs_dict_get(srv_config, "min_account_age")); |
| 1882 | 1821 | ||
| 1883 | if (min_account_age > 0) { | 1822 | if (min_account_age > 0) { |
| 1884 | char *actor_date = xs_dict_get(actor_o, "published"); | 1823 | const char *actor_date = xs_dict_get(actor_o, "published"); |
| 1885 | if (!xs_is_null(actor_date)) { | 1824 | if (!xs_is_null(actor_date)) { |
| 1886 | time_t actor_t = xs_parse_iso_date(actor_date, 0); | 1825 | time_t actor_t = xs_parse_iso_date(actor_date, 0); |
| 1887 | 1826 | ||
| @@ -1941,16 +1880,39 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1941 | } | 1880 | } |
| 1942 | else | 1881 | else |
| 1943 | if (strcmp(type, "Undo") == 0) { /** **/ | 1882 | if (strcmp(type, "Undo") == 0) { /** **/ |
| 1883 | const char *id = xs_dict_get(object, "object"); | ||
| 1884 | |||
| 1944 | if (xs_type(object) != XSTYPE_DICT) | 1885 | if (xs_type(object) != XSTYPE_DICT) |
| 1945 | utype = "Follow"; | 1886 | utype = "Follow"; |
| 1946 | 1887 | ||
| 1947 | if (strcmp(utype, "Follow") == 0) { /** **/ | 1888 | if (strcmp(utype, "Follow") == 0) { /** **/ |
| 1948 | if (valid_status(follower_del(snac, actor))) { | 1889 | if (id && strcmp(id, snac->actor) != 0) |
| 1949 | snac_log(snac, xs_fmt("no longer following us %s", actor)); | 1890 | snac_debug(snac, 1, xs_fmt("Undo + Follow from %s not for us (%s)", actor, id)); |
| 1950 | do_notify = 1; | 1891 | else { |
| 1892 | if (valid_status(follower_del(snac, actor))) { | ||
| 1893 | snac_log(snac, xs_fmt("no longer following us %s", actor)); | ||
| 1894 | do_notify = 1; | ||
| 1895 | } | ||
| 1896 | else | ||
| 1897 | snac_log(snac, xs_fmt("error deleting follower %s", actor)); | ||
| 1951 | } | 1898 | } |
| 1952 | else | 1899 | } |
| 1953 | snac_log(snac, xs_fmt("error deleting follower %s", actor)); | 1900 | else |
| 1901 | if (strcmp(utype, "Like") == 0) { /** **/ | ||
| 1902 | int status = object_unadmire(id, actor, 1); | ||
| 1903 | |||
| 1904 | snac_log(snac, xs_fmt("Unlike for %s %d", id, status)); | ||
| 1905 | } | ||
| 1906 | else | ||
| 1907 | if (strcmp(utype, "Announce") == 0) { /** **/ | ||
| 1908 | int status = 200; | ||
| 1909 | |||
| 1910 | /* commented out: if a followed user boosts something that | ||
| 1911 | is requested and then unboosts, the post remains here, | ||
| 1912 | but with no apparent reason, and that is confusing */ | ||
| 1913 | //status = object_unadmire(id, actor, 0); | ||
| 1914 | |||
| 1915 | snac_log(snac, xs_fmt("Unboost for %s %d", id, status)); | ||
| 1954 | } | 1916 | } |
| 1955 | else | 1917 | else |
| 1956 | snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype)); | 1918 | snac_debug(snac, 1, xs_fmt("ignored 'Undo' for object type '%s'", utype)); |
| @@ -1963,19 +1925,29 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1963 | } | 1925 | } |
| 1964 | 1926 | ||
| 1965 | if (xs_match(utype, "Note|Article")) { /** **/ | 1927 | if (xs_match(utype, "Note|Article")) { /** **/ |
| 1966 | char *id = xs_dict_get(object, "id"); | 1928 | const char *id = xs_dict_get(object, "id"); |
| 1967 | char *in_reply_to = xs_dict_get(object, "inReplyTo"); | 1929 | const char *in_reply_to = xs_dict_get(object, "inReplyTo"); |
| 1930 | const char *atto = get_atto(object); | ||
| 1968 | xs *wrk = NULL; | 1931 | xs *wrk = NULL; |
| 1969 | 1932 | ||
| 1933 | if (xs_is_null(id)) | ||
| 1934 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); | ||
| 1935 | else | ||
| 1936 | if (xs_is_null(atto)) | ||
| 1937 | snac_log(snac, xs_fmt("malformed message: no 'attributedTo' field")); | ||
| 1938 | else | ||
| 1970 | if (!xs_is_null(in_reply_to) && is_hidden(snac, in_reply_to)) { | 1939 | if (!xs_is_null(in_reply_to) && is_hidden(snac, in_reply_to)) { |
| 1971 | snac_debug(snac, 0, xs_fmt("dropped reply %s to hidden post %s", id, in_reply_to)); | 1940 | snac_debug(snac, 0, xs_fmt("dropped reply %s to hidden post %s", id, in_reply_to)); |
| 1972 | } | 1941 | } |
| 1973 | else { | 1942 | else { |
| 1974 | if (content_check("filter_reject.txt", object)) { | 1943 | if (content_match("filter_reject.txt", object)) { |
| 1975 | snac_log(snac, xs_fmt("rejected by content %s", id)); | 1944 | snac_log(snac, xs_fmt("rejected by content %s", id)); |
| 1976 | return 1; | 1945 | return 1; |
| 1977 | } | 1946 | } |
| 1978 | 1947 | ||
| 1948 | if (strcmp(actor, atto) != 0) | ||
| 1949 | snac_log(snac, xs_fmt("SUSPICIOUS: actor != atto (%s != %s)", actor, atto)); | ||
| 1950 | |||
| 1979 | timeline_request(snac, &in_reply_to, &wrk, 0); | 1951 | timeline_request(snac, &in_reply_to, &wrk, 0); |
| 1980 | 1952 | ||
| 1981 | if (timeline_add(snac, id, object)) { | 1953 | if (timeline_add(snac, id, object)) { |
| @@ -1992,14 +1964,14 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 1992 | } | 1964 | } |
| 1993 | else | 1965 | else |
| 1994 | if (strcmp(utype, "Question") == 0) { /** **/ | 1966 | if (strcmp(utype, "Question") == 0) { /** **/ |
| 1995 | char *id = xs_dict_get(object, "id"); | 1967 | const char *id = xs_dict_get(object, "id"); |
| 1996 | 1968 | ||
| 1997 | if (timeline_add(snac, id, object)) | 1969 | if (timeline_add(snac, id, object)) |
| 1998 | snac_log(snac, xs_fmt("new 'Question' %s %s", actor, id)); | 1970 | snac_log(snac, xs_fmt("new 'Question' %s %s", actor, id)); |
| 1999 | } | 1971 | } |
| 2000 | else | 1972 | else |
| 2001 | if (strcmp(utype, "Video") == 0) { /** **/ | 1973 | if (strcmp(utype, "Video") == 0) { /** **/ |
| 2002 | char *id = xs_dict_get(object, "id"); | 1974 | const char *id = xs_dict_get(object, "id"); |
| 2003 | 1975 | ||
| 2004 | if (timeline_add(snac, id, object)) | 1976 | if (timeline_add(snac, id, object)) |
| 2005 | snac_log(snac, xs_fmt("new 'Video' %s %s", actor, id)); | 1977 | snac_log(snac, xs_fmt("new 'Video' %s %s", actor, id)); |
| @@ -2080,6 +2052,9 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 2080 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", | 2052 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", |
| 2081 | actor, object)); | 2053 | actor, object)); |
| 2082 | 2054 | ||
| 2055 | /* distribute the post with the actor as 'proxy' */ | ||
| 2056 | list_distribute(snac, actor, a_msg); | ||
| 2057 | |||
| 2083 | do_notify = 1; | 2058 | do_notify = 1; |
| 2084 | } | 2059 | } |
| 2085 | else | 2060 | else |
| @@ -2172,7 +2147,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) | |||
| 2172 | } | 2147 | } |
| 2173 | 2148 | ||
| 2174 | 2149 | ||
| 2175 | int send_email(char *msg) | 2150 | int send_email(const char *msg) |
| 2176 | /* invoke sendmail with email headers and body in msg */ | 2151 | /* invoke sendmail with email headers and body in msg */ |
| 2177 | { | 2152 | { |
| 2178 | FILE *f; | 2153 | FILE *f; |
| @@ -2204,18 +2179,18 @@ int send_email(char *msg) | |||
| 2204 | void process_user_queue_item(snac *snac, xs_dict *q_item) | 2179 | void process_user_queue_item(snac *snac, xs_dict *q_item) |
| 2205 | /* processes an item from the user queue */ | 2180 | /* processes an item from the user queue */ |
| 2206 | { | 2181 | { |
| 2207 | char *type; | 2182 | const char *type; |
| 2208 | int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); | 2183 | int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); |
| 2209 | 2184 | ||
| 2210 | if ((type = xs_dict_get(q_item, "type")) == NULL) | 2185 | if ((type = xs_dict_get(q_item, "type")) == NULL) |
| 2211 | type = "output"; | 2186 | type = "output"; |
| 2212 | 2187 | ||
| 2213 | if (strcmp(type, "message") == 0) { | 2188 | if (strcmp(type, "message") == 0) { |
| 2214 | xs_dict *msg = xs_dict_get(q_item, "message"); | 2189 | const xs_dict *msg = xs_dict_get(q_item, "message"); |
| 2215 | xs *rcpts = recipient_list(snac, msg, 1); | 2190 | xs *rcpts = recipient_list(snac, msg, 1); |
| 2216 | xs_set inboxes; | 2191 | xs_set inboxes; |
| 2217 | xs_list *p; | 2192 | xs_list *p; |
| 2218 | xs_str *actor; | 2193 | const xs_str *actor; |
| 2219 | 2194 | ||
| 2220 | xs_set_init(&inboxes); | 2195 | xs_set_init(&inboxes); |
| 2221 | 2196 | ||
| @@ -2237,7 +2212,7 @@ void process_user_queue_item(snac *snac, xs_dict *q_item) | |||
| 2237 | if (is_msg_public(msg)) { | 2212 | if (is_msg_public(msg)) { |
| 2238 | if (xs_type(xs_dict_get(srv_config, "disable_inbox_collection")) != XSTYPE_TRUE) { | 2213 | if (xs_type(xs_dict_get(srv_config, "disable_inbox_collection")) != XSTYPE_TRUE) { |
| 2239 | xs *shibx = inbox_list(); | 2214 | xs *shibx = inbox_list(); |
| 2240 | xs_str *inbox; | 2215 | const xs_str *inbox; |
| 2241 | 2216 | ||
| 2242 | p = shibx; | 2217 | p = shibx; |
| 2243 | while (xs_list_iter(&p, &inbox)) { | 2218 | while (xs_list_iter(&p, &inbox)) { |
| @@ -2252,8 +2227,8 @@ void process_user_queue_item(snac *snac, xs_dict *q_item) | |||
| 2252 | else | 2227 | else |
| 2253 | if (strcmp(type, "input") == 0) { | 2228 | if (strcmp(type, "input") == 0) { |
| 2254 | /* process the message */ | 2229 | /* process the message */ |
| 2255 | xs_dict *msg = xs_dict_get(q_item, "message"); | 2230 | const xs_dict *msg = xs_dict_get(q_item, "message"); |
| 2256 | xs_dict *req = xs_dict_get(q_item, "req"); | 2231 | const xs_dict *req = xs_dict_get(q_item, "req"); |
| 2257 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); | 2232 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); |
| 2258 | 2233 | ||
| 2259 | if (xs_is_null(msg)) | 2234 | if (xs_is_null(msg)) |
| @@ -2280,11 +2255,20 @@ void process_user_queue_item(snac *snac, xs_dict *q_item) | |||
| 2280 | update_question(snac, id); | 2255 | update_question(snac, id); |
| 2281 | } | 2256 | } |
| 2282 | else | 2257 | else |
| 2283 | if (strcmp(type, "request_replies") == 0) { | 2258 | if (strcmp(type, "object_request") == 0) { |
| 2284 | const char *id = xs_dict_get(q_item, "message"); | 2259 | const char *id = xs_dict_get(q_item, "message"); |
| 2285 | 2260 | ||
| 2286 | if (!xs_is_null(id)) | 2261 | if (!xs_is_null(id)) { |
| 2287 | timeline_request_replies(snac, id); | 2262 | int status; |
| 2263 | xs *data = NULL; | ||
| 2264 | |||
| 2265 | status = activitypub_request(snac, id, &data); | ||
| 2266 | |||
| 2267 | if (valid_status(status)) | ||
| 2268 | object_add_ow(id, data); | ||
| 2269 | |||
| 2270 | snac_debug(snac, 1, xs_fmt("object_request %s %d", id, status)); | ||
| 2271 | } | ||
| 2288 | } | 2272 | } |
| 2289 | else | 2273 | else |
| 2290 | if (strcmp(type, "verify_links") == 0) { | 2274 | if (strcmp(type, "verify_links") == 0) { |
| @@ -2320,7 +2304,7 @@ int process_user_queue(snac *snac) | |||
| 2320 | xs *list = user_queue(snac); | 2304 | xs *list = user_queue(snac); |
| 2321 | 2305 | ||
| 2322 | xs_list *p = list; | 2306 | xs_list *p = list; |
| 2323 | xs_str *fn; | 2307 | const xs_str *fn; |
| 2324 | 2308 | ||
| 2325 | while (xs_list_iter(&p, &fn)) { | 2309 | while (xs_list_iter(&p, &fn)) { |
| 2326 | xs *q_item = dequeue(fn); | 2310 | xs *q_item = dequeue(fn); |
| @@ -2339,19 +2323,20 @@ int process_user_queue(snac *snac) | |||
| 2339 | void process_queue_item(xs_dict *q_item) | 2323 | void process_queue_item(xs_dict *q_item) |
| 2340 | /* processes an item from the global queue */ | 2324 | /* processes an item from the global queue */ |
| 2341 | { | 2325 | { |
| 2342 | char *type = xs_dict_get(q_item, "type"); | 2326 | const char *type = xs_dict_get(q_item, "type"); |
| 2343 | int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); | 2327 | int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); |
| 2344 | 2328 | ||
| 2345 | if (strcmp(type, "output") == 0) { | 2329 | if (strcmp(type, "output") == 0) { |
| 2346 | int status; | 2330 | int status; |
| 2347 | xs_str *inbox = xs_dict_get(q_item, "inbox"); | 2331 | const xs_str *inbox = xs_dict_get(q_item, "inbox"); |
| 2348 | xs_str *keyid = xs_dict_get(q_item, "keyid"); | 2332 | const xs_str *keyid = xs_dict_get(q_item, "keyid"); |
| 2349 | xs_str *seckey = xs_dict_get(q_item, "seckey"); | 2333 | const xs_str *seckey = xs_dict_get(q_item, "seckey"); |
| 2350 | xs_dict *msg = xs_dict_get(q_item, "message"); | 2334 | const xs_dict *msg = xs_dict_get(q_item, "message"); |
| 2351 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); | 2335 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); |
| 2352 | int p_status = xs_number_get(xs_dict_get(q_item, "p_status")); | 2336 | int p_status = xs_number_get(xs_dict_get(q_item, "p_status")); |
| 2353 | xs *payload = NULL; | 2337 | xs *payload = NULL; |
| 2354 | int p_size = 0; | 2338 | int p_size = 0; |
| 2339 | int timeout = 0; | ||
| 2355 | 2340 | ||
| 2356 | if (xs_is_null(inbox) || xs_is_null(msg) || xs_is_null(keyid) || xs_is_null(seckey)) { | 2341 | if (xs_is_null(inbox) || xs_is_null(msg) || xs_is_null(keyid) || xs_is_null(seckey)) { |
| 2357 | srv_log(xs_fmt("output message error: missing fields")); | 2342 | srv_log(xs_fmt("output message error: missing fields")); |
| @@ -2364,8 +2349,15 @@ void process_queue_item(xs_dict *q_item) | |||
| 2364 | } | 2349 | } |
| 2365 | 2350 | ||
| 2366 | /* deliver (if previous error status was a timeout, try now longer) */ | 2351 | /* deliver (if previous error status was a timeout, try now longer) */ |
| 2367 | status = send_to_inbox_raw(keyid, seckey, inbox, msg, | 2352 | if (p_status == 599) |
| 2368 | &payload, &p_size, p_status == 599 ? 8 : 6); | 2353 | timeout = xs_number_get(xs_dict_get_def(srv_config, "queue_timeout_2", "8")); |
| 2354 | else | ||
| 2355 | timeout = xs_number_get(xs_dict_get_def(srv_config, "queue_timeout", "6")); | ||
| 2356 | |||
| 2357 | if (timeout == 0) | ||
| 2358 | timeout = 6; | ||
| 2359 | |||
| 2360 | status = send_to_inbox_raw(keyid, seckey, inbox, msg, &payload, &p_size, timeout); | ||
| 2369 | 2361 | ||
| 2370 | if (payload) { | 2362 | if (payload) { |
| 2371 | if (p_size > 64) { | 2363 | if (p_size > 64) { |
| @@ -2411,7 +2403,7 @@ void process_queue_item(xs_dict *q_item) | |||
| 2411 | else | 2403 | else |
| 2412 | if (strcmp(type, "email") == 0) { | 2404 | if (strcmp(type, "email") == 0) { |
| 2413 | /* send this email */ | 2405 | /* send this email */ |
| 2414 | xs_str *msg = xs_dict_get(q_item, "message"); | 2406 | const xs_str *msg = xs_dict_get(q_item, "message"); |
| 2415 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); | 2407 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); |
| 2416 | 2408 | ||
| 2417 | if (!send_email(msg)) | 2409 | if (!send_email(msg)) |
| @@ -2433,8 +2425,8 @@ void process_queue_item(xs_dict *q_item) | |||
| 2433 | else | 2425 | else |
| 2434 | if (strcmp(type, "telegram") == 0) { | 2426 | if (strcmp(type, "telegram") == 0) { |
| 2435 | /* send this via telegram */ | 2427 | /* send this via telegram */ |
| 2436 | char *bot = xs_dict_get(q_item, "bot"); | 2428 | const char *bot = xs_dict_get(q_item, "bot"); |
| 2437 | char *msg = xs_dict_get(q_item, "message"); | 2429 | const char *msg = xs_dict_get(q_item, "message"); |
| 2438 | xs *chat_id = xs_dup(xs_dict_get(q_item, "chat_id")); | 2430 | xs *chat_id = xs_dup(xs_dict_get(q_item, "chat_id")); |
| 2439 | int status = 0; | 2431 | int status = 0; |
| 2440 | 2432 | ||
| @@ -2457,9 +2449,9 @@ void process_queue_item(xs_dict *q_item) | |||
| 2457 | else | 2449 | else |
| 2458 | if (strcmp(type, "ntfy") == 0) { | 2450 | if (strcmp(type, "ntfy") == 0) { |
| 2459 | /* send this via ntfy */ | 2451 | /* send this via ntfy */ |
| 2460 | char *ntfy_server = xs_dict_get(q_item, "ntfy_server"); | 2452 | const char *ntfy_server = xs_dict_get(q_item, "ntfy_server"); |
| 2461 | char *msg = xs_dict_get(q_item, "message"); | 2453 | const char *msg = xs_dict_get(q_item, "message"); |
| 2462 | char *ntfy_token = xs_dict_get(q_item, "ntfy_token"); | 2454 | const char *ntfy_token = xs_dict_get(q_item, "ntfy_token"); |
| 2463 | int status = 0; | 2455 | int status = 0; |
| 2464 | 2456 | ||
| 2465 | xs *url = xs_fmt("%s", ntfy_server); | 2457 | xs *url = xs_fmt("%s", ntfy_server); |
| @@ -2488,8 +2480,8 @@ void process_queue_item(xs_dict *q_item) | |||
| 2488 | } | 2480 | } |
| 2489 | else | 2481 | else |
| 2490 | if (strcmp(type, "input") == 0) { | 2482 | if (strcmp(type, "input") == 0) { |
| 2491 | xs_dict *msg = xs_dict_get(q_item, "message"); | 2483 | const xs_dict *msg = xs_dict_get(q_item, "message"); |
| 2492 | xs_dict *req = xs_dict_get(q_item, "req"); | 2484 | const xs_dict *req = xs_dict_get(q_item, "req"); |
| 2493 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); | 2485 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); |
| 2494 | 2486 | ||
| 2495 | /* do some instance-level checks */ | 2487 | /* do some instance-level checks */ |
| @@ -2497,8 +2489,6 @@ void process_queue_item(xs_dict *q_item) | |||
| 2497 | 2489 | ||
| 2498 | if (r == 0) { | 2490 | if (r == 0) { |
| 2499 | /* transient error? retry */ | 2491 | /* transient error? retry */ |
| 2500 | int queue_retry_max = xs_number_get(xs_dict_get(srv_config, "queue_retry_max")); | ||
| 2501 | |||
| 2502 | if (retries > queue_retry_max) | 2492 | if (retries > queue_retry_max) |
| 2503 | srv_log(xs_fmt("shared input giving up")); | 2493 | srv_log(xs_fmt("shared input giving up")); |
| 2504 | else { | 2494 | else { |
| @@ -2510,7 +2500,7 @@ void process_queue_item(xs_dict *q_item) | |||
| 2510 | else | 2500 | else |
| 2511 | if (r == 2) { | 2501 | if (r == 2) { |
| 2512 | /* redistribute the input message to all users */ | 2502 | /* redistribute the input message to all users */ |
| 2513 | char *ntid = xs_dict_get(q_item, "ntid"); | 2503 | const char *ntid = xs_dict_get(q_item, "ntid"); |
| 2514 | xs *tmpfn = xs_fmt("%s/tmp/%s.json", srv_basedir, ntid); | 2504 | xs *tmpfn = xs_fmt("%s/tmp/%s.json", srv_basedir, ntid); |
| 2515 | FILE *f; | 2505 | FILE *f; |
| 2516 | 2506 | ||
| @@ -2521,7 +2511,7 @@ void process_queue_item(xs_dict *q_item) | |||
| 2521 | 2511 | ||
| 2522 | xs *users = user_list(); | 2512 | xs *users = user_list(); |
| 2523 | xs_list *p = users; | 2513 | xs_list *p = users; |
| 2524 | char *v; | 2514 | const char *v; |
| 2525 | int cnt = 0; | 2515 | int cnt = 0; |
| 2526 | 2516 | ||
| 2527 | while (xs_list_iter(&p, &v)) { | 2517 | while (xs_list_iter(&p, &v)) { |
| @@ -2564,7 +2554,7 @@ int process_queue(void) | |||
| 2564 | xs *list = queue(); | 2554 | xs *list = queue(); |
| 2565 | 2555 | ||
| 2566 | xs_list *p = list; | 2556 | xs_list *p = list; |
| 2567 | xs_str *fn; | 2557 | const xs_str *fn; |
| 2568 | 2558 | ||
| 2569 | while (xs_list_iter(&p, &fn)) { | 2559 | while (xs_list_iter(&p, &fn)) { |
| 2570 | xs *q_item = dequeue(fn); | 2560 | xs *q_item = dequeue(fn); |
| @@ -2585,7 +2575,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 2585 | char **body, int *b_size, char **ctype) | 2575 | char **body, int *b_size, char **ctype) |
| 2586 | { | 2576 | { |
| 2587 | int status = 200; | 2577 | int status = 200; |
| 2588 | char *accept = xs_dict_get(req, "accept"); | 2578 | const char *accept = xs_dict_get(req, "accept"); |
| 2589 | snac snac; | 2579 | snac snac; |
| 2590 | xs *msg = NULL; | 2580 | xs *msg = NULL; |
| 2591 | 2581 | ||
| @@ -2597,7 +2587,8 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 2597 | return 0; | 2587 | return 0; |
| 2598 | 2588 | ||
| 2599 | xs *l = xs_split_n(q_path, "/", 2); | 2589 | xs *l = xs_split_n(q_path, "/", 2); |
| 2600 | char *uid, *p_path; | 2590 | const char *uid; |
| 2591 | const char *p_path; | ||
| 2601 | 2592 | ||
| 2602 | uid = xs_list_get(l, 1); | 2593 | uid = xs_list_get(l, 1); |
| 2603 | if (!user_open(&snac, uid)) { | 2594 | if (!user_open(&snac, uid)) { |
| @@ -2615,7 +2606,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 2615 | msg = msg_actor(&snac); | 2606 | msg = msg_actor(&snac); |
| 2616 | *ctype = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; | 2607 | *ctype = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; |
| 2617 | 2608 | ||
| 2618 | char *ua = xs_dict_get(req, "user-agent"); | 2609 | const char *ua = xs_dict_get(req, "user-agent"); |
| 2619 | 2610 | ||
| 2620 | snac_debug(&snac, 0, xs_fmt("serving actor [%s]", ua ? ua : "No UA")); | 2611 | snac_debug(&snac, 0, xs_fmt("serving actor [%s]", ua ? ua : "No UA")); |
| 2621 | } | 2612 | } |
| @@ -2625,15 +2616,16 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 2625 | xs *elems = timeline_simple_list(&snac, "public", 0, 20); | 2616 | xs *elems = timeline_simple_list(&snac, "public", 0, 20); |
| 2626 | xs *list = xs_list_new(); | 2617 | xs *list = xs_list_new(); |
| 2627 | msg = msg_collection(&snac, id); | 2618 | msg = msg_collection(&snac, id); |
| 2628 | char *p, *v; | 2619 | char *p; |
| 2620 | const char *v; | ||
| 2629 | 2621 | ||
| 2630 | p = elems; | 2622 | p = elems; |
| 2631 | while (xs_list_iter(&p, &v)) { | 2623 | while (xs_list_iter(&p, &v)) { |
| 2632 | xs *i = NULL; | 2624 | xs *i = NULL; |
| 2633 | 2625 | ||
| 2634 | if (valid_status(object_get_by_md5(v, &i))) { | 2626 | if (valid_status(object_get_by_md5(v, &i))) { |
| 2635 | char *type = xs_dict_get(i, "type"); | 2627 | const char *type = xs_dict_get(i, "type"); |
| 2636 | char *id = xs_dict_get(i, "id"); | 2628 | const char *id = xs_dict_get(i, "id"); |
| 2637 | 2629 | ||
| 2638 | if (type && id && strcmp(type, "Note") == 0 && xs_startswith(id, snac.actor)) { | 2630 | if (type && id && strcmp(type, "Note") == 0 && xs_startswith(id, snac.actor)) { |
| 2639 | xs *c_msg = msg_create(&snac, i); | 2631 | xs *c_msg = msg_create(&snac, i); |
| @@ -2686,9 +2678,9 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path, | |||
| 2686 | (void)b_size; | 2678 | (void)b_size; |
| 2687 | 2679 | ||
| 2688 | int status = 202; /* accepted */ | 2680 | int status = 202; /* accepted */ |
| 2689 | char *i_ctype = xs_dict_get(req, "content-type"); | 2681 | const char *i_ctype = xs_dict_get(req, "content-type"); |
| 2690 | snac snac; | 2682 | snac snac; |
| 2691 | char *v; | 2683 | const char *v; |
| 2692 | 2684 | ||
| 2693 | if (i_ctype == NULL) { | 2685 | if (i_ctype == NULL) { |
| 2694 | *body = xs_str_new("no content-type"); | 2686 | *body = xs_str_new("no content-type"); |
| @@ -10,6 +10,7 @@ | |||
| 10 | #include "xs_set.h" | 10 | #include "xs_set.h" |
| 11 | #include "xs_time.h" | 11 | #include "xs_time.h" |
| 12 | #include "xs_regex.h" | 12 | #include "xs_regex.h" |
| 13 | #include "xs_match.h" | ||
| 13 | 14 | ||
| 14 | #include "snac.h" | 15 | #include "snac.h" |
| 15 | 16 | ||
| @@ -28,7 +29,7 @@ pthread_mutex_t data_mutex = {0}; | |||
| 28 | int snac_upgrade(xs_str **error); | 29 | int snac_upgrade(xs_str **error); |
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | int srv_open(char *basedir, int auto_upgrade) | 32 | int srv_open(const char *basedir, int auto_upgrade) |
| 32 | /* opens a server */ | 33 | /* opens a server */ |
| 33 | { | 34 | { |
| 34 | int ret = 0; | 35 | int ret = 0; |
| @@ -57,18 +58,20 @@ int srv_open(char *basedir, int auto_upgrade) | |||
| 57 | if (srv_config == NULL) | 58 | if (srv_config == NULL) |
| 58 | error = xs_fmt("ERROR: cannot parse '%s'", cfg_file); | 59 | error = xs_fmt("ERROR: cannot parse '%s'", cfg_file); |
| 59 | else { | 60 | else { |
| 60 | char *host; | 61 | const char *host; |
| 61 | char *prefix; | 62 | const char *prefix; |
| 62 | char *dbglvl; | 63 | const char *dbglvl; |
| 64 | const char *proto; | ||
| 63 | 65 | ||
| 64 | host = xs_dict_get(srv_config, "host"); | 66 | host = xs_dict_get(srv_config, "host"); |
| 65 | prefix = xs_dict_get(srv_config, "prefix"); | 67 | prefix = xs_dict_get(srv_config, "prefix"); |
| 66 | dbglvl = xs_dict_get(srv_config, "dbglevel"); | 68 | dbglvl = xs_dict_get(srv_config, "dbglevel"); |
| 69 | proto = xs_dict_get_def(srv_config, "protocol", "https"); | ||
| 67 | 70 | ||
| 68 | if (host == NULL || prefix == NULL) | 71 | if (host == NULL || prefix == NULL) |
| 69 | error = xs_str_new("ERROR: cannot get server data"); | 72 | error = xs_str_new("ERROR: cannot get server data"); |
| 70 | else { | 73 | else { |
| 71 | srv_baseurl = xs_fmt("https://%s%s", host, prefix); | 74 | srv_baseurl = xs_fmt("%s:/" "/%s%s", proto, host, prefix); |
| 72 | 75 | ||
| 73 | dbglevel = (int) xs_number_get(dbglvl); | 76 | dbglevel = (int) xs_number_get(dbglvl); |
| 74 | 77 | ||
| @@ -111,7 +114,7 @@ int srv_open(char *basedir, int auto_upgrade) | |||
| 111 | #endif | 114 | #endif |
| 112 | 115 | ||
| 113 | #ifdef __OpenBSD__ | 116 | #ifdef __OpenBSD__ |
| 114 | char *v = xs_dict_get(srv_config, "disable_openbsd_security"); | 117 | const char *v = xs_dict_get(srv_config, "disable_openbsd_security"); |
| 115 | 118 | ||
| 116 | if (v && xs_type(v) == XSTYPE_TRUE) { | 119 | if (v && xs_type(v) == XSTYPE_TRUE) { |
| 117 | srv_debug(1, xs_dup("OpenBSD security disabled by admin")); | 120 | srv_debug(1, xs_dup("OpenBSD security disabled by admin")); |
| @@ -190,7 +193,7 @@ int user_open(snac *user, const char *uid) | |||
| 190 | xs *lcuid = xs_tolower_i(xs_dup(uid)); | 193 | xs *lcuid = xs_tolower_i(xs_dup(uid)); |
| 191 | xs *ulist = user_list(); | 194 | xs *ulist = user_list(); |
| 192 | xs_list *p = ulist; | 195 | xs_list *p = ulist; |
| 193 | xs_str *v; | 196 | const xs_str *v; |
| 194 | 197 | ||
| 195 | while (xs_list_iter(&p, &v)) { | 198 | while (xs_list_iter(&p, &v)) { |
| 196 | xs *v2 = xs_tolower_i(xs_dup(v)); | 199 | xs *v2 = xs_tolower_i(xs_dup(v)); |
| @@ -286,7 +289,7 @@ int user_open_by_md5(snac *snac, const char *md5) | |||
| 286 | { | 289 | { |
| 287 | xs *ulist = user_list(); | 290 | xs *ulist = user_list(); |
| 288 | xs_list *p = ulist; | 291 | xs_list *p = ulist; |
| 289 | xs_str *v; | 292 | const xs_str *v; |
| 290 | 293 | ||
| 291 | while (xs_list_iter(&p, &v)) { | 294 | while (xs_list_iter(&p, &v)) { |
| 292 | user_open(snac, v); | 295 | user_open(snac, v); |
| @@ -338,6 +341,12 @@ double f_ctime(const char *fn) | |||
| 338 | } | 341 | } |
| 339 | 342 | ||
| 340 | 343 | ||
| 344 | int is_md5_hex(const char *md5) | ||
| 345 | { | ||
| 346 | return xs_is_hex(md5) && strlen(md5) == 32; | ||
| 347 | } | ||
| 348 | |||
| 349 | |||
| 341 | /** database 2.1+ **/ | 350 | /** database 2.1+ **/ |
| 342 | 351 | ||
| 343 | /** indexes **/ | 352 | /** indexes **/ |
| @@ -349,6 +358,11 @@ int index_add_md5(const char *fn, const char *md5) | |||
| 349 | int status = 201; /* Created */ | 358 | int status = 201; /* Created */ |
| 350 | FILE *f; | 359 | FILE *f; |
| 351 | 360 | ||
| 361 | if (!is_md5_hex(md5)) { | ||
| 362 | srv_log(xs_fmt("index_add_md5: bad md5 %s %s", fn, md5)); | ||
| 363 | return 400; | ||
| 364 | } | ||
| 365 | |||
| 352 | pthread_mutex_lock(&data_mutex); | 366 | pthread_mutex_lock(&data_mutex); |
| 353 | 367 | ||
| 354 | if ((f = fopen(fn, "a")) != NULL) { | 368 | if ((f = fopen(fn, "a")) != NULL) { |
| @@ -406,7 +420,7 @@ int index_del_md5(const char *fn, const char *md5) | |||
| 406 | fclose(f); | 420 | fclose(f); |
| 407 | } | 421 | } |
| 408 | else | 422 | else |
| 409 | status = 500; | 423 | status = 410; |
| 410 | 424 | ||
| 411 | pthread_mutex_unlock(&data_mutex); | 425 | pthread_mutex_unlock(&data_mutex); |
| 412 | 426 | ||
| @@ -604,7 +618,7 @@ static xs_str *_object_fn_by_md5(const char *md5, const char *func) | |||
| 604 | if (md5[0] == '-') | 618 | if (md5[0] == '-') |
| 605 | ok = 0; | 619 | ok = 0; |
| 606 | else | 620 | else |
| 607 | if (!xs_is_hex(md5) || strlen(md5) != 32) { | 621 | if (!is_md5_hex(md5)) { |
| 608 | srv_log(xs_fmt("_object_fn_by_md5() [from %s()]: bad md5 '%s'", func, md5)); | 622 | srv_log(xs_fmt("_object_fn_by_md5() [from %s()]: bad md5 '%s'", func, md5)); |
| 609 | ok = 0; | 623 | ok = 0; |
| 610 | } | 624 | } |
| @@ -696,7 +710,7 @@ int _object_add(const char *id, const xs_dict *obj, int ow) | |||
| 696 | fclose(f); | 710 | fclose(f); |
| 697 | 711 | ||
| 698 | /* does this object has a parent? */ | 712 | /* does this object has a parent? */ |
| 699 | char *in_reply_to = xs_dict_get(obj, "inReplyTo"); | 713 | const char *in_reply_to = xs_dict_get(obj, "inReplyTo"); |
| 700 | 714 | ||
| 701 | if (!xs_is_null(in_reply_to) && *in_reply_to) { | 715 | if (!xs_is_null(in_reply_to) && *in_reply_to) { |
| 702 | /* update the children index of the parent */ | 716 | /* update the children index of the parent */ |
| @@ -758,7 +772,8 @@ int object_del_by_md5(const char *md5) | |||
| 758 | xs *spec = xs_dup(fn); | 772 | xs *spec = xs_dup(fn); |
| 759 | spec = xs_replace_i(spec, ".json", "*.idx"); | 773 | spec = xs_replace_i(spec, ".json", "*.idx"); |
| 760 | xs *files = xs_glob(spec, 0, 0); | 774 | xs *files = xs_glob(spec, 0, 0); |
| 761 | char *p, *v; | 775 | char *p; |
| 776 | const char *v; | ||
| 762 | 777 | ||
| 763 | p = files; | 778 | p = files; |
| 764 | while (xs_list_iter(&p, &v)) { | 779 | while (xs_list_iter(&p, &v)) { |
| @@ -917,6 +932,9 @@ int object_unadmire(const char *id, const char *actor, int like) | |||
| 917 | 932 | ||
| 918 | status = index_del(fn, actor); | 933 | status = index_del(fn, actor); |
| 919 | 934 | ||
| 935 | if (valid_status(status)) | ||
| 936 | index_gc(fn); | ||
| 937 | |||
| 920 | srv_debug(0, | 938 | srv_debug(0, |
| 921 | xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); | 939 | xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); |
| 922 | 940 | ||
| @@ -1016,7 +1034,8 @@ xs_list *follower_list(snac *snac) | |||
| 1016 | { | 1034 | { |
| 1017 | xs *list = object_user_cache_list(snac, "followers", XS_ALL, 0); | 1035 | xs *list = object_user_cache_list(snac, "followers", XS_ALL, 0); |
| 1018 | xs_list *fwers = xs_list_new(); | 1036 | xs_list *fwers = xs_list_new(); |
| 1019 | char *p, *v; | 1037 | char *p; |
| 1038 | const char *v; | ||
| 1020 | 1039 | ||
| 1021 | /* resolve the list of md5 to be a list of actors */ | 1040 | /* resolve the list of md5 to be a list of actors */ |
| 1022 | p = list; | 1041 | p = list; |
| @@ -1060,14 +1079,18 @@ int timeline_touch(snac *snac) | |||
| 1060 | xs_str *timeline_fn_by_md5(snac *snac, const char *md5) | 1079 | xs_str *timeline_fn_by_md5(snac *snac, const char *md5) |
| 1061 | /* get the filename of an entry by md5 from any timeline */ | 1080 | /* get the filename of an entry by md5 from any timeline */ |
| 1062 | { | 1081 | { |
| 1063 | xs_str *fn = xs_fmt("%s/private/%s.json", snac->basedir, md5); | 1082 | xs_str *fn = NULL; |
| 1064 | 1083 | ||
| 1065 | if (mtime(fn) == 0.0) { | 1084 | if (xs_is_hex(md5) && strlen(md5) == 32) { |
| 1066 | fn = xs_free(fn); | 1085 | fn = xs_fmt("%s/private/%s.json", snac->basedir, md5); |
| 1067 | fn = xs_fmt("%s/public/%s.json", snac->basedir, md5); | ||
| 1068 | 1086 | ||
| 1069 | if (mtime(fn) == 0.0) | 1087 | if (mtime(fn) == 0.0) { |
| 1070 | fn = xs_free(fn); | 1088 | fn = xs_free(fn); |
| 1089 | fn = xs_fmt("%s/public/%s.json", snac->basedir, md5); | ||
| 1090 | |||
| 1091 | if (mtime(fn) == 0.0) | ||
| 1092 | fn = xs_free(fn); | ||
| 1093 | } | ||
| 1071 | } | 1094 | } |
| 1072 | 1095 | ||
| 1073 | return fn; | 1096 | return fn; |
| @@ -1103,7 +1126,7 @@ int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg) | |||
| 1103 | } | 1126 | } |
| 1104 | 1127 | ||
| 1105 | 1128 | ||
| 1106 | int timeline_del(snac *snac, char *id) | 1129 | int timeline_del(snac *snac, const char *id) |
| 1107 | /* deletes a message from the timeline */ | 1130 | /* deletes a message from the timeline */ |
| 1108 | { | 1131 | { |
| 1109 | /* delete from the user's caches */ | 1132 | /* delete from the user's caches */ |
| @@ -1145,6 +1168,8 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg) | |||
| 1145 | 1168 | ||
| 1146 | tag_index(id, o_msg); | 1169 | tag_index(id, o_msg); |
| 1147 | 1170 | ||
| 1171 | list_distribute(snac, NULL, o_msg); | ||
| 1172 | |||
| 1148 | snac_debug(snac, 1, xs_fmt("timeline_add %s", id)); | 1173 | snac_debug(snac, 1, xs_fmt("timeline_add %s", id)); |
| 1149 | 1174 | ||
| 1150 | return ret; | 1175 | return ret; |
| @@ -1169,17 +1194,16 @@ int timeline_admire(snac *snac, const char *id, const char *admirer, int like) | |||
| 1169 | } | 1194 | } |
| 1170 | 1195 | ||
| 1171 | 1196 | ||
| 1172 | xs_list *timeline_top_level(snac *snac, xs_list *list) | 1197 | xs_list *timeline_top_level(snac *snac, const xs_list *list) |
| 1173 | /* returns the top level md5 entries from this index */ | 1198 | /* returns the top level md5 entries from this index */ |
| 1174 | { | 1199 | { |
| 1175 | xs_set seen; | 1200 | xs_set seen; |
| 1176 | xs_list *p; | 1201 | const xs_str *v; |
| 1177 | xs_str *v; | ||
| 1178 | 1202 | ||
| 1179 | xs_set_init(&seen); | 1203 | xs_set_init(&seen); |
| 1180 | 1204 | ||
| 1181 | p = list; | 1205 | int c = 0; |
| 1182 | while (xs_list_iter(&p, &v)) { | 1206 | while (xs_list_next(list, &v, &c)) { |
| 1183 | char line[256] = ""; | 1207 | char line[256] = ""; |
| 1184 | 1208 | ||
| 1185 | strncpy(line, v, sizeof(line)); | 1209 | strncpy(line, v, sizeof(line)); |
| @@ -1267,7 +1291,7 @@ int following_add(snac *snac, const char *actor, const xs_dict *msg) | |||
| 1267 | /* object already exists; if it's of type Accept, | 1291 | /* object already exists; if it's of type Accept, |
| 1268 | the actor is already being followed and confirmed, | 1292 | the actor is already being followed and confirmed, |
| 1269 | so do nothing */ | 1293 | so do nothing */ |
| 1270 | char *type = xs_dict_get(p_object, "type"); | 1294 | const char *type = xs_dict_get(p_object, "type"); |
| 1271 | 1295 | ||
| 1272 | if (!xs_is_null(type) && strcmp(type, "Accept") == 0) { | 1296 | if (!xs_is_null(type) && strcmp(type, "Accept") == 0) { |
| 1273 | snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor)); | 1297 | snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor)); |
| @@ -1345,7 +1369,7 @@ xs_list *following_list(snac *snac) | |||
| 1345 | xs *spec = xs_fmt("%s/following/" "*.json", snac->basedir); | 1369 | xs *spec = xs_fmt("%s/following/" "*.json", snac->basedir); |
| 1346 | xs *glist = xs_glob(spec, 0, 0); | 1370 | xs *glist = xs_glob(spec, 0, 0); |
| 1347 | xs_list *p; | 1371 | xs_list *p; |
| 1348 | xs_str *v; | 1372 | const xs_str *v; |
| 1349 | xs_list *list = xs_list_new(); | 1373 | xs_list *list = xs_list_new(); |
| 1350 | 1374 | ||
| 1351 | /* iterate the list of files */ | 1375 | /* iterate the list of files */ |
| @@ -1515,7 +1539,8 @@ void hide(snac *snac, const char *id) | |||
| 1515 | 1539 | ||
| 1516 | /* hide all the children */ | 1540 | /* hide all the children */ |
| 1517 | xs *chld = object_children(id); | 1541 | xs *chld = object_children(id); |
| 1518 | char *p, *v; | 1542 | char *p; |
| 1543 | const char *v; | ||
| 1519 | 1544 | ||
| 1520 | p = chld; | 1545 | p = chld; |
| 1521 | while (xs_list_iter(&p, &v)) { | 1546 | while (xs_list_iter(&p, &v)) { |
| @@ -1523,8 +1548,9 @@ void hide(snac *snac, const char *id) | |||
| 1523 | 1548 | ||
| 1524 | /* resolve to get the id */ | 1549 | /* resolve to get the id */ |
| 1525 | if (valid_status(object_get_by_md5(v, &co))) { | 1550 | if (valid_status(object_get_by_md5(v, &co))) { |
| 1526 | if ((v = xs_dict_get(co, "id")) != NULL) | 1551 | const char *id = xs_dict_get(co, "id"); |
| 1527 | hide(snac, v); | 1552 | if (id != NULL) |
| 1553 | hide(snac, id); | ||
| 1528 | } | 1554 | } |
| 1529 | } | 1555 | } |
| 1530 | } | 1556 | } |
| @@ -1540,7 +1566,7 @@ int is_hidden(snac *snac, const char *id) | |||
| 1540 | } | 1566 | } |
| 1541 | 1567 | ||
| 1542 | 1568 | ||
| 1543 | int actor_add(const char *actor, xs_dict *msg) | 1569 | int actor_add(const char *actor, const xs_dict *msg) |
| 1544 | /* adds an actor */ | 1570 | /* adds an actor */ |
| 1545 | { | 1571 | { |
| 1546 | return object_add_ow(actor, msg); | 1572 | return object_add_ow(actor, msg); |
| @@ -1609,7 +1635,7 @@ int actor_get_refresh(snac *user, const char *actor, xs_dict **data) | |||
| 1609 | int status = actor_get(actor, data); | 1635 | int status = actor_get(actor, data); |
| 1610 | 1636 | ||
| 1611 | if (status == 205 && user && !xs_startswith(actor, srv_baseurl)) | 1637 | if (status == 205 && user && !xs_startswith(actor, srv_baseurl)) |
| 1612 | enqueue_actor_refresh(user, actor); | 1638 | enqueue_actor_refresh(user, actor, 0); |
| 1613 | 1639 | ||
| 1614 | return status; | 1640 | return status; |
| 1615 | } | 1641 | } |
| @@ -1664,17 +1690,18 @@ int limited(snac *user, const char *id, int cmd) | |||
| 1664 | void tag_index(const char *id, const xs_dict *obj) | 1690 | void tag_index(const char *id, const xs_dict *obj) |
| 1665 | /* update the tag indexes for this object */ | 1691 | /* update the tag indexes for this object */ |
| 1666 | { | 1692 | { |
| 1667 | xs_list *tags = xs_dict_get(obj, "tag"); | 1693 | const xs_list *tags = xs_dict_get(obj, "tag"); |
| 1668 | 1694 | ||
| 1669 | if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { | 1695 | if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { |
| 1670 | xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir); | 1696 | xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir); |
| 1671 | 1697 | ||
| 1672 | mkdirx(g_tag_dir); | 1698 | mkdirx(g_tag_dir); |
| 1673 | 1699 | ||
| 1674 | xs_dict *v; | 1700 | const xs_dict *v; |
| 1675 | while (xs_list_iter(&tags, &v)) { | 1701 | int ct = 0; |
| 1676 | char *type = xs_dict_get(v, "type"); | 1702 | while (xs_list_next(tags, &v, &ct)) { |
| 1677 | char *name = xs_dict_get(v, "name"); | 1703 | const char *type = xs_dict_get(v, "type"); |
| 1704 | const char *name = xs_dict_get(v, "name"); | ||
| 1678 | 1705 | ||
| 1679 | if (!xs_is_null(type) && !xs_is_null(name) && strcmp(type, "Hashtag") == 0) { | 1706 | if (!xs_is_null(type) && !xs_is_null(name) && strcmp(type, "Hashtag") == 0) { |
| 1680 | while (*name == '#' || *name == '@') | 1707 | while (*name == '#' || *name == '@') |
| @@ -1683,7 +1710,7 @@ void tag_index(const char *id, const xs_dict *obj) | |||
| 1683 | if (*name == '\0') | 1710 | if (*name == '\0') |
| 1684 | continue; | 1711 | continue; |
| 1685 | 1712 | ||
| 1686 | name = xs_tolower_i(name); | 1713 | name = xs_tolower_i((xs_str *)name); |
| 1687 | 1714 | ||
| 1688 | xs *md5_tag = xs_md5_hex(name, strlen(name)); | 1715 | xs *md5_tag = xs_md5_hex(name, strlen(name)); |
| 1689 | xs *tag_dir = xs_fmt("%s/%c%c", g_tag_dir, md5_tag[0], md5_tag[1]); | 1716 | xs *tag_dir = xs_fmt("%s/%c%c", g_tag_dir, md5_tag[0], md5_tag[1]); |
| @@ -1706,7 +1733,7 @@ void tag_index(const char *id, const xs_dict *obj) | |||
| 1706 | } | 1733 | } |
| 1707 | 1734 | ||
| 1708 | 1735 | ||
| 1709 | xs_list *tag_search(char *tag, int skip, int show) | 1736 | xs_list *tag_search(const char *tag, int skip, int show) |
| 1710 | /* returns the list of posts tagged with tag */ | 1737 | /* returns the list of posts tagged with tag */ |
| 1711 | { | 1738 | { |
| 1712 | if (*tag == '#') | 1739 | if (*tag == '#') |
| @@ -1720,6 +1747,206 @@ xs_list *tag_search(char *tag, int skip, int show) | |||
| 1720 | } | 1747 | } |
| 1721 | 1748 | ||
| 1722 | 1749 | ||
| 1750 | /** lists **/ | ||
| 1751 | |||
| 1752 | xs_val *list_maint(snac *user, const char *list, int op) | ||
| 1753 | /* list maintenance */ | ||
| 1754 | { | ||
| 1755 | xs_val *l = NULL; | ||
| 1756 | |||
| 1757 | switch (op) { | ||
| 1758 | case 0: /** list of lists **/ | ||
| 1759 | { | ||
| 1760 | FILE *f; | ||
| 1761 | xs *spec = xs_fmt("%s/list/" "*.id", user->basedir); | ||
| 1762 | xs *ls = xs_glob(spec, 0, 0); | ||
| 1763 | int c = 0; | ||
| 1764 | const char *v; | ||
| 1765 | |||
| 1766 | l = xs_list_new(); | ||
| 1767 | |||
| 1768 | while (xs_list_next(ls, &v, &c)) { | ||
| 1769 | if ((f = fopen(v, "r")) != NULL) { | ||
| 1770 | xs *title = xs_readline(f); | ||
| 1771 | fclose(f); | ||
| 1772 | |||
| 1773 | title = xs_strip_i(title); | ||
| 1774 | |||
| 1775 | xs *v2 = xs_replace(v, ".id", ""); | ||
| 1776 | xs *l2 = xs_split(v2, "/"); | ||
| 1777 | |||
| 1778 | /* return [ list_id, list_title ] */ | ||
| 1779 | l = xs_list_append(l, xs_list_append(xs_list_new(), xs_list_get(l2, -1), title)); | ||
| 1780 | } | ||
| 1781 | } | ||
| 1782 | } | ||
| 1783 | |||
| 1784 | break; | ||
| 1785 | |||
| 1786 | case 1: /** create new list (list is the name) **/ | ||
| 1787 | { | ||
| 1788 | xs *lol = list_maint(user, NULL, 0); | ||
| 1789 | int c = 0; | ||
| 1790 | const xs_list *v; | ||
| 1791 | int add = 1; | ||
| 1792 | |||
| 1793 | /* check if this list name already exists */ | ||
| 1794 | while (xs_list_next(lol, &v, &c)) { | ||
| 1795 | if (strcmp(xs_list_get(v, 1), list) == 0) { | ||
| 1796 | add = 0; | ||
| 1797 | break; | ||
| 1798 | } | ||
| 1799 | } | ||
| 1800 | |||
| 1801 | if (add) { | ||
| 1802 | FILE *f; | ||
| 1803 | xs *dir = xs_fmt("%s/list/", user->basedir); | ||
| 1804 | xs *id = xs_fmt("%010x", time(NULL)); | ||
| 1805 | |||
| 1806 | mkdirx(dir); | ||
| 1807 | |||
| 1808 | xs *fn = xs_fmt("%s%s.id", dir, id); | ||
| 1809 | |||
| 1810 | if ((f = fopen(fn, "w")) != NULL) { | ||
| 1811 | fprintf(f, "%s\n", list); | ||
| 1812 | fclose(f); | ||
| 1813 | } | ||
| 1814 | |||
| 1815 | l = xs_stock(XSTYPE_TRUE); | ||
| 1816 | } | ||
| 1817 | else | ||
| 1818 | l = xs_stock(XSTYPE_FALSE); | ||
| 1819 | } | ||
| 1820 | |||
| 1821 | break; | ||
| 1822 | |||
| 1823 | case 2: /** delete list (list is the id) **/ | ||
| 1824 | { | ||
| 1825 | if (xs_is_hex(list)) { | ||
| 1826 | xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list); | ||
| 1827 | unlink(fn); | ||
| 1828 | |||
| 1829 | fn = xs_replace_i(fn, ".id", ".lst"); | ||
| 1830 | unlink(fn); | ||
| 1831 | |||
| 1832 | fn = xs_replace_i(fn, ".lst", ".idx"); | ||
| 1833 | unlink(fn); | ||
| 1834 | |||
| 1835 | fn = xs_str_cat(fn, ".bak"); | ||
| 1836 | unlink(fn); | ||
| 1837 | } | ||
| 1838 | } | ||
| 1839 | |||
| 1840 | break; | ||
| 1841 | |||
| 1842 | case 3: /** get list name **/ | ||
| 1843 | if (xs_is_hex(list)) { | ||
| 1844 | FILE *f; | ||
| 1845 | xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list); | ||
| 1846 | |||
| 1847 | if ((f = fopen(fn, "r")) != NULL) { | ||
| 1848 | l = xs_strip_i(xs_readline(f)); | ||
| 1849 | fclose(f); | ||
| 1850 | } | ||
| 1851 | } | ||
| 1852 | |||
| 1853 | break; | ||
| 1854 | } | ||
| 1855 | |||
| 1856 | return l; | ||
| 1857 | } | ||
| 1858 | |||
| 1859 | |||
| 1860 | xs_list *list_timeline(snac *user, const char *list, int skip, int show) | ||
| 1861 | /* returns the timeline of a list */ | ||
| 1862 | { | ||
| 1863 | xs_list *l = NULL; | ||
| 1864 | |||
| 1865 | if (!xs_is_hex(list)) | ||
| 1866 | return NULL; | ||
| 1867 | |||
| 1868 | xs *fn = xs_fmt("%s/list/%s.idx", user->basedir, list); | ||
| 1869 | |||
| 1870 | if (mtime(fn) > 0.0) | ||
| 1871 | l = index_list_desc(fn, skip, show); | ||
| 1872 | |||
| 1873 | return l; | ||
| 1874 | } | ||
| 1875 | |||
| 1876 | |||
| 1877 | xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op) | ||
| 1878 | /* list content management */ | ||
| 1879 | { | ||
| 1880 | xs_val *l = NULL; | ||
| 1881 | |||
| 1882 | if (!xs_is_hex(list)) | ||
| 1883 | return NULL; | ||
| 1884 | |||
| 1885 | if (actor_md5 != NULL && !xs_is_hex(actor_md5)) | ||
| 1886 | return NULL; | ||
| 1887 | |||
| 1888 | xs *fn = xs_fmt("%s/list/%s.lst", user->basedir, list); | ||
| 1889 | |||
| 1890 | switch (op) { | ||
| 1891 | case 0: /** list content **/ | ||
| 1892 | l = index_list(fn, XS_ALL); | ||
| 1893 | |||
| 1894 | break; | ||
| 1895 | |||
| 1896 | case 1: /** append actor to list **/ | ||
| 1897 | if (actor_md5 != NULL) { | ||
| 1898 | if (!index_in(fn, actor_md5)) | ||
| 1899 | index_add_md5(fn, actor_md5); | ||
| 1900 | } | ||
| 1901 | |||
| 1902 | break; | ||
| 1903 | |||
| 1904 | case 2: /** delete actor from list **/ | ||
| 1905 | if (actor_md5 != NULL) | ||
| 1906 | index_del_md5(fn, actor_md5); | ||
| 1907 | |||
| 1908 | break; | ||
| 1909 | |||
| 1910 | default: | ||
| 1911 | srv_log(xs_fmt("ERROR: list_content: bad op %d", op)); | ||
| 1912 | break; | ||
| 1913 | } | ||
| 1914 | |||
| 1915 | return l; | ||
| 1916 | } | ||
| 1917 | |||
| 1918 | |||
| 1919 | void list_distribute(snac *user, const char *who, const xs_dict *post) | ||
| 1920 | /* distributes the post to all appropriate lists */ | ||
| 1921 | { | ||
| 1922 | const char *id = xs_dict_get(post, "id"); | ||
| 1923 | |||
| 1924 | /* if who is not set, use the attributedTo in the message */ | ||
| 1925 | if (xs_is_null(who)) | ||
| 1926 | who = get_atto(post); | ||
| 1927 | |||
| 1928 | if (xs_type(who) == XSTYPE_STRING && xs_type(id) == XSTYPE_STRING) { | ||
| 1929 | xs *a_md5 = xs_md5_hex(who, strlen(who)); | ||
| 1930 | xs *i_md5 = xs_md5_hex(id, strlen(id)); | ||
| 1931 | xs *spec = xs_fmt("%s/list/" "*.lst", user->basedir); | ||
| 1932 | xs *ls = xs_glob(spec, 0, 0); | ||
| 1933 | int c = 0; | ||
| 1934 | const char *v; | ||
| 1935 | |||
| 1936 | while (xs_list_next(ls, &v, &c)) { | ||
| 1937 | /* is the actor in this list? */ | ||
| 1938 | if (index_in_md5(v, a_md5)) { | ||
| 1939 | /* it is; add post md5 to its timeline */ | ||
| 1940 | xs *idx = xs_replace(v, ".lst", ".idx"); | ||
| 1941 | index_add_md5(idx, i_md5); | ||
| 1942 | |||
| 1943 | snac_debug(user, 1, xs_fmt("listed post %s in %s", id, idx)); | ||
| 1944 | } | ||
| 1945 | } | ||
| 1946 | } | ||
| 1947 | } | ||
| 1948 | |||
| 1949 | |||
| 1723 | /** static data **/ | 1950 | /** static data **/ |
| 1724 | 1951 | ||
| 1725 | static int _load_raw_file(const char *fn, xs_val **data, int *size, | 1952 | static int _load_raw_file(const char *fn, xs_val **data, int *size, |
| @@ -1944,7 +2171,7 @@ void inbox_add(const char *inbox) | |||
| 1944 | void inbox_add_by_actor(const xs_dict *actor) | 2171 | void inbox_add_by_actor(const xs_dict *actor) |
| 1945 | /* collects an actor's shared inbox, if it has one */ | 2172 | /* collects an actor's shared inbox, if it has one */ |
| 1946 | { | 2173 | { |
| 1947 | char *v; | 2174 | const char *v; |
| 1948 | 2175 | ||
| 1949 | if (!xs_is_null(v = xs_dict_get(actor, "endpoints")) && | 2176 | if (!xs_is_null(v = xs_dict_get(actor, "endpoints")) && |
| 1950 | !xs_is_null(v = xs_dict_get(v, "sharedInbox"))) { | 2177 | !xs_is_null(v = xs_dict_get(v, "sharedInbox"))) { |
| @@ -1962,7 +2189,7 @@ xs_list *inbox_list(void) | |||
| 1962 | xs *spec = xs_fmt("%s/inbox/" "*", srv_basedir); | 2189 | xs *spec = xs_fmt("%s/inbox/" "*", srv_basedir); |
| 1963 | xs *files = xs_glob(spec, 0, 0); | 2190 | xs *files = xs_glob(spec, 0, 0); |
| 1964 | xs_list *p = files; | 2191 | xs_list *p = files; |
| 1965 | xs_val *v; | 2192 | const xs_val *v; |
| 1966 | 2193 | ||
| 1967 | while (xs_list_iter(&p, &v)) { | 2194 | while (xs_list_iter(&p, &v)) { |
| 1968 | FILE *f; | 2195 | FILE *f; |
| @@ -1987,9 +2214,10 @@ xs_list *inbox_list(void) | |||
| 1987 | 2214 | ||
| 1988 | xs_str *_instance_block_fn(const char *instance) | 2215 | xs_str *_instance_block_fn(const char *instance) |
| 1989 | { | 2216 | { |
| 1990 | xs *s1 = xs_replace(instance, "https:/" "/", ""); | 2217 | xs *s = xs_replace(instance, "http:/" "/", ""); |
| 2218 | xs *s1 = xs_replace(s, "https:/" "/", ""); | ||
| 1991 | xs *l = xs_split(s1, "/"); | 2219 | xs *l = xs_split(s1, "/"); |
| 1992 | char *p = xs_list_get(l, 0); | 2220 | const char *p = xs_list_get(l, 0); |
| 1993 | xs *md5 = xs_md5_hex(p, strlen(p)); | 2221 | xs *md5 = xs_md5_hex(p, strlen(p)); |
| 1994 | 2222 | ||
| 1995 | return xs_fmt("%s/block/%s", srv_basedir, md5); | 2223 | return xs_fmt("%s/block/%s", srv_basedir, md5); |
| @@ -2049,20 +2277,20 @@ int instance_unblock(const char *instance) | |||
| 2049 | } | 2277 | } |
| 2050 | 2278 | ||
| 2051 | 2279 | ||
| 2052 | /** content filtering **/ | 2280 | /** operations by content **/ |
| 2053 | 2281 | ||
| 2054 | int content_check(const char *file, const xs_dict *msg) | 2282 | int content_match(const char *file, const xs_dict *msg) |
| 2055 | /* checks if a message's content matches any of the regexes in file */ | 2283 | /* checks if a message's content matches any of the regexes in file */ |
| 2056 | /* file format: one regex per line */ | 2284 | /* file format: one regex per line */ |
| 2057 | { | 2285 | { |
| 2058 | xs *fn = xs_fmt("%s/%s", srv_basedir, file); | 2286 | xs *fn = xs_fmt("%s/%s", srv_basedir, file); |
| 2059 | FILE *f; | 2287 | FILE *f; |
| 2060 | int r = 0; | 2288 | int r = 0; |
| 2061 | char *v = xs_dict_get(msg, "content"); | 2289 | const char *v = xs_dict_get(msg, "content"); |
| 2062 | 2290 | ||
| 2063 | if (xs_type(v) == XSTYPE_STRING && *v) { | 2291 | if (xs_type(v) == XSTYPE_STRING && *v) { |
| 2064 | if ((f = fopen(fn, "r")) != NULL) { | 2292 | if ((f = fopen(fn, "r")) != NULL) { |
| 2065 | srv_debug(1, xs_fmt("content_check: loading regexes from %s", fn)); | 2293 | srv_debug(1, xs_fmt("content_match: loading regexes from %s", fn)); |
| 2066 | 2294 | ||
| 2067 | /* massage content (strip HTML tags, etc.) */ | 2295 | /* massage content (strip HTML tags, etc.) */ |
| 2068 | xs *c = xs_regex_replace(v, "<[^>]+>", " "); | 2296 | xs *c = xs_regex_replace(v, "<[^>]+>", " "); |
| @@ -2072,13 +2300,9 @@ int content_check(const char *file, const xs_dict *msg) | |||
| 2072 | while (!r && !feof(f)) { | 2300 | while (!r && !feof(f)) { |
| 2073 | xs *rx = xs_strip_i(xs_readline(f)); | 2301 | xs *rx = xs_strip_i(xs_readline(f)); |
| 2074 | 2302 | ||
| 2075 | if (*rx) { | 2303 | if (*rx && xs_regex_match(c, rx)) { |
| 2076 | xs *l = xs_regex_select_n(c, rx, 1); | 2304 | srv_debug(1, xs_fmt("content_match: match for '%s'", rx)); |
| 2077 | 2305 | r = 1; | |
| 2078 | if (xs_list_len(l)) { | ||
| 2079 | srv_debug(1, xs_fmt("content_check: match for '%s'", rx)); | ||
| 2080 | r = 1; | ||
| 2081 | } | ||
| 2082 | } | 2306 | } |
| 2083 | } | 2307 | } |
| 2084 | 2308 | ||
| @@ -2090,6 +2314,119 @@ int content_check(const char *file, const xs_dict *msg) | |||
| 2090 | } | 2314 | } |
| 2091 | 2315 | ||
| 2092 | 2316 | ||
| 2317 | xs_list *content_search(snac *user, const char *regex, | ||
| 2318 | int priv, int skip, int show, int max_secs, int *timeout) | ||
| 2319 | /* returns a list of posts which content matches the regex */ | ||
| 2320 | { | ||
| 2321 | if (regex == NULL || *regex == '\0') | ||
| 2322 | return xs_list_new(); | ||
| 2323 | |||
| 2324 | xs *i_regex = xs_tolower_i(xs_dup(regex)); | ||
| 2325 | |||
| 2326 | xs_set seen; | ||
| 2327 | |||
| 2328 | xs_set_init(&seen); | ||
| 2329 | |||
| 2330 | if (max_secs == 0) | ||
| 2331 | max_secs = 3; | ||
| 2332 | |||
| 2333 | time_t t = time(NULL) + max_secs; | ||
| 2334 | *timeout = 0; | ||
| 2335 | |||
| 2336 | /* iterate all timelines simultaneously */ | ||
| 2337 | xs_list *tls[3] = {0}; | ||
| 2338 | const char *md5s[3] = {0}; | ||
| 2339 | int c[3] = {0}; | ||
| 2340 | |||
| 2341 | tls[0] = timeline_simple_list(user, "public", 0, XS_ALL); /* public */ | ||
| 2342 | tls[1] = timeline_instance_list(0, XS_ALL); /* instance */ | ||
| 2343 | tls[2] = priv ? timeline_simple_list(user, "private", 0, XS_ALL) : xs_list_new(); /* private or none */ | ||
| 2344 | |||
| 2345 | /* first positioning */ | ||
| 2346 | for (int n = 0; n < 3; n++) | ||
| 2347 | xs_list_next(tls[n], &md5s[n], &c[n]); | ||
| 2348 | |||
| 2349 | show += skip; | ||
| 2350 | |||
| 2351 | while (show > 0) { | ||
| 2352 | /* timeout? */ | ||
| 2353 | if (time(NULL) > t) { | ||
| 2354 | *timeout = 1; | ||
| 2355 | break; | ||
| 2356 | } | ||
| 2357 | |||
| 2358 | /* find the newest post */ | ||
| 2359 | int newest = -1; | ||
| 2360 | double mtime = 0.0; | ||
| 2361 | |||
| 2362 | for (int n = 0; n < 3; n++) { | ||
| 2363 | if (md5s[n] != NULL) { | ||
| 2364 | xs *fn = _object_fn_by_md5(md5s[n], "content_search"); | ||
| 2365 | double mt = mtime(fn); | ||
| 2366 | |||
| 2367 | if (mt > mtime) { | ||
| 2368 | newest = n; | ||
| 2369 | mtime = mt; | ||
| 2370 | } | ||
| 2371 | } | ||
| 2372 | } | ||
| 2373 | |||
| 2374 | if (newest == -1) | ||
| 2375 | break; | ||
| 2376 | |||
| 2377 | const char *md5 = md5s[newest]; | ||
| 2378 | |||
| 2379 | /* advance the chosen timeline */ | ||
| 2380 | if (!xs_list_next(tls[newest], &md5s[newest], &c[newest])) | ||
| 2381 | md5s[newest] = NULL; | ||
| 2382 | |||
| 2383 | xs *post = NULL; | ||
| 2384 | |||
| 2385 | if (!valid_status(object_get_by_md5(md5, &post))) | ||
| 2386 | continue; | ||
| 2387 | |||
| 2388 | if (!xs_match(xs_dict_get_def(post, "type", "-"), POSTLIKE_OBJECT_TYPE)) | ||
| 2389 | continue; | ||
| 2390 | |||
| 2391 | const char *id = xs_dict_get(post, "id"); | ||
| 2392 | |||
| 2393 | if (id == NULL || is_hidden(user, id)) | ||
| 2394 | continue; | ||
| 2395 | |||
| 2396 | const char *content = xs_dict_get(post, "content"); | ||
| 2397 | |||
| 2398 | if (xs_is_null(content)) | ||
| 2399 | continue; | ||
| 2400 | |||
| 2401 | /* strip HTML */ | ||
| 2402 | xs *c = xs_regex_replace(content, "<[^>]+>", " "); | ||
| 2403 | c = xs_regex_replace_i(c, " {2,}", " "); | ||
| 2404 | c = xs_tolower_i(c); | ||
| 2405 | |||
| 2406 | /* apply regex */ | ||
| 2407 | if (xs_regex_match(c, i_regex)) { | ||
| 2408 | if (xs_set_add(&seen, md5) == 1) | ||
| 2409 | show--; | ||
| 2410 | } | ||
| 2411 | } | ||
| 2412 | |||
| 2413 | xs_list *r = xs_set_result(&seen); | ||
| 2414 | |||
| 2415 | if (skip) { | ||
| 2416 | /* BAD */ | ||
| 2417 | while (skip--) { | ||
| 2418 | r = xs_list_del(r, 0); | ||
| 2419 | } | ||
| 2420 | } | ||
| 2421 | |||
| 2422 | xs_free(tls[0]); | ||
| 2423 | xs_free(tls[1]); | ||
| 2424 | xs_free(tls[2]); | ||
| 2425 | |||
| 2426 | return r; | ||
| 2427 | } | ||
| 2428 | |||
| 2429 | |||
| 2093 | /** notifications **/ | 2430 | /** notifications **/ |
| 2094 | 2431 | ||
| 2095 | xs_str *notify_check_time(snac *snac, int reset) | 2432 | xs_str *notify_check_time(snac *snac, int reset) |
| @@ -2203,7 +2540,7 @@ xs_list *notify_list(snac *snac, int skip, int show) | |||
| 2203 | xs *spec = xs_fmt("%s/notify/" "*.json", snac->basedir); | 2540 | xs *spec = xs_fmt("%s/notify/" "*.json", snac->basedir); |
| 2204 | xs *lst = xs_glob(spec, 1, 0); | 2541 | xs *lst = xs_glob(spec, 1, 0); |
| 2205 | xs_list *p = lst; | 2542 | xs_list *p = lst; |
| 2206 | char *v; | 2543 | const char *v; |
| 2207 | 2544 | ||
| 2208 | while (xs_list_iter(&p, &v)) { | 2545 | while (xs_list_iter(&p, &v)) { |
| 2209 | char *p = strrchr(v, '.'); | 2546 | char *p = strrchr(v, '.'); |
| @@ -2231,7 +2568,7 @@ int notify_new_num(snac *snac) | |||
| 2231 | int cnt = 0; | 2568 | int cnt = 0; |
| 2232 | 2569 | ||
| 2233 | xs_list *p = lst; | 2570 | xs_list *p = lst; |
| 2234 | xs_str *v; | 2571 | const xs_str *v; |
| 2235 | 2572 | ||
| 2236 | while (xs_list_iter(&p, &v)) { | 2573 | while (xs_list_iter(&p, &v)) { |
| 2237 | xs *id = xs_strip_i(xs_dup(v)); | 2574 | xs *id = xs_strip_i(xs_dup(v)); |
| @@ -2253,7 +2590,7 @@ void notify_clear(snac *snac) | |||
| 2253 | xs *spec = xs_fmt("%s/notify/" "*", snac->basedir); | 2590 | xs *spec = xs_fmt("%s/notify/" "*", snac->basedir); |
| 2254 | xs *lst = xs_glob(spec, 0, 0); | 2591 | xs *lst = xs_glob(spec, 0, 0); |
| 2255 | xs_list *p = lst; | 2592 | xs_list *p = lst; |
| 2256 | xs_str *v; | 2593 | const xs_str *v; |
| 2257 | 2594 | ||
| 2258 | while (xs_list_iter(&p, &v)) | 2595 | while (xs_list_iter(&p, &v)) |
| 2259 | unlink(v); | 2596 | unlink(v); |
| @@ -2309,7 +2646,7 @@ void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retri | |||
| 2309 | /* enqueues an input message */ | 2646 | /* enqueues an input message */ |
| 2310 | { | 2647 | { |
| 2311 | xs *qmsg = _new_qmsg("input", msg, retries); | 2648 | xs *qmsg = _new_qmsg("input", msg, retries); |
| 2312 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2649 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2313 | xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); | 2650 | xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); |
| 2314 | 2651 | ||
| 2315 | qmsg = xs_dict_append(qmsg, "req", req); | 2652 | qmsg = xs_dict_append(qmsg, "req", req); |
| @@ -2324,7 +2661,7 @@ void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries) | |||
| 2324 | /* enqueues an input message from the shared input */ | 2661 | /* enqueues an input message from the shared input */ |
| 2325 | { | 2662 | { |
| 2326 | xs *qmsg = _new_qmsg("input", msg, retries); | 2663 | xs *qmsg = _new_qmsg("input", msg, retries); |
| 2327 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2664 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2328 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); | 2665 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); |
| 2329 | 2666 | ||
| 2330 | qmsg = xs_dict_append(qmsg, "req", req); | 2667 | qmsg = xs_dict_append(qmsg, "req", req); |
| @@ -2336,11 +2673,12 @@ void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries) | |||
| 2336 | 2673 | ||
| 2337 | 2674 | ||
| 2338 | void enqueue_output_raw(const char *keyid, const char *seckey, | 2675 | void enqueue_output_raw(const char *keyid, const char *seckey, |
| 2339 | xs_dict *msg, xs_str *inbox, int retries, int p_status) | 2676 | const xs_dict *msg, const xs_str *inbox, |
| 2677 | int retries, int p_status) | ||
| 2340 | /* enqueues an output message to an inbox */ | 2678 | /* enqueues an output message to an inbox */ |
| 2341 | { | 2679 | { |
| 2342 | xs *qmsg = _new_qmsg("output", msg, retries); | 2680 | xs *qmsg = _new_qmsg("output", msg, retries); |
| 2343 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2681 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2344 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); | 2682 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); |
| 2345 | 2683 | ||
| 2346 | xs *ns = xs_number_new(p_status); | 2684 | xs *ns = xs_number_new(p_status); |
| @@ -2360,7 +2698,8 @@ void enqueue_output_raw(const char *keyid, const char *seckey, | |||
| 2360 | } | 2698 | } |
| 2361 | 2699 | ||
| 2362 | 2700 | ||
| 2363 | void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries, int p_status) | 2701 | void enqueue_output(snac *snac, const xs_dict *msg, |
| 2702 | const xs_str *inbox, int retries, int p_status) | ||
| 2364 | /* enqueues an output message to an inbox */ | 2703 | /* enqueues an output message to an inbox */ |
| 2365 | { | 2704 | { |
| 2366 | if (xs_startswith(inbox, snac->actor)) { | 2705 | if (xs_startswith(inbox, snac->actor)) { |
| @@ -2368,13 +2707,14 @@ void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries, int p_ | |||
| 2368 | return; | 2707 | return; |
| 2369 | } | 2708 | } |
| 2370 | 2709 | ||
| 2371 | char *seckey = xs_dict_get(snac->key, "secret"); | 2710 | const char *seckey = xs_dict_get(snac->key, "secret"); |
| 2372 | 2711 | ||
| 2373 | enqueue_output_raw(snac->actor, seckey, msg, inbox, retries, p_status); | 2712 | enqueue_output_raw(snac->actor, seckey, msg, inbox, retries, p_status); |
| 2374 | } | 2713 | } |
| 2375 | 2714 | ||
| 2376 | 2715 | ||
| 2377 | void enqueue_output_by_actor(snac *snac, xs_dict *msg, const xs_str *actor, int retries) | 2716 | void enqueue_output_by_actor(snac *snac, const xs_dict *msg, |
| 2717 | const xs_str *actor, int retries) | ||
| 2378 | /* enqueues an output message for an actor */ | 2718 | /* enqueues an output message for an actor */ |
| 2379 | { | 2719 | { |
| 2380 | xs *inbox = get_actor_inbox(actor); | 2720 | xs *inbox = get_actor_inbox(actor); |
| @@ -2386,11 +2726,11 @@ void enqueue_output_by_actor(snac *snac, xs_dict *msg, const xs_str *actor, int | |||
| 2386 | } | 2726 | } |
| 2387 | 2727 | ||
| 2388 | 2728 | ||
| 2389 | void enqueue_email(xs_str *msg, int retries) | 2729 | void enqueue_email(const xs_str *msg, int retries) |
| 2390 | /* enqueues an email message to be sent */ | 2730 | /* enqueues an email message to be sent */ |
| 2391 | { | 2731 | { |
| 2392 | xs *qmsg = _new_qmsg("email", msg, retries); | 2732 | xs *qmsg = _new_qmsg("email", msg, retries); |
| 2393 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2733 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2394 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); | 2734 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); |
| 2395 | 2735 | ||
| 2396 | qmsg = _enqueue_put(fn, qmsg); | 2736 | qmsg = _enqueue_put(fn, qmsg); |
| @@ -2403,7 +2743,7 @@ void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id) | |||
| 2403 | /* enqueues a message to be sent via Telegram */ | 2743 | /* enqueues a message to be sent via Telegram */ |
| 2404 | { | 2744 | { |
| 2405 | xs *qmsg = _new_qmsg("telegram", msg, 0); | 2745 | xs *qmsg = _new_qmsg("telegram", msg, 0); |
| 2406 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2746 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2407 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); | 2747 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); |
| 2408 | 2748 | ||
| 2409 | qmsg = xs_dict_append(qmsg, "bot", bot); | 2749 | qmsg = xs_dict_append(qmsg, "bot", bot); |
| @@ -2418,7 +2758,7 @@ void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_t | |||
| 2418 | /* enqueues a message to be sent via ntfy */ | 2758 | /* enqueues a message to be sent via ntfy */ |
| 2419 | { | 2759 | { |
| 2420 | xs *qmsg = _new_qmsg("ntfy", msg, 0); | 2760 | xs *qmsg = _new_qmsg("ntfy", msg, 0); |
| 2421 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2761 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2422 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); | 2762 | xs *fn = xs_fmt("%s/queue/%s.json", srv_basedir, ntid); |
| 2423 | 2763 | ||
| 2424 | qmsg = xs_dict_append(qmsg, "ntfy_server", ntfy_server); | 2764 | qmsg = xs_dict_append(qmsg, "ntfy_server", ntfy_server); |
| @@ -2434,7 +2774,7 @@ void enqueue_message(snac *snac, const xs_dict *msg) | |||
| 2434 | /* enqueues an output message */ | 2774 | /* enqueues an output message */ |
| 2435 | { | 2775 | { |
| 2436 | xs *qmsg = _new_qmsg("message", msg, 0); | 2776 | xs *qmsg = _new_qmsg("message", msg, 0); |
| 2437 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2777 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2438 | xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); | 2778 | xs *fn = xs_fmt("%s/queue/%s.json", snac->basedir, ntid); |
| 2439 | 2779 | ||
| 2440 | qmsg = _enqueue_put(fn, qmsg); | 2780 | qmsg = _enqueue_put(fn, qmsg); |
| @@ -2458,67 +2798,47 @@ void enqueue_close_question(snac *user, const char *id, int end_secs) | |||
| 2458 | } | 2798 | } |
| 2459 | 2799 | ||
| 2460 | 2800 | ||
| 2461 | void enqueue_verify_links(snac *user) | 2801 | void enqueue_object_request(snac *user, const char *id, int forward_secs) |
| 2462 | /* enqueues a link verification */ | 2802 | /* enqueues the request of an object in the future */ |
| 2463 | { | 2803 | { |
| 2464 | xs *qmsg = _new_qmsg("verify_links", "", 0); | 2804 | xs *qmsg = _new_qmsg("object_request", id, 0); |
| 2465 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2805 | xs *ntid = tid(forward_secs); |
| 2466 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); | 2806 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); |
| 2807 | |||
| 2808 | qmsg = xs_dict_set(qmsg, "ntid", ntid); | ||
| 2467 | 2809 | ||
| 2468 | qmsg = _enqueue_put(fn, qmsg); | 2810 | qmsg = _enqueue_put(fn, qmsg); |
| 2469 | 2811 | ||
| 2470 | snac_debug(user, 1, xs_fmt("enqueue_verify_links %s", user->actor)); | 2812 | snac_debug(user, 0, xs_fmt("enqueue_object_request %s %d", id, forward_secs)); |
| 2471 | } | 2813 | } |
| 2472 | 2814 | ||
| 2473 | 2815 | ||
| 2474 | void enqueue_actor_refresh(snac *user, const char *actor) | 2816 | void enqueue_verify_links(snac *user) |
| 2475 | /* enqueues an actor refresh */ | 2817 | /* enqueues a link verification */ |
| 2476 | { | 2818 | { |
| 2477 | xs *qmsg = _new_qmsg("actor_refresh", "", 0); | 2819 | xs *qmsg = _new_qmsg("verify_links", "", 0); |
| 2478 | char *ntid = xs_dict_get(qmsg, "ntid"); | 2820 | const char *ntid = xs_dict_get(qmsg, "ntid"); |
| 2479 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); | 2821 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); |
| 2480 | 2822 | ||
| 2481 | qmsg = xs_dict_append(qmsg, "actor", actor); | ||
| 2482 | |||
| 2483 | qmsg = _enqueue_put(fn, qmsg); | 2823 | qmsg = _enqueue_put(fn, qmsg); |
| 2484 | 2824 | ||
| 2485 | snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor)); | 2825 | snac_debug(user, 1, xs_fmt("enqueue_verify_links %s", user->actor)); |
| 2486 | } | 2826 | } |
| 2487 | 2827 | ||
| 2488 | 2828 | ||
| 2489 | void enqueue_request_replies(snac *user, const char *id) | 2829 | void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs) |
| 2490 | /* enqueues a request for the replies of a message */ | 2830 | /* enqueues an actor refresh */ |
| 2491 | { | 2831 | { |
| 2492 | /* test first if this precise request is already in the queue */ | 2832 | xs *qmsg = _new_qmsg("actor_refresh", "", 0); |
| 2493 | xs *queue = user_queue(user); | 2833 | xs *ntid = tid(forward_secs); |
| 2494 | xs_list *p = queue; | ||
| 2495 | xs_str *v; | ||
| 2496 | |||
| 2497 | while (xs_list_iter(&p, &v)) { | ||
| 2498 | xs *q_item = queue_get(v); | ||
| 2499 | |||
| 2500 | if (q_item != NULL) { | ||
| 2501 | const char *type = xs_dict_get(q_item, "type"); | ||
| 2502 | const char *msg = xs_dict_get(q_item, "message"); | ||
| 2503 | |||
| 2504 | if (type && msg && strcmp(type, "request_replies") == 0 && strcmp(msg, id) == 0) { | ||
| 2505 | /* don't requeue */ | ||
| 2506 | snac_debug(user, 1, xs_fmt("enqueue_request_replies already here %s", id)); | ||
| 2507 | return; | ||
| 2508 | } | ||
| 2509 | } | ||
| 2510 | } | ||
| 2511 | |||
| 2512 | /* not there; enqueue the request with a small delay */ | ||
| 2513 | xs *qmsg = _new_qmsg("request_replies", id, 0); | ||
| 2514 | xs *ntid = tid(10); | ||
| 2515 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); | 2834 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); |
| 2516 | 2835 | ||
| 2517 | qmsg = xs_dict_set(qmsg, "ntid", ntid); | 2836 | qmsg = xs_dict_set(qmsg, "ntid", ntid); |
| 2837 | qmsg = xs_dict_append(qmsg, "actor", actor); | ||
| 2518 | 2838 | ||
| 2519 | qmsg = _enqueue_put(fn, qmsg); | 2839 | qmsg = _enqueue_put(fn, qmsg); |
| 2520 | 2840 | ||
| 2521 | snac_debug(user, 2, xs_fmt("enqueue_request_replies %s", id)); | 2841 | snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor)); |
| 2522 | } | 2842 | } |
| 2523 | 2843 | ||
| 2524 | 2844 | ||
| @@ -2528,14 +2848,14 @@ int was_question_voted(snac *user, const char *id) | |||
| 2528 | xs *children = object_children(id); | 2848 | xs *children = object_children(id); |
| 2529 | int voted = 0; | 2849 | int voted = 0; |
| 2530 | xs_list *p; | 2850 | xs_list *p; |
| 2531 | xs_str *md5; | 2851 | const xs_str *md5; |
| 2532 | 2852 | ||
| 2533 | p = children; | 2853 | p = children; |
| 2534 | while (xs_list_iter(&p, &md5)) { | 2854 | while (xs_list_iter(&p, &md5)) { |
| 2535 | xs *obj = NULL; | 2855 | xs *obj = NULL; |
| 2536 | 2856 | ||
| 2537 | if (valid_status(object_get_by_md5(md5, &obj))) { | 2857 | if (valid_status(object_get_by_md5(md5, &obj))) { |
| 2538 | char *atto = get_atto(obj); | 2858 | const char *atto = get_atto(obj); |
| 2539 | if (atto && strcmp(atto, user->actor) == 0 && | 2859 | if (atto && strcmp(atto, user->actor) == 0 && |
| 2540 | !xs_is_null(xs_dict_get(obj, "name"))) { | 2860 | !xs_is_null(xs_dict_get(obj, "name"))) { |
| 2541 | voted = 1; | 2861 | voted = 1; |
| @@ -2555,7 +2875,7 @@ xs_list *user_queue(snac *snac) | |||
| 2555 | xs_list *list = xs_list_new(); | 2875 | xs_list *list = xs_list_new(); |
| 2556 | time_t t = time(NULL); | 2876 | time_t t = time(NULL); |
| 2557 | xs_list *p; | 2877 | xs_list *p; |
| 2558 | xs_val *v; | 2878 | const xs_val *v; |
| 2559 | 2879 | ||
| 2560 | xs *fns = xs_glob(spec, 0, 0); | 2880 | xs *fns = xs_glob(spec, 0, 0); |
| 2561 | 2881 | ||
| @@ -2584,7 +2904,7 @@ xs_list *queue(void) | |||
| 2584 | xs_list *list = xs_list_new(); | 2904 | xs_list *list = xs_list_new(); |
| 2585 | time_t t = time(NULL); | 2905 | time_t t = time(NULL); |
| 2586 | xs_list *p; | 2906 | xs_list *p; |
| 2587 | xs_val *v; | 2907 | const xs_val *v; |
| 2588 | 2908 | ||
| 2589 | xs *fns = xs_glob(spec, 0, 0); | 2909 | xs *fns = xs_glob(spec, 0, 0); |
| 2590 | 2910 | ||
| @@ -2660,7 +2980,7 @@ static void _purge_dir(const char *dir, int days) | |||
| 2660 | xs *spec = xs_fmt("%s/" "*", dir); | 2980 | xs *spec = xs_fmt("%s/" "*", dir); |
| 2661 | xs *list = xs_glob(spec, 0, 0); | 2981 | xs *list = xs_glob(spec, 0, 0); |
| 2662 | xs_list *p; | 2982 | xs_list *p; |
| 2663 | xs_str *v; | 2983 | const xs_str *v; |
| 2664 | 2984 | ||
| 2665 | p = list; | 2985 | p = list; |
| 2666 | while (xs_list_iter(&p, &v)) | 2986 | while (xs_list_iter(&p, &v)) |
| @@ -2686,7 +3006,7 @@ void purge_server(void) | |||
| 2686 | xs *spec = xs_fmt("%s/object/??", srv_basedir); | 3006 | xs *spec = xs_fmt("%s/object/??", srv_basedir); |
| 2687 | xs *dirs = xs_glob(spec, 0, 0); | 3007 | xs *dirs = xs_glob(spec, 0, 0); |
| 2688 | xs_list *p; | 3008 | xs_list *p; |
| 2689 | xs_str *v; | 3009 | const xs_str *v; |
| 2690 | int cnt = 0; | 3010 | int cnt = 0; |
| 2691 | int icnt = 0; | 3011 | int icnt = 0; |
| 2692 | 3012 | ||
| @@ -2695,7 +3015,7 @@ void purge_server(void) | |||
| 2695 | p = dirs; | 3015 | p = dirs; |
| 2696 | while (xs_list_iter(&p, &v)) { | 3016 | while (xs_list_iter(&p, &v)) { |
| 2697 | xs_list *p2; | 3017 | xs_list *p2; |
| 2698 | xs_str *v2; | 3018 | const xs_str *v2; |
| 2699 | 3019 | ||
| 2700 | { | 3020 | { |
| 2701 | xs *spec2 = xs_fmt("%s/" "*.json", v); | 3021 | xs *spec2 = xs_fmt("%s/" "*.json", v); |
| @@ -2709,7 +3029,7 @@ void purge_server(void) | |||
| 2709 | if (mtime_nl(v2, &n_link) < mt && n_link < 2) { | 3029 | if (mtime_nl(v2, &n_link) < mt && n_link < 2) { |
| 2710 | xs *s1 = xs_replace(v2, ".json", ""); | 3030 | xs *s1 = xs_replace(v2, ".json", ""); |
| 2711 | xs *l = xs_split(s1, "/"); | 3031 | xs *l = xs_split(s1, "/"); |
| 2712 | char *md5 = xs_list_get(l, -1); | 3032 | const char *md5 = xs_list_get(l, -1); |
| 2713 | 3033 | ||
| 2714 | object_del_by_md5(md5); | 3034 | object_del_by_md5(md5); |
| 2715 | cnt++; | 3035 | cnt++; |
| @@ -2743,6 +3063,16 @@ void purge_server(void) | |||
| 2743 | } | 3063 | } |
| 2744 | } | 3064 | } |
| 2745 | } | 3065 | } |
| 3066 | |||
| 3067 | /* delete index backups */ | ||
| 3068 | xs *specb = xs_fmt("%s/" "*.bak", v); | ||
| 3069 | xs *bakfs = xs_glob(specb, 0, 0); | ||
| 3070 | |||
| 3071 | p2 = bakfs; | ||
| 3072 | while (xs_list_iter(&p2, &v2)) { | ||
| 3073 | unlink(v2); | ||
| 3074 | srv_debug(1, xs_fmt("purged %s", v2)); | ||
| 3075 | } | ||
| 2746 | } | 3076 | } |
| 2747 | } | 3077 | } |
| 2748 | 3078 | ||
| @@ -2764,7 +3094,7 @@ void purge_server(void) | |||
| 2764 | xs *spec2 = xs_fmt("%s/" "*.idx", v); | 3094 | xs *spec2 = xs_fmt("%s/" "*.idx", v); |
| 2765 | xs *files = xs_glob(spec2, 0, 0); | 3095 | xs *files = xs_glob(spec2, 0, 0); |
| 2766 | xs_list *p2; | 3096 | xs_list *p2; |
| 2767 | xs_str *v2; | 3097 | const xs_str *v2; |
| 2768 | 3098 | ||
| 2769 | p2 = files; | 3099 | p2 = files; |
| 2770 | while (xs_list_iter(&p2, &v2)) { | 3100 | while (xs_list_iter(&p2, &v2)) { |
| @@ -2791,7 +3121,7 @@ void purge_user(snac *snac) | |||
| 2791 | /* do the purge for this user */ | 3121 | /* do the purge for this user */ |
| 2792 | { | 3122 | { |
| 2793 | int priv_days, pub_days, user_days = 0; | 3123 | int priv_days, pub_days, user_days = 0; |
| 2794 | char *v; | 3124 | const char *v; |
| 2795 | int n; | 3125 | int n; |
| 2796 | 3126 | ||
| 2797 | priv_days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days")); | 3127 | priv_days = xs_number_get(xs_dict_get(srv_config, "timeline_purge_days")); |
| @@ -2823,6 +3153,19 @@ void purge_user(snac *snac) | |||
| 2823 | srv_debug(1, xs_fmt("purge: %s %d", idx, gc)); | 3153 | srv_debug(1, xs_fmt("purge: %s %d", idx, gc)); |
| 2824 | } | 3154 | } |
| 2825 | 3155 | ||
| 3156 | /* purge lists */ | ||
| 3157 | { | ||
| 3158 | xs *spec = xs_fmt("%s/list/" "*.idx", snac->basedir); | ||
| 3159 | xs *lol = xs_glob(spec, 0, 0); | ||
| 3160 | int c = 0; | ||
| 3161 | const char *v; | ||
| 3162 | |||
| 3163 | while (xs_list_next(lol, &v, &c)) { | ||
| 3164 | int gc = index_gc(v); | ||
| 3165 | srv_debug(1, xs_fmt("purge: %s %d", v, gc)); | ||
| 3166 | } | ||
| 3167 | } | ||
| 3168 | |||
| 2826 | /* unrelated to purging, but it's a janitorial process, so what the hell */ | 3169 | /* unrelated to purging, but it's a janitorial process, so what the hell */ |
| 2827 | verify_links(snac); | 3170 | verify_links(snac); |
| 2828 | } | 3171 | } |
| @@ -2833,7 +3176,8 @@ void purge_all(void) | |||
| 2833 | { | 3176 | { |
| 2834 | snac snac; | 3177 | snac snac; |
| 2835 | xs *list = user_list(); | 3178 | xs *list = user_list(); |
| 2836 | char *p, *uid; | 3179 | char *p; |
| 3180 | const char *uid; | ||
| 2837 | 3181 | ||
| 2838 | p = list; | 3182 | p = list; |
| 2839 | while (xs_list_iter(&p, &uid)) { | 3183 | while (xs_list_iter(&p, &uid)) { |
| @@ -2887,7 +3231,7 @@ void srv_archive(const char *direction, const char *url, xs_dict *req, | |||
| 2887 | if (p_size && payload) { | 3231 | if (p_size && payload) { |
| 2888 | xs *payload_fn = NULL; | 3232 | xs *payload_fn = NULL; |
| 2889 | xs *payload_fn_raw = NULL; | 3233 | xs *payload_fn_raw = NULL; |
| 2890 | char *v = xs_dict_get(req, "content-type"); | 3234 | const char *v = xs_dict_get(req, "content-type"); |
| 2891 | 3235 | ||
| 2892 | if (v && xs_str_in(v, "json") != -1) { | 3236 | if (v && xs_str_in(v, "json") != -1) { |
| 2893 | payload_fn = xs_fmt("%s/payload.json", dir); | 3237 | payload_fn = xs_fmt("%s/payload.json", dir); |
| @@ -2918,7 +3262,7 @@ void srv_archive(const char *direction, const char *url, xs_dict *req, | |||
| 2918 | 3262 | ||
| 2919 | if (b_size && body) { | 3263 | if (b_size && body) { |
| 2920 | xs *body_fn = NULL; | 3264 | xs *body_fn = NULL; |
| 2921 | char *v = xs_dict_get(headers, "content-type"); | 3265 | const char *v = xs_dict_get(headers, "content-type"); |
| 2922 | 3266 | ||
| 2923 | if (v && xs_str_in(v, "json") != -1) { | 3267 | if (v && xs_str_in(v, "json") != -1) { |
| 2924 | body_fn = xs_fmt("%s/body.json", dir); | 3268 | body_fn = xs_fmt("%s/body.json", dir); |
| @@ -2987,7 +3331,7 @@ void srv_archive_error(const char *prefix, const xs_str *err, | |||
| 2987 | } | 3331 | } |
| 2988 | 3332 | ||
| 2989 | 3333 | ||
| 2990 | void srv_archive_qitem(char *prefix, xs_dict *q_item) | 3334 | void srv_archive_qitem(const char *prefix, xs_dict *q_item) |
| 2991 | /* archives a q_item in the error folder */ | 3335 | /* archives a q_item in the error folder */ |
| 2992 | { | 3336 | { |
| 2993 | xs *ntid = tid(0); | 3337 | xs *ntid = tid(0); |
| @@ -143,6 +143,14 @@ times the sending will be retried. | |||
| 143 | The number of minutes to wait before the failed posting of a message is | 143 | The number of minutes to wait before the failed posting of a message is |
| 144 | retried. This is not linear, but multipled by the number of retries | 144 | retried. This is not linear, but multipled by the number of retries |
| 145 | already done. | 145 | already done. |
| 146 | .It Ic queue_timeout | ||
| 147 | The maximum number of seconds to wait when sending a message from the queue. | ||
| 148 | .It Ic queue_timeout_2 | ||
| 149 | The maximum number of seconds to wait when sending a message from the queue | ||
| 150 | to those servers that went timeout in the previous retry. If you want to | ||
| 151 | give slow servers a chance to receive your messages, you can increase this | ||
| 152 | value (but also take into account that processing the queue will take longer | ||
| 153 | while waiting for these molasses to respond). | ||
| 146 | .It Ic max_timeline_entries | 154 | .It Ic max_timeline_entries |
| 147 | This is the maximum timeline entries shown in the web interface. | 155 | This is the maximum timeline entries shown in the web interface. |
| 148 | .It Ic timeline_purge_days | 156 | .It Ic timeline_purge_days |
| @@ -209,6 +217,13 @@ with a large number of users. | |||
| 209 | If this numeric value (in seconds) is set, any activity coming from an account | 217 | If this numeric value (in seconds) is set, any activity coming from an account |
| 210 | that was created more recently than that will be rejected. This may be used | 218 | that was created more recently than that will be rejected. This may be used |
| 211 | to mitigate spam from automatically created accounts. | 219 | to mitigate spam from automatically created accounts. |
| 220 | .It Ic protocol | ||
| 221 | This string value contains the protocol (schema) to be used in URLs. If not | ||
| 222 | set, it defaults to "https". If you run | ||
| 223 | .Nm | ||
| 224 | as part of a hidden network like Tor or I2P that doesn't have a TLS / | ||
| 225 | Certificate infrastructure, you need to set it to "http". Don't change it | ||
| 226 | unless you know what you are doing. | ||
| 212 | .El | 227 | .El |
| 213 | .Pp | 228 | .Pp |
| 214 | You must restart the server to make effective these changes. | 229 | You must restart the server to make effective these changes. |
| @@ -82,7 +82,8 @@ static xs_str *format_line(const char *line, xs_list **attach) | |||
| 82 | /* formats a line */ | 82 | /* formats a line */ |
| 83 | { | 83 | { |
| 84 | xs_str *s = xs_str_new(NULL); | 84 | xs_str *s = xs_str_new(NULL); |
| 85 | char *p, *v; | 85 | char *p; |
| 86 | const char *v; | ||
| 86 | 87 | ||
| 87 | /* split by markup */ | 88 | /* split by markup */ |
| 88 | xs *sm = xs_regex_split(line, | 89 | xs *sm = xs_regex_split(line, |
| @@ -155,7 +156,8 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag | |||
| 155 | int in_pre = 0; | 156 | int in_pre = 0; |
| 156 | int in_blq = 0; | 157 | int in_blq = 0; |
| 157 | xs *list; | 158 | xs *list; |
| 158 | char *p, *v; | 159 | char *p; |
| 160 | const char *v; | ||
| 159 | 161 | ||
| 160 | /* work by lines */ | 162 | /* work by lines */ |
| 161 | list = xs_split(content, "\n"); | 163 | list = xs_split(content, "\n"); |
| @@ -234,14 +236,14 @@ xs_str *not_really_markdown(const char *content, xs_list **attach, xs_list **tag | |||
| 234 | /* traditional emoticons */ | 236 | /* traditional emoticons */ |
| 235 | xs *d = emojis(); | 237 | xs *d = emojis(); |
| 236 | int c = 0; | 238 | int c = 0; |
| 237 | char *k, *v; | 239 | const char *k, *v; |
| 238 | 240 | ||
| 239 | while (xs_dict_next(d, &k, &v, &c)) { | 241 | while (xs_dict_next(d, &k, &v, &c)) { |
| 240 | const char *t = NULL; | 242 | const char *t = NULL; |
| 241 | 243 | ||
| 242 | /* is it an URL to an image? */ | 244 | /* is it an URL to an image? */ |
| 243 | if (xs_startswith(v, "https:/" "/") && xs_startswith((t = xs_mime_by_ext(v)), "image/")) { | 245 | if (xs_startswith(v, "https:/" "/") && xs_startswith((t = xs_mime_by_ext(v)), "image/")) { |
| 244 | if (tag) { | 246 | if (tag && xs_str_in(s, k) != -1) { |
| 245 | /* add the emoji to the tag list */ | 247 | /* add the emoji to the tag list */ |
| 246 | xs *e = xs_dict_new(); | 248 | xs *e = xs_dict_new(); |
| 247 | xs *i = xs_dict_new(); | 249 | xs *i = xs_dict_new(); |
| @@ -280,7 +282,8 @@ xs_str *sanitize(const char *content) | |||
| 280 | xs_str *s = xs_str_new(NULL); | 282 | xs_str *s = xs_str_new(NULL); |
| 281 | xs *sl; | 283 | xs *sl; |
| 282 | int n = 0; | 284 | int n = 0; |
| 283 | char *p, *v; | 285 | char *p; |
| 286 | const char *v; | ||
| 284 | 287 | ||
| 285 | sl = xs_regex_split(content, "</?[^>]+>"); | 288 | sl = xs_regex_split(content, "</?[^>]+>"); |
| 286 | 289 | ||
| @@ -311,9 +314,9 @@ xs_str *sanitize(const char *content) | |||
| 311 | 314 | ||
| 312 | s = xs_str_cat(s, s2); | 315 | s = xs_str_cat(s, s2); |
| 313 | } else { | 316 | } else { |
| 314 | /* else? just show it with encoded code.. that's it. */ | 317 | /* treat end of divs as paragraph breaks */ |
| 315 | xs *el = encode_html(v); | 318 | if (strcmp(v, "</div>")) |
| 316 | s = xs_str_cat(s, el); | 319 | s = xs_str_cat(s, "<p>"); |
| 317 | } | 320 | } |
| 318 | } | 321 | } |
| 319 | else { | 322 | else { |
| @@ -41,7 +41,7 @@ int login(snac *snac, const xs_dict *headers) | |||
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | 43 | ||
| 44 | xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems) | 44 | xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems) |
| 45 | /* replaces all the :shortnames: with the emojis in tag */ | 45 | /* replaces all the :shortnames: with the emojis in tag */ |
| 46 | { | 46 | { |
| 47 | if (!xs_is_null(tag)) { | 47 | if (!xs_is_null(tag)) { |
| @@ -55,20 +55,20 @@ xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems) | |||
| 55 | tag_list = xs_dup(tag); | 55 | tag_list = xs_dup(tag); |
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | xs *style = xs_fmt("height: %dem; vertical-align: middle;", ems); | 58 | xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems); |
| 59 | 59 | ||
| 60 | xs_list *p = tag_list; | 60 | const char *v; |
| 61 | char *v; | 61 | int c = 0; |
| 62 | 62 | ||
| 63 | while (xs_list_iter(&p, &v)) { | 63 | while (xs_list_next(tag_list, &v, &c)) { |
| 64 | char *t = xs_dict_get(v, "type"); | 64 | const char *t = xs_dict_get(v, "type"); |
| 65 | 65 | ||
| 66 | if (t && strcmp(t, "Emoji") == 0) { | 66 | if (t && strcmp(t, "Emoji") == 0) { |
| 67 | char *n = xs_dict_get(v, "name"); | 67 | const char *n = xs_dict_get(v, "name"); |
| 68 | char *i = xs_dict_get(v, "icon"); | 68 | const char *i = xs_dict_get(v, "icon"); |
| 69 | 69 | ||
| 70 | if (n && i) { | 70 | if (n && i) { |
| 71 | char *u = xs_dict_get(i, "url"); | 71 | const char *u = xs_dict_get(i, "url"); |
| 72 | xs_html *img = xs_html_sctag("img", | 72 | xs_html *img = xs_html_sctag("img", |
| 73 | xs_html_attr("loading", "lazy"), | 73 | xs_html_attr("loading", "lazy"), |
| 74 | xs_html_attr("src", u), | 74 | xs_html_attr("src", u), |
| @@ -88,7 +88,7 @@ xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems) | |||
| 88 | xs_str *actor_name(xs_dict *actor) | 88 | xs_str *actor_name(xs_dict *actor) |
| 89 | /* gets the actor name */ | 89 | /* gets the actor name */ |
| 90 | { | 90 | { |
| 91 | char *v; | 91 | const char *v; |
| 92 | 92 | ||
| 93 | if (xs_is_null((v = xs_dict_get(actor, "name"))) || *v == '\0') { | 93 | if (xs_is_null((v = xs_dict_get(actor, "name"))) || *v == '\0') { |
| 94 | if (xs_is_null(v = xs_dict_get(actor, "preferredUsername")) || *v == '\0') { | 94 | if (xs_is_null(v = xs_dict_get(actor, "preferredUsername")) || *v == '\0') { |
| @@ -106,7 +106,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 106 | xs_html *actor_icon = xs_html_tag("p", NULL); | 106 | xs_html *actor_icon = xs_html_tag("p", NULL); |
| 107 | 107 | ||
| 108 | xs *avatar = NULL; | 108 | xs *avatar = NULL; |
| 109 | char *v; | 109 | const char *v; |
| 110 | int fwing = 0; | 110 | int fwing = 0; |
| 111 | int fwer = 0; | 111 | int fwer = 0; |
| 112 | 112 | ||
| @@ -125,7 +125,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 125 | if (avatar == NULL) | 125 | if (avatar == NULL) |
| 126 | avatar = xs_fmt("data:image/png;base64, %s", default_avatar_base64()); | 126 | avatar = xs_fmt("data:image/png;base64, %s", default_avatar_base64()); |
| 127 | 127 | ||
| 128 | char *actor_id = xs_dict_get(actor, "id"); | 128 | const char *actor_id = xs_dict_get(actor, "id"); |
| 129 | xs *href = NULL; | 129 | xs *href = NULL; |
| 130 | 130 | ||
| 131 | if (user) { | 131 | if (user) { |
| @@ -216,7 +216,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 216 | } | 216 | } |
| 217 | 217 | ||
| 218 | { | 218 | { |
| 219 | char *username, *id; | 219 | const char *username, *id; |
| 220 | 220 | ||
| 221 | if (xs_is_null(username = xs_dict_get(actor, "preferredUsername")) || *username == '\0') { | 221 | if (xs_is_null(username = xs_dict_get(actor, "preferredUsername")) || *username == '\0') { |
| 222 | /* This should never be reached */ | 222 | /* This should never be reached */ |
| @@ -244,19 +244,19 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 244 | } | 244 | } |
| 245 | 245 | ||
| 246 | 246 | ||
| 247 | xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg) | 247 | xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg) |
| 248 | { | 248 | { |
| 249 | xs *actor = NULL; | 249 | xs *actor = NULL; |
| 250 | xs_html *actor_icon = NULL; | 250 | xs_html *actor_icon = NULL; |
| 251 | 251 | ||
| 252 | if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) { | 252 | if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) { |
| 253 | char *date = NULL; | 253 | const char *date = NULL; |
| 254 | char *udate = NULL; | 254 | const char *udate = NULL; |
| 255 | char *url = NULL; | 255 | const char *url = NULL; |
| 256 | int priv = 0; | 256 | int priv = 0; |
| 257 | const char *type = xs_dict_get(msg, "type"); | 257 | const char *type = xs_dict_get(msg, "type"); |
| 258 | 258 | ||
| 259 | if (xs_match(type, "Note|Question|Page|Article|Video")) | 259 | if (xs_match(type, POSTLIKE_OBJECT_TYPE)) |
| 260 | url = xs_dict_get(msg, "id"); | 260 | url = xs_dict_get(msg, "id"); |
| 261 | 261 | ||
| 262 | priv = !is_msg_public(msg); | 262 | priv = !is_msg_public(msg); |
| @@ -271,14 +271,14 @@ xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg) | |||
| 271 | } | 271 | } |
| 272 | 272 | ||
| 273 | 273 | ||
| 274 | xs_html *html_note(snac *user, char *summary, | 274 | xs_html *html_note(snac *user, const char *summary, |
| 275 | char *div_id, char *form_id, | 275 | const char *div_id, const char *form_id, |
| 276 | char *ta_plh, char *ta_content, | 276 | const char *ta_plh, const char *ta_content, |
| 277 | char *edit_id, char *actor_id, | 277 | const char *edit_id, const char *actor_id, |
| 278 | xs_val *cw_yn, char *cw_text, | 278 | const xs_val *cw_yn, const char *cw_text, |
| 279 | xs_val *mnt_only, char *redir, | 279 | const xs_val *mnt_only, const char *redir, |
| 280 | char *in_reply_to, int poll, | 280 | const char *in_reply_to, int poll, |
| 281 | char *att_file, char *att_alt_text) | 281 | const char *att_file, const char *att_alt_text) |
| 282 | { | 282 | { |
| 283 | xs *action = xs_fmt("%s/admin/note", user->actor); | 283 | xs *action = xs_fmt("%s/admin/note", user->actor); |
| 284 | 284 | ||
| @@ -460,9 +460,11 @@ static xs_html *html_base_head(void) | |||
| 460 | /* add server CSS and favicon */ | 460 | /* add server CSS and favicon */ |
| 461 | xs *f; | 461 | xs *f; |
| 462 | f = xs_fmt("%s/favicon.ico", srv_baseurl); | 462 | f = xs_fmt("%s/favicon.ico", srv_baseurl); |
| 463 | xs_list *p = xs_dict_get(srv_config, "cssurls"); | 463 | const xs_list *p = xs_dict_get(srv_config, "cssurls"); |
| 464 | char *v; | 464 | const char *v; |
| 465 | while (xs_list_iter(&p, &v)) { | 465 | int c = 0; |
| 466 | |||
| 467 | while (xs_list_next(p, &v, &c)) { | ||
| 466 | xs_html_add(head, | 468 | xs_html_add(head, |
| 467 | xs_html_sctag("link", | 469 | xs_html_sctag("link", |
| 468 | xs_html_attr("rel", "stylesheet"), | 470 | xs_html_attr("rel", "stylesheet"), |
| @@ -498,8 +500,8 @@ xs_html *html_instance_head(void) | |||
| 498 | } | 500 | } |
| 499 | } | 501 | } |
| 500 | 502 | ||
| 501 | char *host = xs_dict_get(srv_config, "host"); | 503 | const char *host = xs_dict_get(srv_config, "host"); |
| 502 | char *title = xs_dict_get(srv_config, "title"); | 504 | const char *title = xs_dict_get(srv_config, "title"); |
| 503 | 505 | ||
| 504 | xs_html_add(head, | 506 | xs_html_add(head, |
| 505 | xs_html_tag("title", | 507 | xs_html_tag("title", |
| @@ -509,12 +511,12 @@ xs_html *html_instance_head(void) | |||
| 509 | } | 511 | } |
| 510 | 512 | ||
| 511 | 513 | ||
| 512 | static xs_html *html_instance_body(char *tag) | 514 | static xs_html *html_instance_body(void) |
| 513 | { | 515 | { |
| 514 | char *host = xs_dict_get(srv_config, "host"); | 516 | const char *host = xs_dict_get(srv_config, "host"); |
| 515 | char *sdesc = xs_dict_get(srv_config, "short_description"); | 517 | const char *sdesc = xs_dict_get(srv_config, "short_description"); |
| 516 | char *email = xs_dict_get(srv_config, "admin_email"); | 518 | const char *email = xs_dict_get(srv_config, "admin_email"); |
| 517 | char *acct = xs_dict_get(srv_config, "admin_account"); | 519 | const char *acct = xs_dict_get(srv_config, "admin_account"); |
| 518 | 520 | ||
| 519 | xs *blurb = xs_replace(snac_blurb, "%host%", host); | 521 | xs *blurb = xs_replace(snac_blurb, "%host%", host); |
| 520 | 522 | ||
| @@ -560,16 +562,6 @@ static xs_html *html_instance_body(char *tag) | |||
| 560 | xs_html_text(handle))))); | 562 | xs_html_text(handle))))); |
| 561 | } | 563 | } |
| 562 | 564 | ||
| 563 | { | ||
| 564 | xs *l = tag ? xs_fmt(L("Search results for #%s"), tag) : | ||
| 565 | xs_dup(L("Recent posts by users in this instance")); | ||
| 566 | |||
| 567 | xs_html_add(body, | ||
| 568 | xs_html_tag("h2", | ||
| 569 | xs_html_attr("class", "snac-header"), | ||
| 570 | xs_html_text(l))); | ||
| 571 | } | ||
| 572 | |||
| 573 | return body; | 565 | return body; |
| 574 | } | 566 | } |
| 575 | 567 | ||
| @@ -749,7 +741,17 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 749 | xs_html_text(" - "), | 741 | xs_html_text(" - "), |
| 750 | xs_html_tag("a", | 742 | xs_html_tag("a", |
| 751 | xs_html_attr("href", instance_url), | 743 | xs_html_attr("href", instance_url), |
| 752 | xs_html_text(L("instance")))); | 744 | xs_html_text(L("instance"))), |
| 745 | xs_html_text(" "), | ||
| 746 | xs_html_tag("form", | ||
| 747 | xs_html_attr("style", "display: inline!important"), | ||
| 748 | xs_html_attr("class", "snac-search-box"), | ||
| 749 | xs_html_attr("action", admin_url), | ||
| 750 | xs_html_sctag("input", | ||
| 751 | xs_html_attr("type", "text"), | ||
| 752 | xs_html_attr("name", "q"), | ||
| 753 | xs_html_attr("title", L("Search posts by content (regular expression) or #tag")), | ||
| 754 | xs_html_attr("placeholder", L("Content search"))))); | ||
| 753 | } | 755 | } |
| 754 | 756 | ||
| 755 | xs_html_add(body, | 757 | xs_html_add(body, |
| @@ -760,7 +762,7 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 760 | xs_html_attr("class", "h-card snac-top-user")); | 762 | xs_html_attr("class", "h-card snac-top-user")); |
| 761 | 763 | ||
| 762 | if (read_only) { | 764 | if (read_only) { |
| 763 | char *header = xs_dict_get(user->config, "header"); | 765 | const char *header = xs_dict_get(user->config, "header"); |
| 764 | if (header && *header) { | 766 | if (header && *header) { |
| 765 | xs_html_add(top_user, | 767 | xs_html_add(top_user, |
| 766 | xs_html_tag("div", | 768 | xs_html_tag("div", |
| @@ -797,10 +799,10 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 797 | xs_html_add(top_user, | 799 | xs_html_add(top_user, |
| 798 | top_user_bio); | 800 | top_user_bio); |
| 799 | 801 | ||
| 800 | xs_dict *metadata = xs_dict_get(user->config, "metadata"); | 802 | const xs_dict *metadata = xs_dict_get(user->config, "metadata"); |
| 801 | if (xs_type(metadata) == XSTYPE_DICT) { | 803 | if (xs_type(metadata) == XSTYPE_DICT) { |
| 802 | xs_str *k; | 804 | const xs_str *k; |
| 803 | xs_str *v; | 805 | const xs_str *v; |
| 804 | 806 | ||
| 805 | xs_dict *val_links = user->links; | 807 | xs_dict *val_links = user->links; |
| 806 | if (xs_is_null(val_links)) | 808 | if (xs_is_null(val_links)) |
| @@ -813,10 +815,10 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 813 | while (xs_dict_next(metadata, &k, &v, &c)) { | 815 | while (xs_dict_next(metadata, &k, &v, &c)) { |
| 814 | xs_html *value; | 816 | xs_html *value; |
| 815 | 817 | ||
| 816 | if (xs_startswith(v, "https:/" "/")) { | 818 | if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/")) { |
| 817 | /* is this link validated? */ | 819 | /* is this link validated? */ |
| 818 | xs *verified_link = NULL; | 820 | xs *verified_link = NULL; |
| 819 | xs_number *val_time = xs_dict_get(val_links, v); | 821 | const xs_number *val_time = xs_dict_get(val_links, v); |
| 820 | 822 | ||
| 821 | if (xs_type(val_time) == XSTYPE_NUMBER) { | 823 | if (xs_type(val_time) == XSTYPE_NUMBER) { |
| 822 | time_t t = xs_number_get(val_time); | 824 | time_t t = xs_number_get(val_time); |
| @@ -928,7 +930,7 @@ xs_html *html_top_controls(snac *snac) | |||
| 928 | 930 | ||
| 929 | /** user settings **/ | 931 | /** user settings **/ |
| 930 | 932 | ||
| 931 | char *email = "[disabled by admin]"; | 933 | const char *email = "[disabled by admin]"; |
| 932 | 934 | ||
| 933 | if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) { | 935 | if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) { |
| 934 | email = xs_dict_get(snac->config_o, "email"); | 936 | email = xs_dict_get(snac->config_o, "email"); |
| @@ -940,40 +942,40 @@ xs_html *html_top_controls(snac *snac) | |||
| 940 | } | 942 | } |
| 941 | } | 943 | } |
| 942 | 944 | ||
| 943 | char *cw = xs_dict_get(snac->config, "cw"); | 945 | const char *cw = xs_dict_get(snac->config, "cw"); |
| 944 | if (xs_is_null(cw)) | 946 | if (xs_is_null(cw)) |
| 945 | cw = ""; | 947 | cw = ""; |
| 946 | 948 | ||
| 947 | char *telegram_bot = xs_dict_get(snac->config, "telegram_bot"); | 949 | const char *telegram_bot = xs_dict_get(snac->config, "telegram_bot"); |
| 948 | if (xs_is_null(telegram_bot)) | 950 | if (xs_is_null(telegram_bot)) |
| 949 | telegram_bot = ""; | 951 | telegram_bot = ""; |
| 950 | 952 | ||
| 951 | char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id"); | 953 | const char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id"); |
| 952 | if (xs_is_null(telegram_chat_id)) | 954 | if (xs_is_null(telegram_chat_id)) |
| 953 | telegram_chat_id = ""; | 955 | telegram_chat_id = ""; |
| 954 | 956 | ||
| 955 | char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); | 957 | const char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); |
| 956 | if (xs_is_null(ntfy_server)) | 958 | if (xs_is_null(ntfy_server)) |
| 957 | ntfy_server = ""; | 959 | ntfy_server = ""; |
| 958 | 960 | ||
| 959 | char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); | 961 | const char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); |
| 960 | if (xs_is_null(ntfy_token)) | 962 | if (xs_is_null(ntfy_token)) |
| 961 | ntfy_token = ""; | 963 | ntfy_token = ""; |
| 962 | 964 | ||
| 963 | char *purge_days = xs_dict_get(snac->config, "purge_days"); | 965 | const char *purge_days = xs_dict_get(snac->config, "purge_days"); |
| 964 | if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER) | 966 | if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER) |
| 965 | purge_days = (char *)xs_number_str(purge_days); | 967 | purge_days = (char *)xs_number_str(purge_days); |
| 966 | else | 968 | else |
| 967 | purge_days = "0"; | 969 | purge_days = "0"; |
| 968 | 970 | ||
| 969 | xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown"); | 971 | const xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown"); |
| 970 | xs_val *bot = xs_dict_get(snac->config, "bot"); | 972 | const xs_val *bot = xs_dict_get(snac->config, "bot"); |
| 971 | xs_val *a_private = xs_dict_get(snac->config, "private"); | 973 | const xs_val *a_private = xs_dict_get(snac->config, "private"); |
| 972 | 974 | ||
| 973 | xs *metadata = xs_str_new(NULL); | 975 | xs *metadata = xs_str_new(NULL); |
| 974 | xs_dict *md = xs_dict_get(snac->config, "metadata"); | 976 | const xs_dict *md = xs_dict_get(snac->config, "metadata"); |
| 975 | xs_str *k; | 977 | const xs_str *k; |
| 976 | xs_str *v; | 978 | const xs_str *v; |
| 977 | 979 | ||
| 978 | int c = 0; | 980 | int c = 0; |
| 979 | while (xs_dict_next(md, &k, &v, &c)) { | 981 | while (xs_dict_next(md, &k, &v, &c)) { |
| @@ -1158,13 +1160,14 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg) | |||
| 1158 | /* returns a string with the mentions in msg */ | 1160 | /* returns a string with the mentions in msg */ |
| 1159 | { | 1161 | { |
| 1160 | xs_str *s = xs_str_new(NULL); | 1162 | xs_str *s = xs_str_new(NULL); |
| 1161 | char *list = xs_dict_get(msg, "tag"); | 1163 | const char *list = xs_dict_get(msg, "tag"); |
| 1162 | char *v; | 1164 | const char *v; |
| 1165 | int c = 0; | ||
| 1163 | 1166 | ||
| 1164 | while (xs_list_iter(&list, &v)) { | 1167 | while (xs_list_next(list, &v, &c)) { |
| 1165 | char *type = xs_dict_get(v, "type"); | 1168 | const char *type = xs_dict_get(v, "type"); |
| 1166 | char *href = xs_dict_get(v, "href"); | 1169 | const char *href = xs_dict_get(v, "href"); |
| 1167 | char *name = xs_dict_get(v, "name"); | 1170 | const char *name = xs_dict_get(v, "name"); |
| 1168 | 1171 | ||
| 1169 | if (type && strcmp(type, "Mention") == 0 && | 1172 | if (type && strcmp(type, "Mention") == 0 && |
| 1170 | href && strcmp(href, snac->actor) != 0 && name) { | 1173 | href && strcmp(href, snac->actor) != 0 && name) { |
| @@ -1208,10 +1211,11 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg) | |||
| 1208 | } | 1211 | } |
| 1209 | 1212 | ||
| 1210 | 1213 | ||
| 1211 | xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const char *md5) | 1214 | xs_html *html_entry_controls(snac *snac, const char *actor, |
| 1215 | const xs_dict *msg, const char *md5) | ||
| 1212 | { | 1216 | { |
| 1213 | char *id = xs_dict_get(msg, "id"); | 1217 | const char *id = xs_dict_get(msg, "id"); |
| 1214 | char *group = xs_dict_get(msg, "audience"); | 1218 | const char *group = xs_dict_get(msg, "audience"); |
| 1215 | 1219 | ||
| 1216 | xs *likes = object_likes(id); | 1220 | xs *likes = object_likes(id); |
| 1217 | xs *boosts = object_announces(id); | 1221 | xs *boosts = object_announces(id); |
| @@ -1265,8 +1269,8 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1265 | } | 1269 | } |
| 1266 | 1270 | ||
| 1267 | if (is_msg_public(msg)) { | 1271 | if (is_msg_public(msg)) { |
| 1268 | if (strcmp(actor, snac->actor) == 0 || xs_list_in(boosts, snac->md5) == -1) { | 1272 | if (xs_list_in(boosts, snac->md5) == -1) { |
| 1269 | /* not already boosted or us; add button */ | 1273 | /* not already boosted; add button */ |
| 1270 | xs_html_add(form, | 1274 | xs_html_add(form, |
| 1271 | html_button("boost", L("Boost"), L("Announce this post to your followers"))); | 1275 | html_button("boost", L("Boost"), L("Announce this post to your followers"))); |
| 1272 | } | 1276 | } |
| @@ -1310,7 +1314,7 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1310 | html_button("delete", L("Delete"), L("Delete this post")), | 1314 | html_button("delete", L("Delete"), L("Delete this post")), |
| 1311 | html_button("hide", L("Hide"), L("Hide this post and its children"))); | 1315 | html_button("hide", L("Hide"), L("Hide this post and its children"))); |
| 1312 | 1316 | ||
| 1313 | char *prev_src = xs_dict_get(msg, "sourceContent"); | 1317 | const char *prev_src = xs_dict_get(msg, "sourceContent"); |
| 1314 | 1318 | ||
| 1315 | if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/ | 1319 | if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/ |
| 1316 | /* post can be edited */ | 1320 | /* post can be edited */ |
| @@ -1318,13 +1322,13 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1318 | xs *form_id = xs_fmt("%s_edit_form", md5); | 1322 | xs *form_id = xs_fmt("%s_edit_form", md5); |
| 1319 | xs *redir = xs_fmt("%s_entry", md5); | 1323 | xs *redir = xs_fmt("%s_entry", md5); |
| 1320 | 1324 | ||
| 1321 | char *att_file = ""; | 1325 | const char *att_file = ""; |
| 1322 | char *att_alt_text = ""; | 1326 | const char *att_alt_text = ""; |
| 1323 | xs_list *att_list = xs_dict_get(msg, "attachment"); | 1327 | const xs_list *att_list = xs_dict_get(msg, "attachment"); |
| 1324 | 1328 | ||
| 1325 | /* does it have an attachment? */ | 1329 | /* does it have an attachment? */ |
| 1326 | if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) { | 1330 | if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) { |
| 1327 | xs_dict *d = xs_list_get(att_list, 0); | 1331 | const xs_dict *d = xs_list_get(att_list, 0); |
| 1328 | 1332 | ||
| 1329 | if (xs_type(d) == XSTYPE_DICT) { | 1333 | if (xs_type(d) == XSTYPE_DICT) { |
| 1330 | att_file = xs_dict_get_def(d, "url", ""); | 1334 | att_file = xs_dict_get_def(d, "url", ""); |
| @@ -1368,12 +1372,13 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const | |||
| 1368 | 1372 | ||
| 1369 | 1373 | ||
| 1370 | xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | 1374 | xs_html *html_entry(snac *user, xs_dict *msg, int read_only, |
| 1371 | int level, char *md5, int hide_children) | 1375 | int level, const char *md5, int hide_children) |
| 1372 | { | 1376 | { |
| 1373 | char *id = xs_dict_get(msg, "id"); | 1377 | const char *id = xs_dict_get(msg, "id"); |
| 1374 | char *type = xs_dict_get(msg, "type"); | 1378 | const char *type = xs_dict_get(msg, "type"); |
| 1375 | char *actor; | 1379 | const char *actor; |
| 1376 | char *v; | 1380 | const char *v; |
| 1381 | int has_title = 0; | ||
| 1377 | 1382 | ||
| 1378 | /* do not show non-public messages in the public timeline */ | 1383 | /* do not show non-public messages in the public timeline */ |
| 1379 | if ((read_only || !user) && !is_msg_public(msg)) | 1384 | if ((read_only || !user) && !is_msg_public(msg)) |
| @@ -1405,8 +1410,9 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1405 | html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg))); | 1410 | html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg))); |
| 1406 | } | 1411 | } |
| 1407 | else | 1412 | else |
| 1408 | if (!xs_match(type, "Note|Question|Page|Article|Video")) { | 1413 | if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) { |
| 1409 | /* skip oddities */ | 1414 | /* skip oddities */ |
| 1415 | snac_debug(user, 1, xs_fmt("html_entry: ignoring object type '%s' %s", type, id)); | ||
| 1410 | return NULL; | 1416 | return NULL; |
| 1411 | } | 1417 | } |
| 1412 | 1418 | ||
| @@ -1483,6 +1489,14 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1483 | } | 1489 | } |
| 1484 | } | 1490 | } |
| 1485 | 1491 | ||
| 1492 | if (strcmp(type, "Event") == 0) { | ||
| 1493 | /* add the calendar emoji */ | ||
| 1494 | xs_html_add(score, | ||
| 1495 | xs_html_tag("span", | ||
| 1496 | xs_html_attr("title", L("Event")), | ||
| 1497 | xs_html_raw(" 📅 "))); | ||
| 1498 | } | ||
| 1499 | |||
| 1486 | /* if it's a user from this same instance, add the score */ | 1500 | /* if it's a user from this same instance, add the score */ |
| 1487 | if (xs_startswith(id, srv_baseurl)) { | 1501 | if (xs_startswith(id, srv_baseurl)) { |
| 1488 | int n_likes = object_likes_len(id); | 1502 | int n_likes = object_likes_len(id); |
| @@ -1499,7 +1513,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1499 | 1513 | ||
| 1500 | if (xs_list_len(boosts)) { | 1514 | if (xs_list_len(boosts)) { |
| 1501 | /* if somebody boosted this, show as origin */ | 1515 | /* if somebody boosted this, show as origin */ |
| 1502 | char *p = xs_list_get(boosts, -1); | 1516 | const char *p = xs_list_get(boosts, -1); |
| 1503 | xs *actor_r = NULL; | 1517 | xs *actor_r = NULL; |
| 1504 | 1518 | ||
| 1505 | if (user && xs_list_in(boosts, user->md5) != -1) { | 1519 | if (user && xs_list_in(boosts, user->md5) != -1) { |
| @@ -1519,7 +1533,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1519 | 1533 | ||
| 1520 | if (!xs_is_null(name)) { | 1534 | if (!xs_is_null(name)) { |
| 1521 | xs *href = NULL; | 1535 | xs *href = NULL; |
| 1522 | char *id = xs_dict_get(actor_r, "id"); | 1536 | const char *id = xs_dict_get(actor_r, "id"); |
| 1523 | int fwers = 0; | 1537 | int fwers = 0; |
| 1524 | int fwing = 0; | 1538 | int fwing = 0; |
| 1525 | 1539 | ||
| @@ -1548,7 +1562,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1548 | if (strcmp(type, "Note") == 0) { | 1562 | if (strcmp(type, "Note") == 0) { |
| 1549 | if (level == 0) { | 1563 | if (level == 0) { |
| 1550 | /* is the parent not here? */ | 1564 | /* is the parent not here? */ |
| 1551 | char *parent = xs_dict_get(msg, "inReplyTo"); | 1565 | const char *parent = xs_dict_get(msg, "inReplyTo"); |
| 1552 | 1566 | ||
| 1553 | if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { | 1567 | if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { |
| 1554 | xs_html_add(post_header, | 1568 | xs_html_add(post_header, |
| @@ -1574,11 +1588,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1574 | xs_html_add(entry, | 1588 | xs_html_add(entry, |
| 1575 | snac_content_wrap); | 1589 | snac_content_wrap); |
| 1576 | 1590 | ||
| 1577 | if (!xs_is_null(v = xs_dict_get(msg, "name"))) { | 1591 | if (!has_title && !xs_is_null(v = xs_dict_get(msg, "name"))) { |
| 1578 | xs_html_add(snac_content_wrap, | 1592 | xs_html_add(snac_content_wrap, |
| 1579 | xs_html_tag("h3", | 1593 | xs_html_tag("h3", |
| 1580 | xs_html_attr("class", "snac-entry-title"), | 1594 | xs_html_attr("class", "snac-entry-title"), |
| 1581 | xs_html_text(v))); | 1595 | xs_html_text(v))); |
| 1596 | |||
| 1597 | has_title = 1; | ||
| 1582 | } | 1598 | } |
| 1583 | 1599 | ||
| 1584 | xs_html *snac_content = NULL; | 1600 | xs_html *snac_content = NULL; |
| @@ -1591,7 +1607,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1591 | v = "..."; | 1607 | v = "..."; |
| 1592 | 1608 | ||
| 1593 | /* only show it when not in the public timeline and the config setting is "open" */ | 1609 | /* only show it when not in the public timeline and the config setting is "open" */ |
| 1594 | char *cw = xs_dict_get(user->config, "cw"); | 1610 | const char *cw = xs_dict_get(user->config, "cw"); |
| 1595 | if (xs_is_null(cw) || read_only) | 1611 | if (xs_is_null(cw) || read_only) |
| 1596 | cw = ""; | 1612 | cw = ""; |
| 1597 | 1613 | ||
| @@ -1603,12 +1619,15 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1603 | } | 1619 | } |
| 1604 | else { | 1620 | else { |
| 1605 | /* print the summary as a header (sites like e.g. Friendica can contain one) */ | 1621 | /* print the summary as a header (sites like e.g. Friendica can contain one) */ |
| 1606 | if (!xs_is_null(v) && *v) | 1622 | if (!has_title && !xs_is_null(v) && *v) { |
| 1607 | xs_html_add(snac_content_wrap, | 1623 | xs_html_add(snac_content_wrap, |
| 1608 | xs_html_tag("h3", | 1624 | xs_html_tag("h3", |
| 1609 | xs_html_attr("class", "snac-entry-title"), | 1625 | xs_html_attr("class", "snac-entry-title"), |
| 1610 | xs_html_text(v))); | 1626 | xs_html_text(v))); |
| 1611 | 1627 | ||
| 1628 | has_title = 1; | ||
| 1629 | } | ||
| 1630 | |||
| 1612 | snac_content = xs_html_tag("div", NULL); | 1631 | snac_content = xs_html_tag("div", NULL); |
| 1613 | } | 1632 | } |
| 1614 | 1633 | ||
| @@ -1617,7 +1636,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1617 | 1636 | ||
| 1618 | { | 1637 | { |
| 1619 | /** build the content string **/ | 1638 | /** build the content string **/ |
| 1620 | char *content = xs_dict_get(msg, "content"); | 1639 | const char *content = xs_dict_get(msg, "content"); |
| 1621 | 1640 | ||
| 1622 | xs *c = sanitize(xs_is_null(content) ? "" : content); | 1641 | xs *c = sanitize(xs_is_null(content) ? "" : content); |
| 1623 | 1642 | ||
| @@ -1635,7 +1654,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1635 | c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2); | 1654 | c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2); |
| 1636 | 1655 | ||
| 1637 | /* Peertube videos content is in markdown */ | 1656 | /* Peertube videos content is in markdown */ |
| 1638 | char *mtype = xs_dict_get(msg, "mediaType"); | 1657 | const char *mtype = xs_dict_get(msg, "mediaType"); |
| 1639 | if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "text/markdown") == 0) { | 1658 | if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "text/markdown") == 0) { |
| 1640 | /* a full conversion could be better */ | 1659 | /* a full conversion could be better */ |
| 1641 | c = xs_replace_i(c, "\r", ""); | 1660 | c = xs_replace_i(c, "\r", ""); |
| @@ -1648,26 +1667,33 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1648 | } | 1667 | } |
| 1649 | 1668 | ||
| 1650 | if (strcmp(type, "Question") == 0) { /** question content **/ | 1669 | if (strcmp(type, "Question") == 0) { /** question content **/ |
| 1651 | xs_list *oo = xs_dict_get(msg, "oneOf"); | 1670 | const xs_list *oo = xs_dict_get(msg, "oneOf"); |
| 1652 | xs_list *ao = xs_dict_get(msg, "anyOf"); | 1671 | const xs_list *ao = xs_dict_get(msg, "anyOf"); |
| 1653 | xs_list *p; | 1672 | const xs_list *p; |
| 1654 | xs_dict *v; | 1673 | const xs_dict *v; |
| 1655 | int closed = 0; | 1674 | int closed = 0; |
| 1675 | const char *f_closed = NULL; | ||
| 1656 | 1676 | ||
| 1657 | xs_html *poll = xs_html_tag("div", NULL); | 1677 | xs_html *poll = xs_html_tag("div", NULL); |
| 1658 | 1678 | ||
| 1659 | if (read_only) | 1679 | if (read_only) |
| 1660 | closed = 1; /* non-identified page; show as closed */ | 1680 | closed = 1; /* non-identified page; show as closed */ |
| 1661 | else | 1681 | else |
| 1662 | if (xs_dict_get(msg, "closed")) | ||
| 1663 | closed = 2; | ||
| 1664 | else | ||
| 1665 | if (user && xs_startswith(id, user->actor)) | 1682 | if (user && xs_startswith(id, user->actor)) |
| 1666 | closed = 1; /* we questioned; closed for us */ | 1683 | closed = 1; /* we questioned; closed for us */ |
| 1667 | else | 1684 | else |
| 1668 | if (user && was_question_voted(user, id)) | 1685 | if (user && was_question_voted(user, id)) |
| 1669 | closed = 1; /* we already voted; closed for us */ | 1686 | closed = 1; /* we already voted; closed for us */ |
| 1670 | 1687 | ||
| 1688 | if ((f_closed = xs_dict_get(msg, "closed")) != NULL) { | ||
| 1689 | /* it has a closed date... but is it in the past? */ | ||
| 1690 | time_t t0 = time(NULL); | ||
| 1691 | time_t t1 = xs_parse_iso_date(f_closed, 0); | ||
| 1692 | |||
| 1693 | if (t1 < t0) | ||
| 1694 | closed = 2; | ||
| 1695 | } | ||
| 1696 | |||
| 1671 | /* get the appropriate list of options */ | 1697 | /* get the appropriate list of options */ |
| 1672 | p = oo != NULL ? oo : ao; | 1698 | p = oo != NULL ? oo : ao; |
| 1673 | 1699 | ||
| @@ -1675,10 +1701,11 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1675 | /* closed poll */ | 1701 | /* closed poll */ |
| 1676 | xs_html *poll_result = xs_html_tag("table", | 1702 | xs_html *poll_result = xs_html_tag("table", |
| 1677 | xs_html_attr("class", "snac-poll-result")); | 1703 | xs_html_attr("class", "snac-poll-result")); |
| 1704 | int c = 0; | ||
| 1678 | 1705 | ||
| 1679 | while (xs_list_iter(&p, &v)) { | 1706 | while (xs_list_next(p, &v, &c)) { |
| 1680 | char *name = xs_dict_get(v, "name"); | 1707 | const char *name = xs_dict_get(v, "name"); |
| 1681 | xs_dict *replies = xs_dict_get(v, "replies"); | 1708 | const xs_dict *replies = xs_dict_get(v, "replies"); |
| 1682 | 1709 | ||
| 1683 | if (name && replies) { | 1710 | if (name && replies) { |
| 1684 | char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); | 1711 | char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); |
| @@ -1715,9 +1742,10 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1715 | xs_html_attr("name", "irt"), | 1742 | xs_html_attr("name", "irt"), |
| 1716 | xs_html_attr("value", id)))); | 1743 | xs_html_attr("value", id)))); |
| 1717 | 1744 | ||
| 1718 | while (xs_list_iter(&p, &v)) { | 1745 | int c = 0; |
| 1719 | char *name = xs_dict_get(v, "name"); | 1746 | while (xs_list_next(p, &v, &c)) { |
| 1720 | xs_dict *replies = xs_dict_get(v, "replies"); | 1747 | const char *name = xs_dict_get(v, "name"); |
| 1748 | const xs_dict *replies = xs_dict_get(v, "replies"); | ||
| 1721 | 1749 | ||
| 1722 | if (name) { | 1750 | if (name) { |
| 1723 | char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); | 1751 | char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); |
| @@ -1755,7 +1783,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1755 | } | 1783 | } |
| 1756 | else { | 1784 | else { |
| 1757 | /* show when the poll closes */ | 1785 | /* show when the poll closes */ |
| 1758 | char *end_time = xs_dict_get(msg, "endTime"); | 1786 | const char *end_time = xs_dict_get(msg, "endTime"); |
| 1787 | |||
| 1788 | /* Pleroma does not have an endTime field; | ||
| 1789 | it has a closed time in the future */ | ||
| 1790 | if (xs_is_null(end_time)) | ||
| 1791 | end_time = xs_dict_get(msg, "closed"); | ||
| 1792 | |||
| 1759 | if (!xs_is_null(end_time)) { | 1793 | if (!xs_is_null(end_time)) { |
| 1760 | time_t t0 = time(NULL); | 1794 | time_t t0 = time(NULL); |
| 1761 | time_t t1 = xs_parse_iso_date(end_time, 0); | 1795 | time_t t1 = xs_parse_iso_date(end_time, 0); |
| @@ -1792,12 +1826,12 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1792 | xs_html_add(snac_content, | 1826 | xs_html_add(snac_content, |
| 1793 | content_attachments); | 1827 | content_attachments); |
| 1794 | 1828 | ||
| 1795 | xs_list *p = attach; | 1829 | int c = 0; |
| 1796 | 1830 | const xs_dict *a; | |
| 1797 | while (xs_list_iter(&p, &v)) { | 1831 | while (xs_list_next(attach, &a, &c)) { |
| 1798 | char *type = xs_dict_get(v, "type"); | 1832 | const char *type = xs_dict_get(a, "type"); |
| 1799 | char *href = xs_dict_get(v, "href"); | 1833 | const char *href = xs_dict_get(a, "href"); |
| 1800 | char *name = xs_dict_get(v, "name"); | 1834 | const char *name = xs_dict_get(a, "name"); |
| 1801 | 1835 | ||
| 1802 | if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) { | 1836 | if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) { |
| 1803 | xs_html_add(content_attachments, | 1837 | xs_html_add(content_attachments, |
| @@ -1861,7 +1895,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1861 | } | 1895 | } |
| 1862 | 1896 | ||
| 1863 | /* has this message an audience (i.e., comes from a channel or community)? */ | 1897 | /* has this message an audience (i.e., comes from a channel or community)? */ |
| 1864 | char *audience = xs_dict_get(msg, "audience"); | 1898 | const char *audience = xs_dict_get(msg, "audience"); |
| 1865 | if (strcmp(type, "Page") == 0 && !xs_is_null(audience)) { | 1899 | if (strcmp(type, "Page") == 0 && !xs_is_null(audience)) { |
| 1866 | xs_html *au_tag = xs_html_tag("p", | 1900 | xs_html *au_tag = xs_html_tag("p", |
| 1867 | xs_html_text("("), | 1901 | xs_html_text("("), |
| @@ -1911,7 +1945,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1911 | } | 1945 | } |
| 1912 | 1946 | ||
| 1913 | xs_list *p = children; | 1947 | xs_list *p = children; |
| 1914 | char *cmd5; | 1948 | const char *cmd5; |
| 1915 | int cnt = 0; | 1949 | int cnt = 0; |
| 1916 | int o_cnt = 0; | 1950 | int o_cnt = 0; |
| 1917 | 1951 | ||
| @@ -1983,11 +2017,11 @@ xs_html *html_footer(void) | |||
| 1983 | 2017 | ||
| 1984 | xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | 2018 | xs_str *html_timeline(snac *user, const xs_list *list, int read_only, |
| 1985 | int skip, int show, int show_more, | 2019 | int skip, int show, int show_more, |
| 1986 | char *tag, char *page, int utl) | 2020 | char *title, char *page, int utl) |
| 1987 | /* returns the HTML for the timeline */ | 2021 | /* returns the HTML for the timeline */ |
| 1988 | { | 2022 | { |
| 1989 | xs_list *p = (xs_list *)list; | 2023 | xs_list *p = (xs_list *)list; |
| 1990 | char *v; | 2024 | const char *v; |
| 1991 | double t = ftime(); | 2025 | double t = ftime(); |
| 1992 | 2026 | ||
| 1993 | xs *desc = NULL; | 2027 | xs *desc = NULL; |
| @@ -1995,11 +2029,12 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 1995 | 2029 | ||
| 1996 | if (xs_list_len(list) == 1) { | 2030 | if (xs_list_len(list) == 1) { |
| 1997 | /* only one element? pick the description from the source */ | 2031 | /* only one element? pick the description from the source */ |
| 1998 | char *id = xs_list_get(list, 0); | 2032 | const char *id = xs_list_get(list, 0); |
| 1999 | xs *d = NULL; | 2033 | xs *d = NULL; |
| 2000 | object_get_by_md5(id, &d); | 2034 | object_get_by_md5(id, &d); |
| 2001 | if (d && (v = xs_dict_get(d, "sourceContent")) != NULL) | 2035 | const char *sc = xs_dict_get(d, "sourceContent"); |
| 2002 | desc = xs_dup(v); | 2036 | if (d && sc != NULL) |
| 2037 | desc = xs_dup(sc); | ||
| 2003 | 2038 | ||
| 2004 | alternate = xs_dup(xs_dict_get(d, "id")); | 2039 | alternate = xs_dup(xs_dict_get(d, "id")); |
| 2005 | } | 2040 | } |
| @@ -2013,7 +2048,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 2013 | } | 2048 | } |
| 2014 | else { | 2049 | else { |
| 2015 | head = html_instance_head(); | 2050 | head = html_instance_head(); |
| 2016 | body = html_instance_body(tag); | 2051 | body = html_instance_body(); |
| 2017 | } | 2052 | } |
| 2018 | 2053 | ||
| 2019 | xs_html *html = xs_html_tag("html", | 2054 | xs_html *html = xs_html_tag("html", |
| @@ -2024,6 +2059,41 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 2024 | xs_html_add(body, | 2059 | xs_html_add(body, |
| 2025 | html_top_controls(user)); | 2060 | html_top_controls(user)); |
| 2026 | 2061 | ||
| 2062 | /* show links to the available lists */ | ||
| 2063 | if (user && !read_only) { | ||
| 2064 | xs *lists = list_maint(user, NULL, 0); /* get list of lists */ | ||
| 2065 | |||
| 2066 | if (xs_list_len(lists)) { | ||
| 2067 | int ct = 0; | ||
| 2068 | const char *v; | ||
| 2069 | |||
| 2070 | xs_html *lol = xs_html_tag("ul", | ||
| 2071 | xs_html_attr("class", "snac-list-of-lists")); | ||
| 2072 | xs_html_add(body, lol); | ||
| 2073 | |||
| 2074 | while (xs_list_next(lists, &v, &ct)) { | ||
| 2075 | const char *lname = xs_list_get(v, 1); | ||
| 2076 | xs *url = xs_fmt("%s/list/%s", user->actor, xs_list_get(v, 0)); | ||
| 2077 | xs *ttl = xs_fmt(L("Timeline for list '%s'"), lname); | ||
| 2078 | |||
| 2079 | xs_html_add(lol, | ||
| 2080 | xs_html_tag("li", | ||
| 2081 | xs_html_tag("a", | ||
| 2082 | xs_html_attr("href", url), | ||
| 2083 | xs_html_attr("class", "snac-list-link"), | ||
| 2084 | xs_html_attr("title", ttl), | ||
| 2085 | xs_html_text(lname)))); | ||
| 2086 | } | ||
| 2087 | } | ||
| 2088 | } | ||
| 2089 | |||
| 2090 | if (title) { | ||
| 2091 | xs_html_add(body, | ||
| 2092 | xs_html_tag("h2", | ||
| 2093 | xs_html_attr("class", "snac-header"), | ||
| 2094 | xs_html_text(title))); | ||
| 2095 | } | ||
| 2096 | |||
| 2027 | xs_html_add(body, | 2097 | xs_html_add(body, |
| 2028 | xs_html_tag("a", | 2098 | xs_html_tag("a", |
| 2029 | xs_html_attr("name", "snac-posts"))); | 2099 | xs_html_attr("name", "snac-posts"))); |
| @@ -2052,11 +2122,18 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 2052 | 2122 | ||
| 2053 | /* is this message a non-public reply? */ | 2123 | /* is this message a non-public reply? */ |
| 2054 | if (user != NULL && !is_msg_public(msg)) { | 2124 | if (user != NULL && !is_msg_public(msg)) { |
| 2055 | char *irt = xs_dict_get(msg, "inReplyTo"); | 2125 | const char *irt = xs_dict_get(msg, "inReplyTo"); |
| 2056 | 2126 | ||
| 2127 | /* is it a reply to something not in the storage? */ | ||
| 2057 | if (!xs_is_null(irt) && !object_here(irt)) { | 2128 | if (!xs_is_null(irt) && !object_here(irt)) { |
| 2058 | snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v)); | 2129 | /* is it for me? */ |
| 2059 | continue; | 2130 | const xs_list *to = xs_dict_get_def(msg, "to", xs_stock(XSTYPE_LIST)); |
| 2131 | const xs_list *cc = xs_dict_get_def(msg, "cc", xs_stock(XSTYPE_LIST)); | ||
| 2132 | |||
| 2133 | if (xs_list_in(to, user->actor) == -1 && xs_list_in(cc, user->actor) == -1) { | ||
| 2134 | snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v)); | ||
| 2135 | continue; | ||
| 2136 | } | ||
| 2060 | } | 2137 | } |
| 2061 | } | 2138 | } |
| 2062 | 2139 | ||
| @@ -2081,7 +2158,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 2081 | 2158 | ||
| 2082 | xs *list = history_list(user); | 2159 | xs *list = history_list(user); |
| 2083 | xs_list *p = list; | 2160 | xs_list *p = list; |
| 2084 | char *v; | 2161 | const char *v; |
| 2085 | 2162 | ||
| 2086 | while (xs_list_iter(&p, &v)) { | 2163 | while (xs_list_iter(&p, &v)) { |
| 2087 | xs *fn = xs_replace(v, ".html", ""); | 2164 | xs *fn = xs_replace(v, ".html", ""); |
| @@ -2106,25 +2183,22 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 2106 | } | 2183 | } |
| 2107 | 2184 | ||
| 2108 | if (show_more) { | 2185 | if (show_more) { |
| 2109 | xs *t = NULL; | ||
| 2110 | xs *m = NULL; | 2186 | xs *m = NULL; |
| 2111 | xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); | 2187 | xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); |
| 2112 | 2188 | ||
| 2113 | xs *url = page == NULL || user == NULL ? | 2189 | xs *url = xs_dup(user == NULL ? srv_baseurl : user->actor); |
| 2114 | xs_dup(srv_baseurl) : xs_fmt("%s%s", user->actor, page); | ||
| 2115 | 2190 | ||
| 2116 | if (tag) { | 2191 | if (page != NULL) |
| 2117 | t = xs_fmt("%s?t=%s", url, tag); | 2192 | url = xs_str_cat(url, page); |
| 2118 | m = xs_fmt("%s&%s", t, ss); | 2193 | |
| 2119 | } | 2194 | if (xs_str_in(url, "?") != -1) |
| 2120 | else { | 2195 | m = xs_fmt("%s&%s", url, ss); |
| 2121 | t = xs_dup(url); | 2196 | else |
| 2122 | m = xs_fmt("%s?%s", t, ss); | 2197 | m = xs_fmt("%s?%s", url, ss); |
| 2123 | } | ||
| 2124 | 2198 | ||
| 2125 | xs_html *more_links = xs_html_tag("p", | 2199 | xs_html *more_links = xs_html_tag("p", |
| 2126 | xs_html_tag("a", | 2200 | xs_html_tag("a", |
| 2127 | xs_html_attr("href", t), | 2201 | xs_html_attr("href", url), |
| 2128 | xs_html_attr("name", "snac-more"), | 2202 | xs_html_attr("name", "snac-more"), |
| 2129 | xs_html_text(L("Back to top"))), | 2203 | xs_html_text(L("Back to top"))), |
| 2130 | xs_html_text(" - "), | 2204 | xs_html_text(" - "), |
| @@ -2157,7 +2231,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t) | |||
| 2157 | xs_html_text("...")))); | 2231 | xs_html_text("...")))); |
| 2158 | 2232 | ||
| 2159 | xs_list *p = list; | 2233 | xs_list *p = list; |
| 2160 | char *actor_id; | 2234 | const char *actor_id; |
| 2161 | 2235 | ||
| 2162 | while (xs_list_iter(&p, &actor_id)) { | 2236 | while (xs_list_iter(&p, &actor_id)) { |
| 2163 | xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); | 2237 | xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); |
| @@ -2173,7 +2247,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t) | |||
| 2173 | html_actor_icon(snac, actor, xs_dict_get(actor, "published"), NULL, NULL, 0, 1))); | 2247 | html_actor_icon(snac, actor, xs_dict_get(actor, "published"), NULL, NULL, 0, 1))); |
| 2174 | 2248 | ||
| 2175 | /* content (user bio) */ | 2249 | /* content (user bio) */ |
| 2176 | char *c = xs_dict_get(actor, "summary"); | 2250 | const char *c = xs_dict_get(actor, "summary"); |
| 2177 | 2251 | ||
| 2178 | if (!xs_is_null(c)) { | 2252 | if (!xs_is_null(c)) { |
| 2179 | xs *sc = sanitize(c); | 2253 | xs *sc = sanitize(c); |
| @@ -2317,7 +2391,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2317 | xs_html *noti_seen = NULL; | 2391 | xs_html *noti_seen = NULL; |
| 2318 | 2392 | ||
| 2319 | xs_list *p = n_list; | 2393 | xs_list *p = n_list; |
| 2320 | xs_str *v; | 2394 | const xs_str *v; |
| 2321 | while (xs_list_iter(&p, &v)) { | 2395 | while (xs_list_iter(&p, &v)) { |
| 2322 | xs *noti = notify_get(user, v); | 2396 | xs *noti = notify_get(user, v); |
| 2323 | 2397 | ||
| @@ -2325,10 +2399,10 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2325 | continue; | 2399 | continue; |
| 2326 | 2400 | ||
| 2327 | xs *obj = NULL; | 2401 | xs *obj = NULL; |
| 2328 | char *type = xs_dict_get(noti, "type"); | 2402 | const char *type = xs_dict_get(noti, "type"); |
| 2329 | char *utype = xs_dict_get(noti, "utype"); | 2403 | const char *utype = xs_dict_get(noti, "utype"); |
| 2330 | char *id = xs_dict_get(noti, "objid"); | 2404 | const char *id = xs_dict_get(noti, "objid"); |
| 2331 | char *date = xs_dict_get(noti, "date"); | 2405 | const char *date = xs_dict_get(noti, "date"); |
| 2332 | 2406 | ||
| 2333 | if (xs_is_null(id) || !valid_status(object_get(id, &obj))) | 2407 | if (xs_is_null(id) || !valid_status(object_get(id, &obj))) |
| 2334 | continue; | 2408 | continue; |
| @@ -2336,14 +2410,14 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2336 | if (is_hidden(user, id)) | 2410 | if (is_hidden(user, id)) |
| 2337 | continue; | 2411 | continue; |
| 2338 | 2412 | ||
| 2339 | char *actor_id = xs_dict_get(noti, "actor"); | 2413 | const char *actor_id = xs_dict_get(noti, "actor"); |
| 2340 | xs *actor = NULL; | 2414 | xs *actor = NULL; |
| 2341 | 2415 | ||
| 2342 | if (!valid_status(actor_get(actor_id, &actor))) | 2416 | if (!valid_status(actor_get(actor_id, &actor))) |
| 2343 | continue; | 2417 | continue; |
| 2344 | 2418 | ||
| 2345 | xs *a_name = actor_name(actor); | 2419 | xs *a_name = actor_name(actor); |
| 2346 | char *label = type; | 2420 | const char *label = type; |
| 2347 | 2421 | ||
| 2348 | if (strcmp(type, "Create") == 0) | 2422 | if (strcmp(type, "Create") == 0) |
| 2349 | label = L("Mention"); | 2423 | label = L("Mention"); |
| @@ -2455,14 +2529,14 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2455 | int html_get_handler(const xs_dict *req, const char *q_path, | 2529 | int html_get_handler(const xs_dict *req, const char *q_path, |
| 2456 | char **body, int *b_size, char **ctype, xs_str **etag) | 2530 | char **body, int *b_size, char **ctype, xs_str **etag) |
| 2457 | { | 2531 | { |
| 2458 | char *accept = xs_dict_get(req, "accept"); | 2532 | const char *accept = xs_dict_get(req, "accept"); |
| 2459 | int status = 404; | 2533 | int status = 404; |
| 2460 | snac snac; | 2534 | snac snac; |
| 2461 | xs *uid = NULL; | 2535 | xs *uid = NULL; |
| 2462 | char *p_path; | 2536 | const char *p_path; |
| 2463 | int cache = 1; | 2537 | int cache = 1; |
| 2464 | int save = 1; | 2538 | int save = 1; |
| 2465 | char *v; | 2539 | const char *v; |
| 2466 | 2540 | ||
| 2467 | xs *l = xs_split_n(q_path, "/", 2); | 2541 | xs *l = xs_split_n(q_path, "/", 2); |
| 2468 | v = xs_list_get(l, 1); | 2542 | v = xs_list_get(l, 1); |
| @@ -2501,7 +2575,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2501 | 2575 | ||
| 2502 | int skip = 0; | 2576 | int skip = 0; |
| 2503 | int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); | 2577 | int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); |
| 2504 | char *q_vars = xs_dict_get(req, "q_vars"); | 2578 | const xs_dict *q_vars = xs_dict_get(req, "q_vars"); |
| 2505 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) | 2579 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) |
| 2506 | skip = atoi(v), cache = 0, save = 0; | 2580 | skip = atoi(v), cache = 0, save = 0; |
| 2507 | if ((v = xs_dict_get(q_vars, "show")) != NULL) | 2581 | if ((v = xs_dict_get(q_vars, "show")) != NULL) |
| @@ -2546,35 +2620,99 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2546 | status = 401; | 2620 | status = 401; |
| 2547 | } | 2621 | } |
| 2548 | else { | 2622 | else { |
| 2549 | double t = history_mtime(&snac, "timeline.html_"); | 2623 | const char *q = xs_dict_get(q_vars, "q"); |
| 2624 | |||
| 2625 | if (q && *q) { | ||
| 2626 | if (*q == '#') { | ||
| 2627 | /** search by tag **/ | ||
| 2628 | xs *tl = tag_search(q, skip, show + 1); | ||
| 2629 | int more = 0; | ||
| 2630 | if (xs_list_len(tl) >= show + 1) { | ||
| 2631 | /* drop the last one */ | ||
| 2632 | tl = xs_list_del(tl, -1); | ||
| 2633 | more = 1; | ||
| 2634 | } | ||
| 2550 | 2635 | ||
| 2551 | /* if enabled by admin, return a cached page if its timestamp is: | 2636 | xs *page = xs_fmt("/admin?q=%%23%s", q + 1); |
| 2552 | a) newer than the timeline timestamp | 2637 | xs *title = xs_fmt(xs_list_len(tl) ? |
| 2553 | b) newer than the start time of the server | 2638 | L("Search results for tag %s") : L("Nothing found for tag %s"), q); |
| 2554 | */ | ||
| 2555 | if (cache && t > timeline_mtime(&snac) && t > p_state->srv_start_time) { | ||
| 2556 | snac_debug(&snac, 1, xs_fmt("serving cached timeline")); | ||
| 2557 | 2639 | ||
| 2558 | status = history_get(&snac, "timeline.html_", body, b_size, | 2640 | *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0); |
| 2559 | xs_dict_get(req, "if-none-match"), etag); | 2641 | *b_size = strlen(*body); |
| 2642 | status = 200; | ||
| 2643 | } | ||
| 2644 | else { | ||
| 2645 | /** search by content **/ | ||
| 2646 | int to = 0; | ||
| 2647 | int msecs = atoi(xs_dict_get_def(q_vars, "msecs", "0")); | ||
| 2648 | xs *tl = content_search(&snac, q, 1, skip, show, msecs, &to); | ||
| 2649 | xs *title = NULL; | ||
| 2650 | xs *page = xs_fmt("/admin?q=%s&msecs=%d", q, msecs + 10); | ||
| 2651 | int tl_len = xs_list_len(tl); | ||
| 2652 | |||
| 2653 | if (tl_len) | ||
| 2654 | title = xs_fmt(L("Search results for '%s'"), q); | ||
| 2655 | else | ||
| 2656 | if (skip) | ||
| 2657 | title = xs_fmt(L("No more matches for '%s'"), q); | ||
| 2658 | else | ||
| 2659 | title = xs_fmt(L("Nothing found for '%s'"), q); | ||
| 2660 | |||
| 2661 | *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, title, page, 0); | ||
| 2662 | *b_size = strlen(*body); | ||
| 2663 | status = 200; | ||
| 2664 | } | ||
| 2560 | } | 2665 | } |
| 2561 | else { | 2666 | else { |
| 2562 | snac_debug(&snac, 1, xs_fmt("building timeline")); | 2667 | double t = history_mtime(&snac, "timeline.html_"); |
| 2668 | |||
| 2669 | /* if enabled by admin, return a cached page if its timestamp is: | ||
| 2670 | a) newer than the timeline timestamp | ||
| 2671 | b) newer than the start time of the server | ||
| 2672 | */ | ||
| 2673 | if (cache && t > timeline_mtime(&snac) && t > p_state->srv_start_time) { | ||
| 2674 | snac_debug(&snac, 1, xs_fmt("serving cached timeline")); | ||
| 2563 | 2675 | ||
| 2564 | xs *list = timeline_list(&snac, "private", skip, show); | 2676 | status = history_get(&snac, "timeline.html_", body, b_size, |
| 2565 | xs *next = timeline_list(&snac, "private", skip + show, 1); | 2677 | xs_dict_get(req, "if-none-match"), etag); |
| 2678 | } | ||
| 2679 | else { | ||
| 2680 | snac_debug(&snac, 1, xs_fmt("building timeline")); | ||
| 2681 | |||
| 2682 | xs *list = timeline_list(&snac, "private", skip, show); | ||
| 2683 | xs *next = timeline_list(&snac, "private", skip + show, 1); | ||
| 2684 | |||
| 2685 | xs *pins = pinned_list(&snac); | ||
| 2686 | pins = xs_list_cat(pins, list); | ||
| 2566 | 2687 | ||
| 2567 | xs *pins = pinned_list(&snac); | 2688 | *body = html_timeline(&snac, pins, 0, skip, show, |
| 2568 | pins = xs_list_cat(pins, list); | 2689 | xs_list_len(next), NULL, "/admin", 1); |
| 2569 | 2690 | ||
| 2570 | *body = html_timeline(&snac, pins, 0, skip, show, | 2691 | *b_size = strlen(*body); |
| 2571 | xs_list_len(next), NULL, "/admin", 1); | 2692 | status = 200; |
| 2572 | 2693 | ||
| 2694 | if (save) | ||
| 2695 | history_add(&snac, "timeline.html_", *body, *b_size, etag); | ||
| 2696 | } | ||
| 2697 | } | ||
| 2698 | } | ||
| 2699 | } | ||
| 2700 | else | ||
| 2701 | if (xs_startswith(p_path, "admin/p/")) { /** unique post by md5 **/ | ||
| 2702 | if (!login(&snac, req)) { | ||
| 2703 | *body = xs_dup(uid); | ||
| 2704 | status = 401; | ||
| 2705 | } | ||
| 2706 | else { | ||
| 2707 | xs *l = xs_split(p_path, "/"); | ||
| 2708 | const char *md5 = xs_list_get(l, -1); | ||
| 2709 | |||
| 2710 | if (md5 && *md5 && timeline_here(&snac, md5)) { | ||
| 2711 | xs *list = xs_list_append(xs_list_new(), md5); | ||
| 2712 | |||
| 2713 | *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1); | ||
| 2573 | *b_size = strlen(*body); | 2714 | *b_size = strlen(*body); |
| 2574 | status = 200; | 2715 | status = 200; |
| 2575 | |||
| 2576 | if (save) | ||
| 2577 | history_add(&snac, "timeline.html_", *body, *b_size, etag); | ||
| 2578 | } | 2716 | } |
| 2579 | } | 2717 | } |
| 2580 | } | 2718 | } |
| @@ -2613,12 +2751,37 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2613 | xs *next = timeline_instance_list(skip + show, 1); | 2751 | xs *next = timeline_instance_list(skip + show, 1); |
| 2614 | 2752 | ||
| 2615 | *body = html_timeline(&snac, list, 0, skip, show, | 2753 | *body = html_timeline(&snac, list, 0, skip, show, |
| 2616 | xs_list_len(next), NULL, "/instance", 0); | 2754 | xs_list_len(next), L("Showing instance timeline"), "/instance", 0); |
| 2617 | *b_size = strlen(*body); | 2755 | *b_size = strlen(*body); |
| 2618 | status = 200; | 2756 | status = 200; |
| 2619 | } | 2757 | } |
| 2620 | } | 2758 | } |
| 2621 | else | 2759 | else |
| 2760 | if (xs_startswith(p_path, "list/")) { /** list timelines **/ | ||
| 2761 | if (!login(&snac, req)) { | ||
| 2762 | *body = xs_dup(uid); | ||
| 2763 | status = 401; | ||
| 2764 | } | ||
| 2765 | else { | ||
| 2766 | xs *l = xs_split(p_path, "/"); | ||
| 2767 | const char *lid = xs_list_get(l, -1); | ||
| 2768 | |||
| 2769 | xs *list = list_timeline(&snac, lid, skip, show); | ||
| 2770 | xs *next = list_timeline(&snac, lid, skip + show, 1); | ||
| 2771 | |||
| 2772 | if (list != NULL) { | ||
| 2773 | xs *base = xs_fmt("/list/%s", lid); | ||
| 2774 | xs *name = list_maint(&snac, lid, 3); | ||
| 2775 | xs *title = xs_fmt(L("Showing timeline for list '%s'"), name); | ||
| 2776 | |||
| 2777 | *body = html_timeline(&snac, list, 0, skip, show, | ||
| 2778 | xs_list_len(next), title, base, 1); | ||
| 2779 | *b_size = strlen(*body); | ||
| 2780 | status = 200; | ||
| 2781 | } | ||
| 2782 | } | ||
| 2783 | } | ||
| 2784 | else | ||
| 2622 | if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ | 2785 | if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ |
| 2623 | if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) | 2786 | if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) |
| 2624 | return 403; | 2787 | return 403; |
| @@ -2640,7 +2803,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2640 | else | 2803 | else |
| 2641 | if (xs_startswith(p_path, "s/")) { /** a static file **/ | 2804 | if (xs_startswith(p_path, "s/")) { /** a static file **/ |
| 2642 | xs *l = xs_split(p_path, "/"); | 2805 | xs *l = xs_split(p_path, "/"); |
| 2643 | char *id = xs_list_get(l, 1); | 2806 | const char *id = xs_list_get(l, 1); |
| 2644 | int sz; | 2807 | int sz; |
| 2645 | 2808 | ||
| 2646 | if (id && *id) { | 2809 | if (id && *id) { |
| @@ -2661,8 +2824,8 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2661 | if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE) | 2824 | if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE) |
| 2662 | return 403; | 2825 | return 403; |
| 2663 | 2826 | ||
| 2664 | xs *l = xs_split(p_path, "/"); | 2827 | xs *l = xs_split(p_path, "/"); |
| 2665 | char *id = xs_list_get(l, 1); | 2828 | const char *id = xs_list_get(l, 1); |
| 2666 | 2829 | ||
| 2667 | if (id && *id) { | 2830 | if (id && *id) { |
| 2668 | if (xs_endswith(id, "timeline.html_")) { | 2831 | if (xs_endswith(id, "timeline.html_")) { |
| @@ -2689,55 +2852,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2689 | xs_dict_get(srv_config, "host")); | 2852 | xs_dict_get(srv_config, "host")); |
| 2690 | xs *rss_link = xs_fmt("%s.rss", snac.actor); | 2853 | xs *rss_link = xs_fmt("%s.rss", snac.actor); |
| 2691 | 2854 | ||
| 2692 | xs_html *rss = xs_html_tag("rss", | 2855 | *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio); |
| 2693 | xs_html_attr("version", "0.91")); | ||
| 2694 | |||
| 2695 | xs_html *channel = xs_html_tag("channel", | ||
| 2696 | xs_html_tag("title", | ||
| 2697 | xs_html_text(rss_title)), | ||
| 2698 | xs_html_tag("language", | ||
| 2699 | xs_html_text("en")), | ||
| 2700 | xs_html_tag("link", | ||
| 2701 | xs_html_text(rss_link)), | ||
| 2702 | xs_html_tag("description", | ||
| 2703 | xs_html_text(bio))); | ||
| 2704 | |||
| 2705 | xs_html_add(rss, channel); | ||
| 2706 | |||
| 2707 | xs_list *p = elems; | ||
| 2708 | char *v; | ||
| 2709 | |||
| 2710 | while (xs_list_iter(&p, &v)) { | ||
| 2711 | xs *msg = NULL; | ||
| 2712 | |||
| 2713 | if (!valid_status(timeline_get_by_md5(&snac, v, &msg))) | ||
| 2714 | continue; | ||
| 2715 | |||
| 2716 | char *id = xs_dict_get(msg, "id"); | ||
| 2717 | char *content = xs_dict_get(msg, "content"); | ||
| 2718 | |||
| 2719 | if (!xs_startswith(id, snac.actor)) | ||
| 2720 | continue; | ||
| 2721 | |||
| 2722 | /* create a title with the first line of the content */ | ||
| 2723 | xs *es_title = xs_replace(content, "<br>", "\n"); | ||
| 2724 | xs *title = xs_str_new(NULL); | ||
| 2725 | int i; | ||
| 2726 | |||
| 2727 | for (i = 0; es_title[i] && es_title[i] != '\n' && es_title[i] != '&' && i < 50; i++) | ||
| 2728 | title = xs_append_m(title, &es_title[i], 1); | ||
| 2729 | |||
| 2730 | xs_html_add(channel, | ||
| 2731 | xs_html_tag("item", | ||
| 2732 | xs_html_tag("title", | ||
| 2733 | xs_html_text(title)), | ||
| 2734 | xs_html_tag("link", | ||
| 2735 | xs_html_text(id)), | ||
| 2736 | xs_html_tag("description", | ||
| 2737 | xs_html_text(content)))); | ||
| 2738 | } | ||
| 2739 | |||
| 2740 | *body = xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | ||
| 2741 | *b_size = strlen(*body); | 2856 | *b_size = strlen(*body); |
| 2742 | *ctype = "application/rss+xml; charset=utf-8"; | 2857 | *ctype = "application/rss+xml; charset=utf-8"; |
| 2743 | status = 200; | 2858 | status = 200; |
| @@ -2766,8 +2881,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2766 | 2881 | ||
| 2767 | int status = 0; | 2882 | int status = 0; |
| 2768 | snac snac; | 2883 | snac snac; |
| 2769 | char *uid, *p_path; | 2884 | const char *uid; |
| 2770 | xs_dict *p_vars; | 2885 | const char *p_path; |
| 2886 | const xs_dict *p_vars; | ||
| 2771 | 2887 | ||
| 2772 | xs *l = xs_split_n(q_path, "/", 2); | 2888 | xs *l = xs_split_n(q_path, "/", 2); |
| 2773 | 2889 | ||
| @@ -2795,15 +2911,15 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2795 | 2911 | ||
| 2796 | if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/ | 2912 | if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/ |
| 2797 | /* post note */ | 2913 | /* post note */ |
| 2798 | xs_str *content = xs_dict_get(p_vars, "content"); | 2914 | const xs_str *content = xs_dict_get(p_vars, "content"); |
| 2799 | xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); | 2915 | const xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); |
| 2800 | xs_str *attach_url = xs_dict_get(p_vars, "attach_url"); | 2916 | const xs_str *attach_url = xs_dict_get(p_vars, "attach_url"); |
| 2801 | xs_list *attach_file = xs_dict_get(p_vars, "attach"); | 2917 | const xs_list *attach_file = xs_dict_get(p_vars, "attach"); |
| 2802 | xs_str *to = xs_dict_get(p_vars, "to"); | 2918 | const xs_str *to = xs_dict_get(p_vars, "to"); |
| 2803 | xs_str *sensitive = xs_dict_get(p_vars, "sensitive"); | 2919 | const xs_str *sensitive = xs_dict_get(p_vars, "sensitive"); |
| 2804 | xs_str *summary = xs_dict_get(p_vars, "summary"); | 2920 | const xs_str *summary = xs_dict_get(p_vars, "summary"); |
| 2805 | xs_str *edit_id = xs_dict_get(p_vars, "edit_id"); | 2921 | const xs_str *edit_id = xs_dict_get(p_vars, "edit_id"); |
| 2806 | xs_str *alt_text = xs_dict_get(p_vars, "alt_text"); | 2922 | const xs_str *alt_text = xs_dict_get(p_vars, "alt_text"); |
| 2807 | int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); | 2923 | int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); |
| 2808 | xs *attach_list = xs_list_new(); | 2924 | xs *attach_list = xs_list_new(); |
| 2809 | 2925 | ||
| @@ -2823,7 +2939,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2823 | 2939 | ||
| 2824 | /* is attach_file set? */ | 2940 | /* is attach_file set? */ |
| 2825 | if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) { | 2941 | if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) { |
| 2826 | char *fn = xs_list_get(attach_file, 0); | 2942 | const char *fn = xs_list_get(attach_file, 0); |
| 2827 | 2943 | ||
| 2828 | if (*fn != '\0') { | 2944 | if (*fn != '\0') { |
| 2829 | char *ext = strrchr(fn, '.'); | 2945 | char *ext = strrchr(fn, '.'); |
| @@ -2899,7 +3015,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2899 | int n; | 3015 | int n; |
| 2900 | 3016 | ||
| 2901 | for (n = 0; fields[n]; n++) { | 3017 | for (n = 0; fields[n]; n++) { |
| 2902 | char *v = xs_dict_get(p_msg, fields[n]); | 3018 | const char *v = xs_dict_get(p_msg, fields[n]); |
| 2903 | msg = xs_dict_set(msg, fields[n], v); | 3019 | msg = xs_dict_set(msg, fields[n], v); |
| 2904 | } | 3020 | } |
| 2905 | 3021 | ||
| @@ -2928,10 +3044,10 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2928 | else | 3044 | else |
| 2929 | if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/ | 3045 | if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/ |
| 2930 | /* action on an entry */ | 3046 | /* action on an entry */ |
| 2931 | char *id = xs_dict_get(p_vars, "id"); | 3047 | const char *id = xs_dict_get(p_vars, "id"); |
| 2932 | char *actor = xs_dict_get(p_vars, "actor"); | 3048 | const char *actor = xs_dict_get(p_vars, "actor"); |
| 2933 | char *action = xs_dict_get(p_vars, "action"); | 3049 | const char *action = xs_dict_get(p_vars, "action"); |
| 2934 | char *group = xs_dict_get(p_vars, "group"); | 3050 | const char *group = xs_dict_get(p_vars, "group"); |
| 2935 | 3051 | ||
| 2936 | if (action == NULL) | 3052 | if (action == NULL) |
| 2937 | return 404; | 3053 | return 404; |
| @@ -3055,7 +3171,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3055 | } | 3171 | } |
| 3056 | else | 3172 | else |
| 3057 | if (strcmp(action, L("Delete")) == 0) { /** **/ | 3173 | if (strcmp(action, L("Delete")) == 0) { /** **/ |
| 3058 | char *actor_form = xs_dict_get(p_vars, "actor-form"); | 3174 | const char *actor_form = xs_dict_get(p_vars, "actor-form"); |
| 3059 | if (actor_form != NULL) { | 3175 | if (actor_form != NULL) { |
| 3060 | /* delete follower */ | 3176 | /* delete follower */ |
| 3061 | if (valid_status(follower_del(&snac, actor))) | 3177 | if (valid_status(follower_del(&snac, actor))) |
| @@ -3099,8 +3215,8 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3099 | else | 3215 | else |
| 3100 | if (p_path && strcmp(p_path, "admin/user-setup") == 0) { /** **/ | 3216 | if (p_path && strcmp(p_path, "admin/user-setup") == 0) { /** **/ |
| 3101 | /* change of user data */ | 3217 | /* change of user data */ |
| 3102 | char *v; | 3218 | const char *v; |
| 3103 | char *p1, *p2; | 3219 | const char *p1, *p2; |
| 3104 | 3220 | ||
| 3105 | if ((v = xs_dict_get(p_vars, "name")) != NULL) | 3221 | if ((v = xs_dict_get(p_vars, "name")) != NULL) |
| 3106 | snac.config = xs_dict_set(snac.config, "name", v); | 3222 | snac.config = xs_dict_set(snac.config, "name", v); |
| @@ -3145,7 +3261,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3145 | xs_dict *md = xs_dict_new(); | 3261 | xs_dict *md = xs_dict_new(); |
| 3146 | xs *l = xs_split(v, "\n"); | 3262 | xs *l = xs_split(v, "\n"); |
| 3147 | xs_list *p = l; | 3263 | xs_list *p = l; |
| 3148 | xs_str *kp; | 3264 | const xs_str *kp; |
| 3149 | 3265 | ||
| 3150 | while (xs_list_iter(&p, &kp)) { | 3266 | while (xs_list_iter(&p, &kp)) { |
| 3151 | xs *kpl = xs_split_n(kp, "=", 1); | 3267 | xs *kpl = xs_split_n(kp, "=", 1); |
| @@ -3166,7 +3282,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3166 | for (n = 0; uploads[n]; n++) { | 3282 | for (n = 0; uploads[n]; n++) { |
| 3167 | xs *var_name = xs_fmt("%s_file", uploads[n]); | 3283 | xs *var_name = xs_fmt("%s_file", uploads[n]); |
| 3168 | 3284 | ||
| 3169 | xs_list *uploaded_file = xs_dict_get(p_vars, var_name); | 3285 | const xs_list *uploaded_file = xs_dict_get(p_vars, var_name); |
| 3170 | if (xs_type(uploaded_file) == XSTYPE_LIST) { | 3286 | if (xs_type(uploaded_file) == XSTYPE_LIST) { |
| 3171 | const char *fn = xs_list_get(uploaded_file, 0); | 3287 | const char *fn = xs_list_get(uploaded_file, 0); |
| 3172 | 3288 | ||
| @@ -3231,7 +3347,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3231 | } | 3347 | } |
| 3232 | else | 3348 | else |
| 3233 | if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/ | 3349 | if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/ |
| 3234 | char *irt = xs_dict_get(p_vars, "irt"); | 3350 | const char *irt = xs_dict_get(p_vars, "irt"); |
| 3235 | const char *opt = xs_dict_get(p_vars, "question"); | 3351 | const char *opt = xs_dict_get(p_vars, "question"); |
| 3236 | const char *actor = xs_dict_get(p_vars, "actor"); | 3352 | const char *actor = xs_dict_get(p_vars, "actor"); |
| 3237 | 3353 | ||
| @@ -3246,7 +3362,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3246 | } | 3362 | } |
| 3247 | 3363 | ||
| 3248 | xs_list *p = ls; | 3364 | xs_list *p = ls; |
| 3249 | xs_str *v; | 3365 | const xs_str *v; |
| 3250 | 3366 | ||
| 3251 | while (xs_list_iter(&p, &v)) { | 3367 | while (xs_list_iter(&p, &v)) { |
| 3252 | xs *msg = msg_note(&snac, "", actor, irt, NULL, 1); | 3368 | xs *msg = msg_note(&snac, "", actor, irt, NULL, 1); |
| @@ -3261,11 +3377,30 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3261 | timeline_add(&snac, xs_dict_get(msg, "id"), msg); | 3377 | timeline_add(&snac, xs_dict_get(msg, "id"), msg); |
| 3262 | } | 3378 | } |
| 3263 | 3379 | ||
| 3380 | { | ||
| 3381 | /* get the poll object */ | ||
| 3382 | xs *poll = NULL; | ||
| 3383 | |||
| 3384 | if (valid_status(object_get(irt, &poll))) { | ||
| 3385 | const char *date = xs_dict_get(poll, "endTime"); | ||
| 3386 | if (xs_is_null(date)) | ||
| 3387 | date = xs_dict_get(poll, "closed"); | ||
| 3388 | |||
| 3389 | if (!xs_is_null(date)) { | ||
| 3390 | time_t t = xs_parse_iso_date(date, 0) - time(NULL); | ||
| 3391 | |||
| 3392 | /* request the poll when it's closed; | ||
| 3393 | Pleroma does not send and update when the poll closes */ | ||
| 3394 | enqueue_object_request(&snac, irt, t + 2); | ||
| 3395 | } | ||
| 3396 | } | ||
| 3397 | } | ||
| 3398 | |||
| 3264 | status = 303; | 3399 | status = 303; |
| 3265 | } | 3400 | } |
| 3266 | 3401 | ||
| 3267 | if (status == 303) { | 3402 | if (status == 303) { |
| 3268 | char *redir = xs_dict_get(p_vars, "redir"); | 3403 | const char *redir = xs_dict_get(p_vars, "redir"); |
| 3269 | 3404 | ||
| 3270 | if (xs_is_null(redir)) | 3405 | if (xs_is_null(redir)) |
| 3271 | redir = "top"; | 3406 | redir = "top"; |
| @@ -3278,3 +3413,71 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 3278 | 3413 | ||
| 3279 | return status; | 3414 | return status; |
| 3280 | } | 3415 | } |
| 3416 | |||
| 3417 | |||
| 3418 | xs_str *timeline_to_rss(snac *user, const xs_list *timeline, char *title, char *link, char *desc) | ||
| 3419 | /* converts a timeline to rss */ | ||
| 3420 | { | ||
| 3421 | xs_html *rss = xs_html_tag("rss", | ||
| 3422 | xs_html_attr("version", "0.91")); | ||
| 3423 | |||
| 3424 | xs_html *channel = xs_html_tag("channel", | ||
| 3425 | xs_html_tag("title", | ||
| 3426 | xs_html_text(title)), | ||
| 3427 | xs_html_tag("language", | ||
| 3428 | xs_html_text("en")), | ||
| 3429 | xs_html_tag("link", | ||
| 3430 | xs_html_text(link)), | ||
| 3431 | xs_html_tag("description", | ||
| 3432 | xs_html_text(desc))); | ||
| 3433 | |||
| 3434 | xs_html_add(rss, channel); | ||
| 3435 | |||
| 3436 | int c = 0; | ||
| 3437 | const char *v; | ||
| 3438 | |||
| 3439 | while (xs_list_next(timeline, &v, &c)) { | ||
| 3440 | xs *msg = NULL; | ||
| 3441 | |||
| 3442 | if (user) { | ||
| 3443 | if (!valid_status(timeline_get_by_md5(user, v, &msg))) | ||
| 3444 | continue; | ||
| 3445 | } | ||
| 3446 | else { | ||
| 3447 | if (!valid_status(object_get_by_md5(v, &msg))) | ||
| 3448 | continue; | ||
| 3449 | } | ||
| 3450 | |||
| 3451 | const char *id = xs_dict_get(msg, "id"); | ||
| 3452 | const char *content = xs_dict_get(msg, "content"); | ||
| 3453 | |||
| 3454 | if (user && !xs_startswith(id, user->actor)) | ||
| 3455 | continue; | ||
| 3456 | |||
| 3457 | /* create a title with the first line of the content */ | ||
| 3458 | xs *title = xs_replace(content, "<br>", "\n"); | ||
| 3459 | title = xs_regex_replace_i(title, "<[^>]+>", " "); | ||
| 3460 | title = xs_regex_replace_i(title, "&[^;]+;", " "); | ||
| 3461 | int i; | ||
| 3462 | |||
| 3463 | for (i = 0; title[i] && title[i] != '\n' && i < 50; i++); | ||
| 3464 | |||
| 3465 | if (title[i] != '\0') { | ||
| 3466 | title[i] = '\0'; | ||
| 3467 | title = xs_str_cat(title, "..."); | ||
| 3468 | } | ||
| 3469 | |||
| 3470 | title = xs_strip_i(title); | ||
| 3471 | |||
| 3472 | xs_html_add(channel, | ||
| 3473 | xs_html_tag("item", | ||
| 3474 | xs_html_tag("title", | ||
| 3475 | xs_html_text(title)), | ||
| 3476 | xs_html_tag("link", | ||
| 3477 | xs_html_text(id)), | ||
| 3478 | xs_html_tag("description", | ||
| 3479 | xs_html_text(content)))); | ||
| 3480 | } | ||
| 3481 | |||
| 3482 | return xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | ||
| 3483 | } | ||
| @@ -12,7 +12,7 @@ | |||
| 12 | 12 | ||
| 13 | xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, | 13 | xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, |
| 14 | const char *method, const char *url, | 14 | const char *method, const char *url, |
| 15 | xs_dict *headers, | 15 | const xs_dict *headers, |
| 16 | const char *body, int b_size, | 16 | const char *body, int b_size, |
| 17 | int *status, xs_str **payload, int *p_size, | 17 | int *status, xs_str **payload, int *p_size, |
| 18 | int timeout) | 18 | int timeout) |
| @@ -24,15 +24,16 @@ xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, | |||
| 24 | xs *s64 = NULL; | 24 | xs *s64 = NULL; |
| 25 | xs *signature = NULL; | 25 | xs *signature = NULL; |
| 26 | xs *hdrs = NULL; | 26 | xs *hdrs = NULL; |
| 27 | char *host; | 27 | const char *host; |
| 28 | char *target; | 28 | const char *target; |
| 29 | char *k, *v; | 29 | const char *k, *v; |
| 30 | xs_dict *response; | 30 | xs_dict *response; |
| 31 | 31 | ||
| 32 | date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT"); | 32 | date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT"); |
| 33 | 33 | ||
| 34 | { | 34 | { |
| 35 | xs *s = xs_replace_n(url, "https:/" "/", "", 1); | 35 | xs *s1 = xs_replace_n(url, "http:/" "/", "", 1); |
| 36 | xs *s = xs_replace_n(s1, "https:/" "/", "", 1); | ||
| 36 | l1 = xs_split_n(s, "/", 1); | 37 | l1 = xs_split_n(s, "/", 1); |
| 37 | } | 38 | } |
| 38 | 39 | ||
| @@ -105,13 +106,13 @@ xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, | |||
| 105 | 106 | ||
| 106 | 107 | ||
| 107 | xs_dict *http_signed_request(snac *snac, const char *method, const char *url, | 108 | xs_dict *http_signed_request(snac *snac, const char *method, const char *url, |
| 108 | xs_dict *headers, | 109 | const xs_dict *headers, |
| 109 | const char *body, int b_size, | 110 | const char *body, int b_size, |
| 110 | int *status, xs_str **payload, int *p_size, | 111 | int *status, xs_str **payload, int *p_size, |
| 111 | int timeout) | 112 | int timeout) |
| 112 | /* does a signed HTTP request */ | 113 | /* does a signed HTTP request */ |
| 113 | { | 114 | { |
| 114 | char *seckey = xs_dict_get(snac->key, "secret"); | 115 | const char *seckey = xs_dict_get(snac->key, "secret"); |
| 115 | xs_dict *response; | 116 | xs_dict *response; |
| 116 | 117 | ||
| 117 | response = http_signed_request_raw(snac->actor, seckey, method, url, | 118 | response = http_signed_request_raw(snac->actor, seckey, method, url, |
| @@ -121,17 +122,18 @@ xs_dict *http_signed_request(snac *snac, const char *method, const char *url, | |||
| 121 | } | 122 | } |
| 122 | 123 | ||
| 123 | 124 | ||
| 124 | int check_signature(xs_dict *req, xs_str **err) | 125 | int check_signature(const xs_dict *req, xs_str **err) |
| 125 | /* check the signature */ | 126 | /* check the signature */ |
| 126 | { | 127 | { |
| 127 | char *sig_hdr = xs_dict_get(req, "signature"); | 128 | const char *sig_hdr = xs_dict_get(req, "signature"); |
| 128 | xs *keyId = NULL; | 129 | xs *keyId = NULL; |
| 129 | xs *headers = NULL; | 130 | xs *headers = NULL; |
| 130 | xs *signature = NULL; | 131 | xs *signature = NULL; |
| 131 | xs *created = NULL; | 132 | xs *created = NULL; |
| 132 | xs *expires = NULL; | 133 | xs *expires = NULL; |
| 133 | char *pubkey; | ||
| 134 | char *p; | 134 | char *p; |
| 135 | const char *pubkey; | ||
| 136 | const char *k; | ||
| 135 | 137 | ||
| 136 | if (xs_is_null(sig_hdr)) { | 138 | if (xs_is_null(sig_hdr)) { |
| 137 | *err = xs_fmt("missing 'signature' header"); | 139 | *err = xs_fmt("missing 'signature' header"); |
| @@ -141,10 +143,10 @@ int check_signature(xs_dict *req, xs_str **err) | |||
| 141 | { | 143 | { |
| 142 | /* extract the values */ | 144 | /* extract the values */ |
| 143 | xs *l = xs_split(sig_hdr, ","); | 145 | xs *l = xs_split(sig_hdr, ","); |
| 144 | xs_list *p = l; | 146 | int c = 0; |
| 145 | xs_val *v; | 147 | const xs_val *v; |
| 146 | 148 | ||
| 147 | while (xs_list_iter(&p, &v)) { | 149 | while (xs_list_next(l, &v, &c)) { |
| 148 | xs *kv = xs_split_n(v, "=", 1); | 150 | xs *kv = xs_split_n(v, "=", 1); |
| 149 | 151 | ||
| 150 | if (xs_list_len(kv) != 2) | 152 | if (xs_list_len(kv) != 2) |
| @@ -191,8 +193,8 @@ int check_signature(xs_dict *req, xs_str **err) | |||
| 191 | return 0; | 193 | return 0; |
| 192 | } | 194 | } |
| 193 | 195 | ||
| 194 | if ((p = xs_dict_get(actor, "publicKey")) == NULL || | 196 | if ((k = xs_dict_get(actor, "publicKey")) == NULL || |
| 195 | ((pubkey = xs_dict_get(p, "publicKeyPem")) == NULL)) { | 197 | ((pubkey = xs_dict_get(k, "publicKeyPem")) == NULL)) { |
| 196 | *err = xs_fmt("cannot get pubkey from %s", keyId); | 198 | *err = xs_fmt("cannot get pubkey from %s", keyId); |
| 197 | return 0; | 199 | return 0; |
| 198 | } | 200 | } |
| @@ -203,11 +205,11 @@ int check_signature(xs_dict *req, xs_str **err) | |||
| 203 | { | 205 | { |
| 204 | xs *l = xs_split(headers, " "); | 206 | xs *l = xs_split(headers, " "); |
| 205 | xs_list *p; | 207 | xs_list *p; |
| 206 | xs_val *v; | 208 | const xs_val *v; |
| 207 | 209 | ||
| 208 | p = l; | 210 | p = l; |
| 209 | while (xs_list_iter(&p, &v)) { | 211 | while (xs_list_iter(&p, &v)) { |
| 210 | char *hc; | 212 | const char *hc; |
| 211 | xs *ss = NULL; | 213 | xs *ss = NULL; |
| 212 | 214 | ||
| 213 | if (*sig_str != '\0') | 215 | if (*sig_str != '\0') |
| @@ -75,7 +75,7 @@ xs_str *nodeinfo_2_0(void) | |||
| 75 | int n_posts = 0; | 75 | int n_posts = 0; |
| 76 | xs *users = user_list(); | 76 | xs *users = user_list(); |
| 77 | xs_list *p = users; | 77 | xs_list *p = users; |
| 78 | char *v; | 78 | const char *v; |
| 79 | double now = (double)time(NULL); | 79 | double now = (double)time(NULL); |
| 80 | 80 | ||
| 81 | while (xs_list_iter(&p, &v)) { | 81 | while (xs_list_iter(&p, &v)) { |
| @@ -125,10 +125,10 @@ static xs_str *greeting_html(void) | |||
| 125 | 125 | ||
| 126 | /* does it have a %userlist% mark? */ | 126 | /* does it have a %userlist% mark? */ |
| 127 | if (xs_str_in(s, "%userlist%") != -1) { | 127 | if (xs_str_in(s, "%userlist%") != -1) { |
| 128 | char *host = xs_dict_get(srv_config, "host"); | 128 | const char *host = xs_dict_get(srv_config, "host"); |
| 129 | xs *list = user_list(); | 129 | xs *list = user_list(); |
| 130 | xs_list *p = list; | 130 | xs_list *p = list; |
| 131 | xs_str *uid; | 131 | const xs_str *uid; |
| 132 | 132 | ||
| 133 | xs_html *ul = xs_html_tag("ul", | 133 | xs_html *ul = xs_html_tag("ul", |
| 134 | xs_html_attr("class", "snac-user-list")); | 134 | xs_html_attr("class", "snac-user-list")); |
| @@ -169,18 +169,16 @@ int server_get_handler(xs_dict *req, const char *q_path, | |||
| 169 | { | 169 | { |
| 170 | int status = 0; | 170 | int status = 0; |
| 171 | 171 | ||
| 172 | (void)req; | ||
| 173 | |||
| 174 | /* is it the server root? */ | 172 | /* is it the server root? */ |
| 175 | if (*q_path == '\0') { | 173 | if (*q_path == '\0') { |
| 176 | xs_dict *q_vars = xs_dict_get(req, "q_vars"); | 174 | const xs_dict *q_vars = xs_dict_get(req, "q_vars"); |
| 177 | char *t = NULL; | 175 | const char *t = NULL; |
| 178 | 176 | ||
| 179 | if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { | 177 | if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { |
| 180 | /** search by tag **/ | 178 | /** search by tag **/ |
| 181 | int skip = 0; | 179 | int skip = 0; |
| 182 | int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); | 180 | int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); |
| 183 | char *v; | 181 | const char *v; |
| 184 | 182 | ||
| 185 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) | 183 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) |
| 186 | skip = atoi(v); | 184 | skip = atoi(v); |
| @@ -195,13 +193,25 @@ int server_get_handler(xs_dict *req, const char *q_path, | |||
| 195 | more = 1; | 193 | more = 1; |
| 196 | } | 194 | } |
| 197 | 195 | ||
| 198 | *body = html_timeline(NULL, tl, 0, skip, show, more, t, NULL, 0); | 196 | const char *accept = xs_dict_get(req, "accept"); |
| 197 | if (!xs_is_null(accept) && strcmp(accept, "application/rss+xml") == 0) { | ||
| 198 | xs *link = xs_fmt("%s/?t=%s", srv_baseurl, t); | ||
| 199 | |||
| 200 | *body = timeline_to_rss(NULL, tl, link, link, link); | ||
| 201 | *ctype = "application/rss+xml; charset=utf-8"; | ||
| 202 | } | ||
| 203 | else { | ||
| 204 | xs *page = xs_fmt("?t=%s", t); | ||
| 205 | xs *title = xs_fmt(L("Search results for tag #%s"), t); | ||
| 206 | *body = html_timeline(NULL, tl, 0, skip, show, more, title, page, 0); | ||
| 207 | } | ||
| 199 | } | 208 | } |
| 200 | else | 209 | else |
| 201 | if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) { | 210 | if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) { |
| 202 | /** instance timeline **/ | 211 | /** instance timeline **/ |
| 203 | xs *tl = timeline_instance_list(0, 30); | 212 | xs *tl = timeline_instance_list(0, 30); |
| 204 | *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL, NULL, 0); | 213 | *body = html_timeline(NULL, tl, 0, 0, 0, 0, |
| 214 | L("Recent posts by users in this instance"), NULL, 0); | ||
| 205 | } | 215 | } |
| 206 | else | 216 | else |
| 207 | *body = greeting_html(); | 217 | *body = greeting_html(); |
| @@ -258,7 +268,7 @@ void httpd_connection(FILE *f) | |||
| 258 | /* the connection processor */ | 268 | /* the connection processor */ |
| 259 | { | 269 | { |
| 260 | xs *req; | 270 | xs *req; |
| 261 | char *method; | 271 | const char *method; |
| 262 | int status = 0; | 272 | int status = 0; |
| 263 | xs_str *body = NULL; | 273 | xs_str *body = NULL; |
| 264 | int b_size = 0; | 274 | int b_size = 0; |
| @@ -268,7 +278,7 @@ void httpd_connection(FILE *f) | |||
| 268 | xs *payload = NULL; | 278 | xs *payload = NULL; |
| 269 | xs *etag = NULL; | 279 | xs *etag = NULL; |
| 270 | int p_size = 0; | 280 | int p_size = 0; |
| 271 | char *p; | 281 | const char *p; |
| 272 | int fcgi_id; | 282 | int fcgi_id; |
| 273 | 283 | ||
| 274 | if (p_state->use_fcgi) | 284 | if (p_state->use_fcgi) |
| @@ -360,7 +370,7 @@ void httpd_connection(FILE *f) | |||
| 360 | #ifndef NO_MASTODON_API | 370 | #ifndef NO_MASTODON_API |
| 361 | if (status == 0) | 371 | if (status == 0) |
| 362 | status = mastoapi_delete_handler(req, q_path, | 372 | status = mastoapi_delete_handler(req, q_path, |
| 363 | &body, &b_size, &ctype); | 373 | payload, p_size, &body, &b_size, &ctype); |
| 364 | #endif | 374 | #endif |
| 365 | } | 375 | } |
| 366 | 376 | ||
| @@ -401,9 +411,9 @@ void httpd_connection(FILE *f) | |||
| 401 | headers = xs_dict_append(headers, "etag", etag); | 411 | headers = xs_dict_append(headers, "etag", etag); |
| 402 | 412 | ||
| 403 | /* if there are any additional headers, add them */ | 413 | /* if there are any additional headers, add them */ |
| 404 | xs_dict *more_headers = xs_dict_get(srv_config, "http_headers"); | 414 | const xs_dict *more_headers = xs_dict_get(srv_config, "http_headers"); |
| 405 | if (xs_type(more_headers) == XSTYPE_DICT) { | 415 | if (xs_type(more_headers) == XSTYPE_DICT) { |
| 406 | char *k, *v; | 416 | const char *k, *v; |
| 407 | int c = 0; | 417 | int c = 0; |
| 408 | while (xs_dict_next(more_headers, &k, &v, &c)) | 418 | while (xs_dict_next(more_headers, &k, &v, &c)) |
| 409 | headers = xs_dict_set(headers, k, v); | 419 | headers = xs_dict_set(headers, k, v); |
| @@ -580,7 +590,8 @@ static void *background_thread(void *arg) | |||
| 580 | 590 | ||
| 581 | { | 591 | { |
| 582 | xs *list = user_list(); | 592 | xs *list = user_list(); |
| 583 | char *p, *uid; | 593 | char *p; |
| 594 | const char *uid; | ||
| 584 | 595 | ||
| 585 | /* process queues for all users */ | 596 | /* process queues for all users */ |
| 586 | p = list; | 597 | p = list; |
| @@ -654,6 +665,13 @@ srv_state *srv_state_op(xs_str **fname, int op) | |||
| 654 | 665 | ||
| 655 | switch (op) { | 666 | switch (op) { |
| 656 | case 0: /* open for writing */ | 667 | case 0: /* open for writing */ |
| 668 | |||
| 669 | #ifdef WITHOUT_SHM | ||
| 670 | |||
| 671 | errno = ENOTSUP; | ||
| 672 | |||
| 673 | #else | ||
| 674 | |||
| 657 | if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) { | 675 | if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) { |
| 658 | ftruncate(fd, sizeof(*ss)); | 676 | ftruncate(fd, sizeof(*ss)); |
| 659 | 677 | ||
| @@ -664,6 +682,8 @@ srv_state *srv_state_op(xs_str **fname, int op) | |||
| 664 | close(fd); | 682 | close(fd); |
| 665 | } | 683 | } |
| 666 | 684 | ||
| 685 | #endif | ||
| 686 | |||
| 667 | if (ss == NULL) { | 687 | if (ss == NULL) { |
| 668 | /* shared memory error: just create a plain structure */ | 688 | /* shared memory error: just create a plain structure */ |
| 669 | srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno))); | 689 | srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno))); |
| @@ -677,6 +697,13 @@ srv_state *srv_state_op(xs_str **fname, int op) | |||
| 677 | break; | 697 | break; |
| 678 | 698 | ||
| 679 | case 1: /* open for reading */ | 699 | case 1: /* open for reading */ |
| 700 | |||
| 701 | #ifdef WITHOUT_SHM | ||
| 702 | |||
| 703 | errno = ENOTSUP; | ||
| 704 | |||
| 705 | #else | ||
| 706 | |||
| 680 | if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) { | 707 | if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) { |
| 681 | if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) | 708 | if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) |
| 682 | ss = NULL; | 709 | ss = NULL; |
| @@ -684,6 +711,8 @@ srv_state *srv_state_op(xs_str **fname, int op) | |||
| 684 | close(fd); | 711 | close(fd); |
| 685 | } | 712 | } |
| 686 | 713 | ||
| 714 | #endif | ||
| 715 | |||
| 687 | if (ss == NULL) { | 716 | if (ss == NULL) { |
| 688 | /* shared memory error */ | 717 | /* shared memory error */ |
| 689 | srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno))); | 718 | srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno))); |
| @@ -701,9 +730,14 @@ srv_state *srv_state_op(xs_str **fname, int op) | |||
| 701 | break; | 730 | break; |
| 702 | 731 | ||
| 703 | case 2: /* unlink */ | 732 | case 2: /* unlink */ |
| 733 | |||
| 734 | #ifndef WITHOUT_SHM | ||
| 735 | |||
| 704 | if (*fname) | 736 | if (*fname) |
| 705 | shm_unlink(*fname); | 737 | shm_unlink(*fname); |
| 706 | 738 | ||
| 739 | #endif | ||
| 740 | |||
| 707 | break; | 741 | break; |
| 708 | } | 742 | } |
| 709 | 743 | ||
| @@ -44,6 +44,7 @@ int usage(void) | |||
| 44 | printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\n"); | 44 | printf("limit {basedir} {uid} {actor} Limits an actor (drops their announces)\n"); |
| 45 | printf("unlimit {basedir} {uid} {actor} Unlimits an actor\n"); | 45 | printf("unlimit {basedir} {uid} {actor} Unlimits an actor\n"); |
| 46 | printf("verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n"); | 46 | printf("verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n"); |
| 47 | printf("search {basedir} {uid} {regex} Searches posts by content\n"); | ||
| 47 | 48 | ||
| 48 | return 1; | 49 | return 1; |
| 49 | } | 50 | } |
| @@ -314,7 +315,7 @@ int main(int argc, char *argv[]) | |||
| 314 | xs *msg = msg_follow(&snac, url); | 315 | xs *msg = msg_follow(&snac, url); |
| 315 | 316 | ||
| 316 | if (msg != NULL) { | 317 | if (msg != NULL) { |
| 317 | char *actor = xs_dict_get(msg, "object"); | 318 | const char *actor = xs_dict_get(msg, "object"); |
| 318 | 319 | ||
| 319 | following_add(&snac, actor, msg); | 320 | following_add(&snac, actor, msg); |
| 320 | 321 | ||
| @@ -374,6 +375,23 @@ int main(int argc, char *argv[]) | |||
| 374 | return 0; | 375 | return 0; |
| 375 | } | 376 | } |
| 376 | 377 | ||
| 378 | if (strcmp(cmd, "search") == 0) { /** **/ | ||
| 379 | int to; | ||
| 380 | |||
| 381 | /* 'url' contains the regex */ | ||
| 382 | xs *r = content_search(&snac, url, 1, 0, XS_ALL, 10, &to); | ||
| 383 | |||
| 384 | int c = 0; | ||
| 385 | const char *v; | ||
| 386 | |||
| 387 | /* print results as standalone links */ | ||
| 388 | while (xs_list_next(r, &v, &c)) { | ||
| 389 | printf("%s/admin/p/%s\n", snac.actor, v); | ||
| 390 | } | ||
| 391 | |||
| 392 | return 0; | ||
| 393 | } | ||
| 394 | |||
| 377 | if (strcmp(cmd, "ping") == 0) { /** **/ | 395 | if (strcmp(cmd, "ping") == 0) { /** **/ |
| 378 | xs *actor_o = NULL; | 396 | xs *actor_o = NULL; |
| 379 | 397 | ||
| @@ -458,6 +476,12 @@ int main(int argc, char *argv[]) | |||
| 458 | return 0; | 476 | return 0; |
| 459 | } | 477 | } |
| 460 | 478 | ||
| 479 | if (strcmp(cmd, "request2") == 0) { /** **/ | ||
| 480 | enqueue_object_request(&snac, url, 2); | ||
| 481 | |||
| 482 | return 0; | ||
| 483 | } | ||
| 484 | |||
| 461 | if (strcmp(cmd, "actor") == 0) { /** **/ | 485 | if (strcmp(cmd, "actor") == 0) { /** **/ |
| 462 | int status; | 486 | int status; |
| 463 | xs *data = NULL; | 487 | xs *data = NULL; |
| @@ -156,7 +156,7 @@ const char *login_page = "" | |||
| 156 | "</head>\n" | 156 | "</head>\n" |
| 157 | "<body><h1>%s OAuth identify</h1>\n" | 157 | "<body><h1>%s OAuth identify</h1>\n" |
| 158 | "<div style=\"background-color: red; color: white\">%s</div>\n" | 158 | "<div style=\"background-color: red; color: white\">%s</div>\n" |
| 159 | "<form method=\"post\" action=\"https:/" "/%s/%s\">\n" | 159 | "<form method=\"post\" action=\"%s:/" "/%s/%s\">\n" |
| 160 | "<p>Login: <input type=\"text\" name=\"login\"></p>\n" | 160 | "<p>Login: <input type=\"text\" name=\"login\"></p>\n" |
| 161 | "<p>Password: <input type=\"password\" name=\"passwd\"></p>\n" | 161 | "<p>Password: <input type=\"password\" name=\"passwd\"></p>\n" |
| 162 | "<input type=\"hidden\" name=\"redir\" value=\"%s\">\n" | 162 | "<input type=\"hidden\" name=\"redir\" value=\"%s\">\n" |
| @@ -175,7 +175,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path, | |||
| 175 | return 0; | 175 | return 0; |
| 176 | 176 | ||
| 177 | int status = 404; | 177 | int status = 404; |
| 178 | xs_dict *msg = xs_dict_get(req, "q_vars"); | 178 | const xs_dict *msg = xs_dict_get(req, "q_vars"); |
| 179 | xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); | 179 | xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); |
| 180 | 180 | ||
| 181 | srv_debug(1, xs_fmt("oauth_get_handler %s", q_path)); | 181 | srv_debug(1, xs_fmt("oauth_get_handler %s", q_path)); |
| @@ -193,11 +193,12 @@ int oauth_get_handler(const xs_dict *req, const char *q_path, | |||
| 193 | 193 | ||
| 194 | if (app != NULL) { | 194 | if (app != NULL) { |
| 195 | const char *host = xs_dict_get(srv_config, "host"); | 195 | const char *host = xs_dict_get(srv_config, "host"); |
| 196 | const char *proto = xs_dict_get_def(srv_config, "protocol", "https"); | ||
| 196 | 197 | ||
| 197 | if (xs_is_null(state)) | 198 | if (xs_is_null(state)) |
| 198 | state = ""; | 199 | state = ""; |
| 199 | 200 | ||
| 200 | *body = xs_fmt(login_page, host, host, "", host, "oauth/x-snac-login", | 201 | *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-login", |
| 201 | ruri, cid, state, USER_AGENT); | 202 | ruri, cid, state, USER_AGENT); |
| 202 | *ctype = "text/html"; | 203 | *ctype = "text/html"; |
| 203 | status = 200; | 204 | status = 200; |
| @@ -213,8 +214,9 @@ int oauth_get_handler(const xs_dict *req, const char *q_path, | |||
| 213 | else | 214 | else |
| 214 | if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ | 215 | if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ |
| 215 | const char *host = xs_dict_get(srv_config, "host"); | 216 | const char *host = xs_dict_get(srv_config, "host"); |
| 217 | const char *proto = xs_dict_get_def(srv_config, "protocol", "https"); | ||
| 216 | 218 | ||
| 217 | *body = xs_fmt(login_page, host, host, "", host, "oauth/x-snac-get-token", | 219 | *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-get-token", |
| 218 | "", "", "", USER_AGENT); | 220 | "", "", "", USER_AGENT); |
| 219 | *ctype = "text/html"; | 221 | *ctype = "text/html"; |
| 220 | status = 200; | 222 | status = 200; |
| @@ -237,7 +239,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 237 | 239 | ||
| 238 | int status = 404; | 240 | int status = 404; |
| 239 | 241 | ||
| 240 | char *i_ctype = xs_dict_get(req, "content-type"); | 242 | const char *i_ctype = xs_dict_get(req, "content-type"); |
| 241 | xs *args = NULL; | 243 | xs *args = NULL; |
| 242 | 244 | ||
| 243 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { | 245 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { |
| @@ -265,11 +267,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 265 | const char *redir = xs_dict_get(args, "redir"); | 267 | const char *redir = xs_dict_get(args, "redir"); |
| 266 | const char *cid = xs_dict_get(args, "cid"); | 268 | const char *cid = xs_dict_get(args, "cid"); |
| 267 | const char *state = xs_dict_get(args, "state"); | 269 | const char *state = xs_dict_get(args, "state"); |
| 268 | 270 | const char *host = xs_dict_get(srv_config, "host"); | |
| 269 | const char *host = xs_dict_get(srv_config, "host"); | 271 | const char *proto = xs_dict_get_def(srv_config, "protocol", "https"); |
| 270 | 272 | ||
| 271 | /* by default, generate another login form with an error */ | 273 | /* by default, generate another login form with an error */ |
| 272 | *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", host, "oauth/x-snac-login", | 274 | *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-login", |
| 273 | redir, cid, state, USER_AGENT); | 275 | redir, cid, state, USER_AGENT); |
| 274 | *ctype = "text/html"; | 276 | *ctype = "text/html"; |
| 275 | status = 200; | 277 | status = 200; |
| @@ -289,7 +291,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 289 | *body = xs_dup(code); | 291 | *body = xs_dup(code); |
| 290 | } | 292 | } |
| 291 | else { | 293 | else { |
| 292 | *body = xs_fmt("%s?code=%s", redir, code); | 294 | if (xs_str_in(redir, "?") != -1) |
| 295 | *body = xs_fmt("%s&code=%s", redir, code); | ||
| 296 | else | ||
| 297 | *body = xs_fmt("%s?code=%s", redir, code); | ||
| 298 | |||
| 293 | status = 303; | 299 | status = 303; |
| 294 | } | 300 | } |
| 295 | 301 | ||
| @@ -446,11 +452,11 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 446 | if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ | 452 | if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ |
| 447 | const char *login = xs_dict_get(args, "login"); | 453 | const char *login = xs_dict_get(args, "login"); |
| 448 | const char *passwd = xs_dict_get(args, "passwd"); | 454 | const char *passwd = xs_dict_get(args, "passwd"); |
| 449 | 455 | const char *host = xs_dict_get(srv_config, "host"); | |
| 450 | const char *host = xs_dict_get(srv_config, "host"); | 456 | const char *proto = xs_dict_get_def(srv_config, "protocol", "https"); |
| 451 | 457 | ||
| 452 | /* by default, generate another login form with an error */ | 458 | /* by default, generate another login form with an error */ |
| 453 | *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", host, "oauth/x-snac-get-token", | 459 | *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-get-token", |
| 454 | "", "", "", USER_AGENT); | 460 | "", "", "", USER_AGENT); |
| 455 | *ctype = "text/html"; | 461 | *ctype = "text/html"; |
| 456 | status = 200; | 462 | status = 200; |
| @@ -544,6 +550,9 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 544 | acct = xs_dict_append(acct, "created_at", date); | 550 | acct = xs_dict_append(acct, "created_at", date); |
| 545 | } | 551 | } |
| 546 | 552 | ||
| 553 | xs *last_status_at = xs_str_utctime(0, "%Y-%m-%d"); | ||
| 554 | acct = xs_dict_append(acct, "last_status_at", last_status_at); | ||
| 555 | |||
| 547 | const char *note = xs_dict_get(actor, "summary"); | 556 | const char *note = xs_dict_get(actor, "summary"); |
| 548 | if (xs_is_null(note)) | 557 | if (xs_is_null(note)) |
| 549 | note = ""; | 558 | note = ""; |
| @@ -559,10 +568,10 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 559 | acct = xs_dict_append(acct, "uri", id); | 568 | acct = xs_dict_append(acct, "uri", id); |
| 560 | 569 | ||
| 561 | xs *avatar = NULL; | 570 | xs *avatar = NULL; |
| 562 | xs_dict *av = xs_dict_get(actor, "icon"); | 571 | const xs_dict *av = xs_dict_get(actor, "icon"); |
| 563 | 572 | ||
| 564 | if (xs_type(av) == XSTYPE_DICT) { | 573 | if (xs_type(av) == XSTYPE_DICT) { |
| 565 | char *url = xs_dict_get(av, "url"); | 574 | const char *url = xs_dict_get(av, "url"); |
| 566 | 575 | ||
| 567 | if (url != NULL) | 576 | if (url != NULL) |
| 568 | avatar = xs_dup(url); | 577 | avatar = xs_dup(url); |
| @@ -575,7 +584,7 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 575 | acct = xs_dict_append(acct, "avatar_static", avatar); | 584 | acct = xs_dict_append(acct, "avatar_static", avatar); |
| 576 | 585 | ||
| 577 | xs *header = NULL; | 586 | xs *header = NULL; |
| 578 | xs_dict *hd = xs_dict_get(actor, "image"); | 587 | const xs_dict *hd = xs_dict_get(actor, "image"); |
| 579 | 588 | ||
| 580 | if (xs_type(hd) == XSTYPE_DICT) | 589 | if (xs_type(hd) == XSTYPE_DICT) |
| 581 | header = xs_dup(xs_dict_get(hd, "url")); | 590 | header = xs_dup(xs_dict_get(hd, "url")); |
| @@ -587,12 +596,13 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 587 | acct = xs_dict_append(acct, "header_static", header); | 596 | acct = xs_dict_append(acct, "header_static", header); |
| 588 | 597 | ||
| 589 | /* emojis */ | 598 | /* emojis */ |
| 590 | xs_list *p; | 599 | const xs_list *p; |
| 591 | if (!xs_is_null(p = xs_dict_get(actor, "tag"))) { | 600 | if (!xs_is_null(p = xs_dict_get(actor, "tag"))) { |
| 592 | xs *eml = xs_list_new(); | 601 | xs *eml = xs_list_new(); |
| 593 | xs_dict *v; | 602 | const xs_dict *v; |
| 603 | int c = 0; | ||
| 594 | 604 | ||
| 595 | while (xs_list_iter(&p, &v)) { | 605 | while (xs_list_next(p, &v, &c)) { |
| 596 | const char *type = xs_dict_get(v, "type"); | 606 | const char *type = xs_dict_get(v, "type"); |
| 597 | 607 | ||
| 598 | if (!xs_is_null(type) && strcmp(type, "Emoji") == 0) { | 608 | if (!xs_is_null(type) && strcmp(type, "Emoji") == 0) { |
| @@ -627,11 +637,11 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 627 | 637 | ||
| 628 | xs *fields = xs_list_new(); | 638 | xs *fields = xs_list_new(); |
| 629 | p = xs_dict_get(actor, "attachment"); | 639 | p = xs_dict_get(actor, "attachment"); |
| 630 | xs_dict *v; | 640 | const xs_dict *v; |
| 631 | 641 | ||
| 632 | /* dict of validated links */ | 642 | /* dict of validated links */ |
| 633 | xs_dict *val_links = NULL; | 643 | xs_dict *val_links = NULL; |
| 634 | xs_dict *metadata = xs_stock(XSTYPE_DICT); | 644 | const xs_dict *metadata = xs_stock(XSTYPE_DICT); |
| 635 | snac user = {0}; | 645 | snac user = {0}; |
| 636 | 646 | ||
| 637 | if (xs_startswith(id, srv_baseurl)) { | 647 | if (xs_startswith(id, srv_baseurl)) { |
| @@ -645,19 +655,20 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 645 | if (xs_is_null(val_links)) | 655 | if (xs_is_null(val_links)) |
| 646 | val_links = xs_stock(XSTYPE_DICT); | 656 | val_links = xs_stock(XSTYPE_DICT); |
| 647 | 657 | ||
| 648 | while (xs_list_iter(&p, &v)) { | 658 | int c = 0; |
| 649 | char *type = xs_dict_get(v, "type"); | 659 | while (xs_list_next(p, &v, &c)) { |
| 650 | char *name = xs_dict_get(v, "name"); | 660 | const char *type = xs_dict_get(v, "type"); |
| 651 | char *value = xs_dict_get(v, "value"); | 661 | const char *name = xs_dict_get(v, "name"); |
| 662 | const char *value = xs_dict_get(v, "value"); | ||
| 652 | 663 | ||
| 653 | if (!xs_is_null(type) && !xs_is_null(name) && | 664 | if (!xs_is_null(type) && !xs_is_null(name) && |
| 654 | !xs_is_null(value) && strcmp(type, "PropertyValue") == 0) { | 665 | !xs_is_null(value) && strcmp(type, "PropertyValue") == 0) { |
| 655 | xs *val_date = NULL; | 666 | xs *val_date = NULL; |
| 656 | 667 | ||
| 657 | char *url = xs_dict_get(metadata, name); | 668 | const char *url = xs_dict_get(metadata, name); |
| 658 | 669 | ||
| 659 | if (!xs_is_null(url) && xs_startswith(url, "https:/" "/")) { | 670 | if (!xs_is_null(url) && xs_startswith(url, "https:/" "/")) { |
| 660 | xs_number *verified_time = xs_dict_get(val_links, url); | 671 | const xs_number *verified_time = xs_dict_get(val_links, url); |
| 661 | if (xs_type(verified_time) == XSTYPE_NUMBER) { | 672 | if (xs_type(verified_time) == XSTYPE_NUMBER) { |
| 662 | time_t t = xs_number_get(verified_time); | 673 | time_t t = xs_number_get(verified_time); |
| 663 | 674 | ||
| @@ -686,7 +697,7 @@ xs_dict *mastoapi_account(const xs_dict *actor) | |||
| 686 | } | 697 | } |
| 687 | 698 | ||
| 688 | 699 | ||
| 689 | xs_str *mastoapi_date(char *date) | 700 | xs_str *mastoapi_date(const char *date) |
| 690 | /* converts an ISO 8601 date to whatever format Mastodon uses */ | 701 | /* converts an ISO 8601 date to whatever format Mastodon uses */ |
| 691 | { | 702 | { |
| 692 | xs_str *s = xs_crop_i(xs_dup(date), 0, 19); | 703 | xs_str *s = xs_crop_i(xs_dup(date), 0, 19); |
| @@ -701,16 +712,29 @@ xs_dict *mastoapi_poll(snac *snac, const xs_dict *msg) | |||
| 701 | { | 712 | { |
| 702 | xs_dict *poll = xs_dict_new(); | 713 | xs_dict *poll = xs_dict_new(); |
| 703 | xs *mid = mastoapi_id(msg); | 714 | xs *mid = mastoapi_id(msg); |
| 704 | xs_list *opts = NULL; | 715 | const xs_list *opts = NULL; |
| 705 | xs_val *v; | 716 | const xs_val *v; |
| 706 | int num_votes = 0; | 717 | int num_votes = 0; |
| 707 | xs *options = xs_list_new(); | 718 | xs *options = xs_list_new(); |
| 708 | 719 | ||
| 709 | poll = xs_dict_append(poll, "id", mid); | 720 | poll = xs_dict_append(poll, "id", mid); |
| 710 | xs *fd = mastoapi_date(xs_dict_get(msg, "endTime")); | 721 | const char *date = xs_dict_get(msg, "endTime"); |
| 722 | if (date == NULL) | ||
| 723 | date = xs_dict_get(msg, "closed"); | ||
| 724 | if (date == NULL) | ||
| 725 | return NULL; | ||
| 726 | |||
| 727 | xs *fd = mastoapi_date(date); | ||
| 711 | poll = xs_dict_append(poll, "expires_at", fd); | 728 | poll = xs_dict_append(poll, "expires_at", fd); |
| 729 | |||
| 730 | date = xs_dict_get(msg, "closed"); | ||
| 731 | time_t t = 0; | ||
| 732 | |||
| 733 | if (date != NULL) | ||
| 734 | t = xs_parse_iso_date(date, 0); | ||
| 735 | |||
| 712 | poll = xs_dict_append(poll, "expired", | 736 | poll = xs_dict_append(poll, "expired", |
| 713 | xs_dict_get(msg, "closed") != NULL ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); | 737 | t < time(NULL) ? xs_stock(XSTYPE_FALSE) : xs_stock(XSTYPE_TRUE)); |
| 714 | 738 | ||
| 715 | if ((opts = xs_dict_get(msg, "oneOf")) != NULL) | 739 | if ((opts = xs_dict_get(msg, "oneOf")) != NULL) |
| 716 | poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_FALSE)); | 740 | poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_FALSE)); |
| @@ -719,7 +743,8 @@ xs_dict *mastoapi_poll(snac *snac, const xs_dict *msg) | |||
| 719 | poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_TRUE)); | 743 | poll = xs_dict_append(poll, "multiple", xs_stock(XSTYPE_TRUE)); |
| 720 | } | 744 | } |
| 721 | 745 | ||
| 722 | while (xs_list_iter(&opts, &v)) { | 746 | int c = 0; |
| 747 | while (xs_list_next(opts, &v, &c)) { | ||
| 723 | const char *title = xs_dict_get(v, "name"); | 748 | const char *title = xs_dict_get(v, "name"); |
| 724 | const char *replies = xs_dict_get(v, "replies"); | 749 | const char *replies = xs_dict_get(v, "replies"); |
| 725 | 750 | ||
| @@ -772,7 +797,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 772 | 797 | ||
| 773 | xs *idx = NULL; | 798 | xs *idx = NULL; |
| 774 | xs *ixc = NULL; | 799 | xs *ixc = NULL; |
| 775 | char *tmp; | 800 | const char *tmp; |
| 776 | xs *mid = mastoapi_id(msg); | 801 | xs *mid = mastoapi_id(msg); |
| 777 | 802 | ||
| 778 | xs_dict *st = xs_dict_new(); | 803 | xs_dict *st = xs_dict_new(); |
| @@ -824,14 +849,14 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 824 | 849 | ||
| 825 | { | 850 | { |
| 826 | xs_list *p = attach; | 851 | xs_list *p = attach; |
| 827 | xs_dict *v; | 852 | const xs_dict *v; |
| 828 | 853 | ||
| 829 | xs *matt = xs_list_new(); | 854 | xs *matt = xs_list_new(); |
| 830 | 855 | ||
| 831 | while (xs_list_iter(&p, &v)) { | 856 | while (xs_list_iter(&p, &v)) { |
| 832 | char *type = xs_dict_get(v, "type"); | 857 | const char *type = xs_dict_get(v, "type"); |
| 833 | char *href = xs_dict_get(v, "href"); | 858 | const char *href = xs_dict_get(v, "href"); |
| 834 | char *name = xs_dict_get(v, "name"); | 859 | const char *name = xs_dict_get(v, "name"); |
| 835 | 860 | ||
| 836 | if (xs_match(type, "image/*|video/*|Image|Video")) { /* */ | 861 | if (xs_match(type, "image/*|video/*|Image|Video")) { /* */ |
| 837 | xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt)); | 862 | xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt)); |
| @@ -857,7 +882,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 857 | xs *ml = xs_list_new(); | 882 | xs *ml = xs_list_new(); |
| 858 | xs *htl = xs_list_new(); | 883 | xs *htl = xs_list_new(); |
| 859 | xs *eml = xs_list_new(); | 884 | xs *eml = xs_list_new(); |
| 860 | xs_list *tag = xs_dict_get(msg, "tag"); | 885 | const xs_list *tag = xs_dict_get(msg, "tag"); |
| 861 | int n = 0; | 886 | int n = 0; |
| 862 | 887 | ||
| 863 | xs *tag_list = NULL; | 888 | xs *tag_list = NULL; |
| @@ -873,9 +898,10 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 873 | tag_list = xs_list_new(); | 898 | tag_list = xs_list_new(); |
| 874 | 899 | ||
| 875 | tag = tag_list; | 900 | tag = tag_list; |
| 876 | xs_dict *v; | 901 | const xs_dict *v; |
| 877 | 902 | ||
| 878 | while (xs_list_iter(&tag, &v)) { | 903 | int c = 0; |
| 904 | while (xs_list_next(tag, &v, &c)) { | ||
| 879 | const char *type = xs_dict_get(v, "type"); | 905 | const char *type = xs_dict_get(v, "type"); |
| 880 | 906 | ||
| 881 | if (xs_is_null(type)) | 907 | if (xs_is_null(type)) |
| @@ -984,7 +1010,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 984 | xs *irt_mid = mastoapi_id(irto); | 1010 | xs *irt_mid = mastoapi_id(irto); |
| 985 | st = xs_dict_set(st, "in_reply_to_id", irt_mid); | 1011 | st = xs_dict_set(st, "in_reply_to_id", irt_mid); |
| 986 | 1012 | ||
| 987 | char *at = NULL; | 1013 | const char *at = NULL; |
| 988 | if (!xs_is_null(at = get_atto(irto))) { | 1014 | if (!xs_is_null(at = get_atto(irto))) { |
| 989 | xs *at_md5 = xs_md5_hex(at, strlen(at)); | 1015 | xs *at_md5 = xs_md5_hex(at, strlen(at)); |
| 990 | st = xs_dict_set(st, "in_reply_to_account_id", at_md5); | 1016 | st = xs_dict_set(st, "in_reply_to_account_id", at_md5); |
| @@ -994,7 +1020,10 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) | |||
| 994 | 1020 | ||
| 995 | st = xs_dict_append(st, "reblog", xs_stock(XSTYPE_NULL)); | 1021 | st = xs_dict_append(st, "reblog", xs_stock(XSTYPE_NULL)); |
| 996 | st = xs_dict_append(st, "card", xs_stock(XSTYPE_NULL)); | 1022 | st = xs_dict_append(st, "card", xs_stock(XSTYPE_NULL)); |
| 997 | st = xs_dict_append(st, "language", xs_stock(XSTYPE_NULL)); | 1023 | st = xs_dict_append(st, "language", "en"); |
| 1024 | |||
| 1025 | st = xs_dict_append(st, "filtered", xs_stock(XSTYPE_LIST)); | ||
| 1026 | st = xs_dict_append(st, "muted", xs_stock(XSTYPE_FALSE)); | ||
| 998 | 1027 | ||
| 999 | tmp = xs_dict_get(msg, "sourceContent"); | 1028 | tmp = xs_dict_get(msg, "sourceContent"); |
| 1000 | if (xs_is_null(tmp)) | 1029 | if (xs_is_null(tmp)) |
| @@ -1093,7 +1122,7 @@ int process_auth_token(snac *snac, const xs_dict *req) | |||
| 1093 | /* processes an authorization token, if there is one */ | 1122 | /* processes an authorization token, if there is one */ |
| 1094 | { | 1123 | { |
| 1095 | int logged_in = 0; | 1124 | int logged_in = 0; |
| 1096 | char *v; | 1125 | const char *v; |
| 1097 | 1126 | ||
| 1098 | /* if there is an authorization field, try to validate it */ | 1127 | /* if there is an authorization field, try to validate it */ |
| 1099 | if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) { | 1128 | if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) { |
| @@ -1131,7 +1160,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1131 | return 0; | 1160 | return 0; |
| 1132 | 1161 | ||
| 1133 | int status = 404; | 1162 | int status = 404; |
| 1134 | xs_dict *args = xs_dict_get(req, "q_vars"); | 1163 | const xs_dict *args = xs_dict_get(req, "q_vars"); |
| 1135 | xs *cmd = xs_replace_n(q_path, "/api", "", 1); | 1164 | xs *cmd = xs_replace_n(q_path, "/api", "", 1); |
| 1136 | 1165 | ||
| 1137 | snac snac1 = {0}; | 1166 | snac snac1 = {0}; |
| @@ -1157,7 +1186,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1157 | acct = xs_dict_append(acct, "source", src); | 1186 | acct = xs_dict_append(acct, "source", src); |
| 1158 | 1187 | ||
| 1159 | xs *avatar = NULL; | 1188 | xs *avatar = NULL; |
| 1160 | char *av = xs_dict_get(snac1.config, "avatar"); | 1189 | const char *av = xs_dict_get(snac1.config, "avatar"); |
| 1161 | 1190 | ||
| 1162 | if (xs_is_null(av) || *av == '\0') | 1191 | if (xs_is_null(av) || *av == '\0') |
| 1163 | avatar = xs_fmt("%s/susie.png", srv_baseurl); | 1192 | avatar = xs_fmt("%s/susie.png", srv_baseurl); |
| @@ -1168,7 +1197,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1168 | acct = xs_dict_append(acct, "avatar_static", avatar); | 1197 | acct = xs_dict_append(acct, "avatar_static", avatar); |
| 1169 | 1198 | ||
| 1170 | xs *header = NULL; | 1199 | xs *header = NULL; |
| 1171 | char *hd = xs_dict_get(snac1.config, "header"); | 1200 | const char *hd = xs_dict_get(snac1.config, "header"); |
| 1172 | 1201 | ||
| 1173 | if (!xs_is_null(hd)) | 1202 | if (!xs_is_null(hd)) |
| 1174 | header = xs_dup(hd); | 1203 | header = xs_dup(hd); |
| @@ -1178,11 +1207,11 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1178 | acct = xs_dict_append(acct, "header", header); | 1207 | acct = xs_dict_append(acct, "header", header); |
| 1179 | acct = xs_dict_append(acct, "header_static", header); | 1208 | acct = xs_dict_append(acct, "header_static", header); |
| 1180 | 1209 | ||
| 1181 | xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); | 1210 | const xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); |
| 1182 | if (xs_type(metadata) == XSTYPE_DICT) { | 1211 | if (xs_type(metadata) == XSTYPE_DICT) { |
| 1183 | xs *fields = xs_list_new(); | 1212 | xs *fields = xs_list_new(); |
| 1184 | xs_str *k; | 1213 | const xs_str *k; |
| 1185 | xs_str *v; | 1214 | const xs_str *v; |
| 1186 | 1215 | ||
| 1187 | xs_dict *val_links = snac1.links; | 1216 | xs_dict *val_links = snac1.links; |
| 1188 | if (xs_is_null(val_links)) | 1217 | if (xs_is_null(val_links)) |
| @@ -1192,7 +1221,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1192 | while (xs_dict_next(metadata, &k, &v, &c)) { | 1221 | while (xs_dict_next(metadata, &k, &v, &c)) { |
| 1193 | xs *val_date = NULL; | 1222 | xs *val_date = NULL; |
| 1194 | 1223 | ||
| 1195 | xs_number *verified_time = xs_dict_get(val_links, v); | 1224 | const xs_number *verified_time = xs_dict_get(val_links, v); |
| 1196 | if (xs_type(verified_time) == XSTYPE_NUMBER) { | 1225 | if (xs_type(verified_time) == XSTYPE_NUMBER) { |
| 1197 | time_t t = xs_number_get(verified_time); | 1226 | time_t t = xs_number_get(verified_time); |
| 1198 | 1227 | ||
| @@ -1258,13 +1287,13 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1258 | else | 1287 | else |
| 1259 | if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/ | 1288 | if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/ |
| 1260 | /* lookup an account */ | 1289 | /* lookup an account */ |
| 1261 | char *acct = xs_dict_get(args, "acct"); | 1290 | const char *acct = xs_dict_get(args, "acct"); |
| 1262 | 1291 | ||
| 1263 | if (!xs_is_null(acct)) { | 1292 | if (!xs_is_null(acct)) { |
| 1264 | xs *s = xs_strip_chars_i(xs_dup(acct), "@"); | 1293 | xs *s = xs_strip_chars_i(xs_dup(acct), "@"); |
| 1265 | xs *l = xs_split_n(s, "@", 1); | 1294 | xs *l = xs_split_n(s, "@", 1); |
| 1266 | char *uid = xs_list_get(l, 0); | 1295 | const char *uid = xs_list_get(l, 0); |
| 1267 | char *host = xs_list_get(l, 1); | 1296 | const char *host = xs_list_get(l, 1); |
| 1268 | 1297 | ||
| 1269 | if (uid && (!host || strcmp(host, xs_dict_get(srv_config, "host")) == 0)) { | 1298 | if (uid && (!host || strcmp(host, xs_dict_get(srv_config, "host")) == 0)) { |
| 1270 | snac user; | 1299 | snac user; |
| @@ -1305,7 +1334,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1305 | xs *wers = follower_list(&snac1); | 1334 | xs *wers = follower_list(&snac1); |
| 1306 | xs *ulst = user_list(); | 1335 | xs *ulst = user_list(); |
| 1307 | xs_list *p; | 1336 | xs_list *p; |
| 1308 | xs_str *v; | 1337 | const xs_str *v; |
| 1309 | xs_set seen; | 1338 | xs_set seen; |
| 1310 | 1339 | ||
| 1311 | xs_set_init(&seen); | 1340 | xs_set_init(&seen); |
| @@ -1381,7 +1410,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1381 | /* the public list of posts of a user */ | 1410 | /* the public list of posts of a user */ |
| 1382 | xs *timeline = timeline_simple_list(&snac2, "public", 0, 256); | 1411 | xs *timeline = timeline_simple_list(&snac2, "public", 0, 256); |
| 1383 | xs_list *p = timeline; | 1412 | xs_list *p = timeline; |
| 1384 | xs_str *v; | 1413 | const xs_str *v; |
| 1385 | 1414 | ||
| 1386 | out = xs_list_new(); | 1415 | out = xs_list_new(); |
| 1387 | 1416 | ||
| @@ -1446,7 +1475,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1446 | 1475 | ||
| 1447 | xs *out = xs_list_new(); | 1476 | xs *out = xs_list_new(); |
| 1448 | xs_list *p = timeline; | 1477 | xs_list *p = timeline; |
| 1449 | xs_str *v; | 1478 | const xs_str *v; |
| 1450 | 1479 | ||
| 1451 | while (xs_list_iter(&p, &v) && cnt < limit) { | 1480 | while (xs_list_iter(&p, &v) && cnt < limit) { |
| 1452 | xs *msg = NULL; | 1481 | xs *msg = NULL; |
| @@ -1479,7 +1508,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1479 | /* discard non-Notes */ | 1508 | /* discard non-Notes */ |
| 1480 | const char *id = xs_dict_get(msg, "id"); | 1509 | const char *id = xs_dict_get(msg, "id"); |
| 1481 | const char *type = xs_dict_get(msg, "type"); | 1510 | const char *type = xs_dict_get(msg, "type"); |
| 1482 | if (!xs_match(type, "Note|Question|Page|Article|Video")) | 1511 | if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) |
| 1483 | continue; | 1512 | continue; |
| 1484 | 1513 | ||
| 1485 | const char *from = NULL; | 1514 | const char *from = NULL; |
| @@ -1550,7 +1579,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1550 | xs *timeline = timeline_instance_list(0, limit); | 1579 | xs *timeline = timeline_instance_list(0, limit); |
| 1551 | xs *out = xs_list_new(); | 1580 | xs *out = xs_list_new(); |
| 1552 | xs_list *p = timeline; | 1581 | xs_list *p = timeline; |
| 1553 | xs_str *md5; | 1582 | const xs_str *md5; |
| 1554 | 1583 | ||
| 1555 | snac *user = NULL; | 1584 | snac *user = NULL; |
| 1556 | if (logged_in) | 1585 | if (logged_in) |
| @@ -1599,12 +1628,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1599 | 1628 | ||
| 1600 | /* get the tag */ | 1629 | /* get the tag */ |
| 1601 | xs *l = xs_split(cmd, "/"); | 1630 | xs *l = xs_split(cmd, "/"); |
| 1602 | char *tag = xs_list_get(l, -1); | 1631 | const char *tag = xs_list_get(l, -1); |
| 1603 | 1632 | ||
| 1604 | xs *timeline = tag_search(tag, 0, limit); | 1633 | xs *timeline = tag_search(tag, 0, limit); |
| 1605 | xs *out = xs_list_new(); | 1634 | xs *out = xs_list_new(); |
| 1606 | xs_list *p = timeline; | 1635 | xs_list *p = timeline; |
| 1607 | xs_str *md5; | 1636 | const xs_str *md5; |
| 1608 | 1637 | ||
| 1609 | while (xs_list_iter(&p, &md5) && cnt < limit) { | 1638 | while (xs_list_iter(&p, &md5) && cnt < limit) { |
| 1610 | xs *msg = NULL; | 1639 | xs *msg = NULL; |
| @@ -1635,6 +1664,77 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1635 | status = 200; | 1664 | status = 200; |
| 1636 | } | 1665 | } |
| 1637 | else | 1666 | else |
| 1667 | if (xs_startswith(cmd, "/v1/timelines/list/")) { /** **/ | ||
| 1668 | /* get the list id */ | ||
| 1669 | if (logged_in) { | ||
| 1670 | xs *l = xs_split(cmd, "/"); | ||
| 1671 | const char *list = xs_list_get(l, -1); | ||
| 1672 | |||
| 1673 | xs *timeline = list_timeline(&snac1, list, 0, 2048); | ||
| 1674 | xs *out = xs_list_new(); | ||
| 1675 | int c = 0; | ||
| 1676 | const char *md5; | ||
| 1677 | |||
| 1678 | while (xs_list_next(timeline, &md5, &c)) { | ||
| 1679 | xs *msg = NULL; | ||
| 1680 | |||
| 1681 | /* get the entry */ | ||
| 1682 | if (!valid_status(timeline_get_by_md5(&snac1, md5, &msg))) | ||
| 1683 | continue; | ||
| 1684 | |||
| 1685 | /* discard non-Notes */ | ||
| 1686 | const char *id = xs_dict_get(msg, "id"); | ||
| 1687 | const char *type = xs_dict_get(msg, "type"); | ||
| 1688 | if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) | ||
| 1689 | continue; | ||
| 1690 | |||
| 1691 | const char *from = NULL; | ||
| 1692 | if (strcmp(type, "Page") == 0) | ||
| 1693 | from = xs_dict_get(msg, "audience"); | ||
| 1694 | |||
| 1695 | if (from == NULL) | ||
| 1696 | from = get_atto(msg); | ||
| 1697 | |||
| 1698 | if (from == NULL) | ||
| 1699 | continue; | ||
| 1700 | |||
| 1701 | /* is this message from a person we don't follow? */ | ||
| 1702 | if (strcmp(from, snac1.actor) && !following_check(&snac1, from)) { | ||
| 1703 | /* discard if it was not boosted */ | ||
| 1704 | xs *idx = object_announces(id); | ||
| 1705 | |||
| 1706 | if (xs_list_len(idx) == 0) | ||
| 1707 | continue; | ||
| 1708 | } | ||
| 1709 | |||
| 1710 | /* discard notes from muted morons */ | ||
| 1711 | if (is_muted(&snac1, from)) | ||
| 1712 | continue; | ||
| 1713 | |||
| 1714 | /* discard hidden notes */ | ||
| 1715 | if (is_hidden(&snac1, id)) | ||
| 1716 | continue; | ||
| 1717 | |||
| 1718 | /* if it has a name and it's not a Page or a Video, | ||
| 1719 | it's a poll vote, so discard it */ | ||
| 1720 | if (!xs_is_null(xs_dict_get(msg, "name")) && !xs_match(type, "Page|Video")) | ||
| 1721 | continue; | ||
| 1722 | |||
| 1723 | /* convert the Note into a Mastodon status */ | ||
| 1724 | xs *st = mastoapi_status(&snac1, msg); | ||
| 1725 | |||
| 1726 | if (st != NULL) | ||
| 1727 | out = xs_list_append(out, st); | ||
| 1728 | } | ||
| 1729 | |||
| 1730 | *body = xs_json_dumps(out, 4); | ||
| 1731 | *ctype = "application/json"; | ||
| 1732 | status = 200; | ||
| 1733 | } | ||
| 1734 | else | ||
| 1735 | status = 421; | ||
| 1736 | } | ||
| 1737 | else | ||
| 1638 | if (strcmp(cmd, "/v1/conversations") == 0) { /** **/ | 1738 | if (strcmp(cmd, "/v1/conversations") == 0) { /** **/ |
| 1639 | /* TBD */ | 1739 | /* TBD */ |
| 1640 | *body = xs_dup("[]"); | 1740 | *body = xs_dup("[]"); |
| @@ -1647,8 +1747,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1647 | xs *l = notify_list(&snac1, 0, 64); | 1747 | xs *l = notify_list(&snac1, 0, 64); |
| 1648 | xs *out = xs_list_new(); | 1748 | xs *out = xs_list_new(); |
| 1649 | xs_list *p = l; | 1749 | xs_list *p = l; |
| 1650 | xs_dict *v; | 1750 | const xs_dict *v; |
| 1651 | xs_list *excl = xs_dict_get(args, "exclude_types[]"); | 1751 | const xs_list *excl = xs_dict_get(args, "exclude_types[]"); |
| 1652 | 1752 | ||
| 1653 | while (xs_list_iter(&p, &v)) { | 1753 | while (xs_list_iter(&p, &v)) { |
| 1654 | xs *noti = notify_get(&snac1, v); | 1754 | xs *noti = notify_get(&snac1, v); |
| @@ -1753,11 +1853,88 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1753 | status = 200; | 1853 | status = 200; |
| 1754 | } | 1854 | } |
| 1755 | else | 1855 | else |
| 1756 | if (strcmp(cmd, "/v1/lists") == 0) { /** **/ | 1856 | if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/ |
| 1757 | /* snac does not support lists */ | 1857 | if (logged_in) { |
| 1758 | *body = xs_dup("[]"); | 1858 | xs *lol = list_maint(&snac1, NULL, 0); |
| 1759 | *ctype = "application/json"; | 1859 | xs *l = xs_list_new(); |
| 1760 | status = 200; | 1860 | int c = 0; |
| 1861 | const xs_list *li; | ||
| 1862 | |||
| 1863 | while (xs_list_next(lol, &li, &c)) { | ||
| 1864 | xs *d = xs_dict_new(); | ||
| 1865 | |||
| 1866 | d = xs_dict_append(d, "id", xs_list_get(li, 0)); | ||
| 1867 | d = xs_dict_append(d, "title", xs_list_get(li, 1)); | ||
| 1868 | d = xs_dict_append(d, "replies_policy", "list"); | ||
| 1869 | d = xs_dict_append(d, "exclusive", xs_stock(XSTYPE_FALSE)); | ||
| 1870 | |||
| 1871 | l = xs_list_append(l, d); | ||
| 1872 | } | ||
| 1873 | |||
| 1874 | *body = xs_json_dumps(l, 4); | ||
| 1875 | *ctype = "application/json"; | ||
| 1876 | status = 200; | ||
| 1877 | } | ||
| 1878 | } | ||
| 1879 | else | ||
| 1880 | if (xs_startswith(cmd, "/v1/lists/")) { /** list information **/ | ||
| 1881 | if (logged_in) { | ||
| 1882 | xs *l = xs_split(cmd, "/"); | ||
| 1883 | const char *p = xs_list_get(l, -1); | ||
| 1884 | |||
| 1885 | if (p) { | ||
| 1886 | if (strcmp(p, "accounts") == 0) { | ||
| 1887 | p = xs_list_get(l, -2); | ||
| 1888 | |||
| 1889 | if (p && xs_is_hex(p)) { | ||
| 1890 | xs *actors = list_content(&snac1, p, NULL, 0); | ||
| 1891 | xs *out = xs_list_new(); | ||
| 1892 | int c = 0; | ||
| 1893 | const char *v; | ||
| 1894 | |||
| 1895 | while (xs_list_next(actors, &v, &c)) { | ||
| 1896 | xs *actor = NULL; | ||
| 1897 | |||
| 1898 | if (valid_status(object_get_by_md5(v, &actor))) { | ||
| 1899 | xs *acct = mastoapi_account(actor); | ||
| 1900 | out = xs_list_append(out, acct); | ||
| 1901 | } | ||
| 1902 | } | ||
| 1903 | |||
| 1904 | *body = xs_json_dumps(out, 4); | ||
| 1905 | *ctype = "application/json"; | ||
| 1906 | status = 200; | ||
| 1907 | } | ||
| 1908 | } | ||
| 1909 | else | ||
| 1910 | if (xs_is_hex(p)) { | ||
| 1911 | xs *out = xs_list_new(); | ||
| 1912 | xs *lol = list_maint(&snac1, NULL, 0); | ||
| 1913 | int c = 0; | ||
| 1914 | const xs_list *v; | ||
| 1915 | |||
| 1916 | while (xs_list_next(lol, &v, &c)) { | ||
| 1917 | const char *id = xs_list_get(v, 0); | ||
| 1918 | |||
| 1919 | if (id && strcmp(id, p) == 0) { | ||
| 1920 | xs *d = xs_dict_new(); | ||
| 1921 | |||
| 1922 | d = xs_dict_append(d, "id", p); | ||
| 1923 | d = xs_dict_append(d, "title", xs_list_get(v, 1)); | ||
| 1924 | d = xs_dict_append(d, "replies_policy", "list"); | ||
| 1925 | d = xs_dict_append(d, "exclusive", xs_stock(XSTYPE_FALSE)); | ||
| 1926 | |||
| 1927 | out = xs_list_append(out, d); | ||
| 1928 | break; | ||
| 1929 | } | ||
| 1930 | } | ||
| 1931 | |||
| 1932 | *body = xs_json_dumps(out, 4); | ||
| 1933 | *ctype = "application/json"; | ||
| 1934 | status = 200; | ||
| 1935 | } | ||
| 1936 | } | ||
| 1937 | } | ||
| 1761 | } | 1938 | } |
| 1762 | else | 1939 | else |
| 1763 | if (strcmp(cmd, "/v1/scheduled_statuses") == 0) { /** **/ | 1940 | if (strcmp(cmd, "/v1/scheduled_statuses") == 0) { /** **/ |
| @@ -1928,7 +2105,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1928 | xs *anc = xs_list_new(); | 2105 | xs *anc = xs_list_new(); |
| 1929 | xs *des = xs_list_new(); | 2106 | xs *des = xs_list_new(); |
| 1930 | xs_list *p; | 2107 | xs_list *p; |
| 1931 | xs_str *v; | 2108 | const xs_str *v; |
| 1932 | char pid[64]; | 2109 | char pid[64]; |
| 1933 | 2110 | ||
| 1934 | /* build the [grand]parent list, moving up */ | 2111 | /* build the [grand]parent list, moving up */ |
| @@ -1982,7 +2159,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1982 | l = object_likes(xs_dict_get(msg, "id")); | 2159 | l = object_likes(xs_dict_get(msg, "id")); |
| 1983 | 2160 | ||
| 1984 | xs_list *p = l; | 2161 | xs_list *p = l; |
| 1985 | xs_str *v; | 2162 | const xs_str *v; |
| 1986 | 2163 | ||
| 1987 | while (xs_list_iter(&p, &v)) { | 2164 | while (xs_list_iter(&p, &v)) { |
| 1988 | xs *actor2 = NULL; | 2165 | xs *actor2 = NULL; |
| @@ -2052,8 +2229,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2052 | /* reply something only for offset 0; otherwise, | 2229 | /* reply something only for offset 0; otherwise, |
| 2053 | apps like Tusky keep asking again and again */ | 2230 | apps like Tusky keep asking again and again */ |
| 2054 | 2231 | ||
| 2055 | if (!xs_is_null(q) && !xs_is_null(type)) { | 2232 | if (!xs_is_null(q)) { |
| 2056 | if (strcmp(type, "accounts") == 0) { | 2233 | if (xs_is_null(type) || strcmp(type, "accounts") == 0) { |
| 2057 | /* do a webfinger query */ | 2234 | /* do a webfinger query */ |
| 2058 | char *actor = NULL; | 2235 | char *actor = NULL; |
| 2059 | char *user = NULL; | 2236 | char *user = NULL; |
| @@ -2068,8 +2245,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2068 | } | 2245 | } |
| 2069 | } | 2246 | } |
| 2070 | } | 2247 | } |
| 2071 | else | 2248 | |
| 2072 | if (strcmp(type, "hashtags") == 0) { | 2249 | if (xs_is_null(type) || strcmp(type, "hashtags") == 0) { |
| 2073 | /* search this tag */ | 2250 | /* search this tag */ |
| 2074 | xs *tl = tag_search((char *)q, 0, 1); | 2251 | xs *tl = tag_search((char *)q, 0, 1); |
| 2075 | 2252 | ||
| @@ -2084,6 +2261,26 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2084 | htl = xs_list_append(htl, d); | 2261 | htl = xs_list_append(htl, d); |
| 2085 | } | 2262 | } |
| 2086 | } | 2263 | } |
| 2264 | |||
| 2265 | if (xs_is_null(type) || strcmp(type, "statuses") == 0) { | ||
| 2266 | int to = 0; | ||
| 2267 | int cnt = 40; | ||
| 2268 | xs *tl = content_search(&snac1, q, 1, 0, cnt, 0, &to); | ||
| 2269 | int c = 0; | ||
| 2270 | const char *v; | ||
| 2271 | |||
| 2272 | while (xs_list_next(tl, &v, &c) && --cnt) { | ||
| 2273 | xs *post = NULL; | ||
| 2274 | |||
| 2275 | if (!valid_status(timeline_get_by_md5(&snac1, v, &post))) | ||
| 2276 | continue; | ||
| 2277 | |||
| 2278 | xs *s = mastoapi_status(&snac1, post); | ||
| 2279 | |||
| 2280 | if (!xs_is_null(s)) | ||
| 2281 | stl = xs_list_append(stl, s); | ||
| 2282 | } | ||
| 2283 | } | ||
| 2087 | } | 2284 | } |
| 2088 | } | 2285 | } |
| 2089 | 2286 | ||
| @@ -2119,11 +2316,9 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2119 | if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) | 2316 | if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) |
| 2120 | return 0; | 2317 | return 0; |
| 2121 | 2318 | ||
| 2122 | srv_debug(1, xs_fmt("mastoapi_post_handler %s", q_path)); | ||
| 2123 | |||
| 2124 | int status = 404; | 2319 | int status = 404; |
| 2125 | xs *args = NULL; | 2320 | xs *args = NULL; |
| 2126 | char *i_ctype = xs_dict_get(req, "content-type"); | 2321 | const char *i_ctype = xs_dict_get(req, "content-type"); |
| 2127 | 2322 | ||
| 2128 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { | 2323 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { |
| 2129 | if (!xs_is_null(payload)) | 2324 | if (!xs_is_null(payload)) |
| @@ -2238,7 +2433,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2238 | } | 2433 | } |
| 2239 | 2434 | ||
| 2240 | xs_list *p = mi; | 2435 | xs_list *p = mi; |
| 2241 | xs_str *v; | 2436 | const xs_str *v; |
| 2242 | 2437 | ||
| 2243 | while (xs_list_iter(&p, &v)) { | 2438 | while (xs_list_iter(&p, &v)) { |
| 2244 | xs *l = xs_list_new(); | 2439 | xs *l = xs_list_new(); |
| @@ -2296,7 +2491,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2296 | mid = MID_TO_MD5(mid); | 2491 | mid = MID_TO_MD5(mid); |
| 2297 | 2492 | ||
| 2298 | if (valid_status(timeline_get_by_md5(&snac, mid, &msg))) { | 2493 | if (valid_status(timeline_get_by_md5(&snac, mid, &msg))) { |
| 2299 | char *id = xs_dict_get(msg, "id"); | 2494 | const char *id = xs_dict_get(msg, "id"); |
| 2300 | 2495 | ||
| 2301 | if (op == NULL) { | 2496 | if (op == NULL) { |
| 2302 | /* no operation (?) */ | 2497 | /* no operation (?) */ |
| @@ -2402,7 +2597,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2402 | if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/ | 2597 | if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/ |
| 2403 | /* I don't know what I'm doing */ | 2598 | /* I don't know what I'm doing */ |
| 2404 | if (logged_in) { | 2599 | if (logged_in) { |
| 2405 | char *v; | 2600 | const char *v; |
| 2406 | 2601 | ||
| 2407 | xs *wpush = xs_dict_new(); | 2602 | xs *wpush = xs_dict_new(); |
| 2408 | 2603 | ||
| @@ -2574,7 +2769,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2574 | const char *id = xs_dict_get(msg, "id"); | 2769 | const char *id = xs_dict_get(msg, "id"); |
| 2575 | const char *atto = get_atto(msg); | 2770 | const char *atto = get_atto(msg); |
| 2576 | 2771 | ||
| 2577 | xs_list *opts = xs_dict_get(msg, "oneOf"); | 2772 | const xs_list *opts = xs_dict_get(msg, "oneOf"); |
| 2578 | if (opts == NULL) | 2773 | if (opts == NULL) |
| 2579 | opts = xs_dict_get(msg, "anyOf"); | 2774 | opts = xs_dict_get(msg, "anyOf"); |
| 2580 | 2775 | ||
| @@ -2582,15 +2777,16 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2582 | } | 2777 | } |
| 2583 | else | 2778 | else |
| 2584 | if (strcmp(op, "votes") == 0) { | 2779 | if (strcmp(op, "votes") == 0) { |
| 2585 | xs_list *choices = xs_dict_get(args, "choices[]"); | 2780 | const xs_list *choices = xs_dict_get(args, "choices[]"); |
| 2586 | 2781 | ||
| 2587 | if (xs_is_null(choices)) | 2782 | if (xs_is_null(choices)) |
| 2588 | choices = xs_dict_get(args, "choices"); | 2783 | choices = xs_dict_get(args, "choices"); |
| 2589 | 2784 | ||
| 2590 | if (xs_type(choices) == XSTYPE_LIST) { | 2785 | if (xs_type(choices) == XSTYPE_LIST) { |
| 2591 | xs_str *v; | 2786 | const xs_str *v; |
| 2592 | 2787 | ||
| 2593 | while (xs_list_iter(&choices, &v)) { | 2788 | int c = 0; |
| 2789 | while (xs_list_next(choices, &v, &c)) { | ||
| 2594 | int io = atoi(v); | 2790 | int io = atoi(v); |
| 2595 | const xs_dict *o = xs_list_get(opts, io); | 2791 | const xs_dict *o = xs_list_get(opts, io); |
| 2596 | 2792 | ||
| @@ -2621,19 +2817,73 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 2621 | else | 2817 | else |
| 2622 | status = 401; | 2818 | status = 401; |
| 2623 | } | 2819 | } |
| 2820 | else | ||
| 2821 | if (strcmp(cmd, "/v1/lists") == 0) { | ||
| 2822 | if (logged_in) { | ||
| 2823 | const char *title = xs_dict_get(args, "title"); | ||
| 2824 | |||
| 2825 | if (xs_type(title) == XSTYPE_STRING) { | ||
| 2826 | /* add the list */ | ||
| 2827 | xs *out = xs_dict_new(); | ||
| 2828 | |||
| 2829 | if (xs_type(list_maint(&snac, title, 1)) == XSTYPE_TRUE) { | ||
| 2830 | out = xs_dict_append(out, "title", title); | ||
| 2831 | out = xs_dict_append(out, "replies_policy", xs_dict_get_def(args, "replies_policy", "list")); | ||
| 2832 | out = xs_dict_append(out, "exclusive", xs_stock(XSTYPE_FALSE)); | ||
| 2833 | |||
| 2834 | status = 200; | ||
| 2835 | } | ||
| 2836 | else { | ||
| 2837 | out = xs_dict_append(out, "error", "cannot create list"); | ||
| 2838 | status = 422; | ||
| 2839 | } | ||
| 2840 | |||
| 2841 | *body = xs_json_dumps(out, 4); | ||
| 2842 | *ctype = "application/json"; | ||
| 2843 | } | ||
| 2844 | else | ||
| 2845 | status = 422; | ||
| 2846 | } | ||
| 2847 | } | ||
| 2848 | if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/ | ||
| 2849 | if (logged_in) { | ||
| 2850 | xs *l = xs_split(cmd, "/"); | ||
| 2851 | const char *op = xs_list_get(l, -1); | ||
| 2852 | const char *id = xs_list_get(l, -2); | ||
| 2853 | |||
| 2854 | if (op && id && xs_is_hex(id)) { | ||
| 2855 | if (strcmp(op, "accounts") == 0) { | ||
| 2856 | const xs_list *accts = xs_dict_get(args, "account_ids[]"); | ||
| 2857 | int c = 0; | ||
| 2858 | const char *v; | ||
| 2859 | |||
| 2860 | while (xs_list_next(accts, &v, &c)) { | ||
| 2861 | list_content(&snac, id, v, 1); | ||
| 2862 | } | ||
| 2863 | |||
| 2864 | status = 200; | ||
| 2865 | } | ||
| 2866 | } | ||
| 2867 | } | ||
| 2868 | else | ||
| 2869 | status = 422; | ||
| 2870 | } | ||
| 2624 | 2871 | ||
| 2625 | /* user cleanup */ | 2872 | /* user cleanup */ |
| 2626 | if (logged_in) | 2873 | if (logged_in) |
| 2627 | user_free(&snac); | 2874 | user_free(&snac); |
| 2628 | 2875 | ||
| 2876 | srv_debug(1, xs_fmt("mastoapi_post_handler %s %d", q_path, status)); | ||
| 2877 | |||
| 2629 | return status; | 2878 | return status; |
| 2630 | } | 2879 | } |
| 2631 | 2880 | ||
| 2632 | 2881 | ||
| 2633 | int mastoapi_delete_handler(const xs_dict *req, const char *q_path, | 2882 | int mastoapi_delete_handler(const xs_dict *req, const char *q_path, |
| 2634 | char **body, int *b_size, char **ctype) { | 2883 | const char *payload, int p_size, |
| 2635 | 2884 | char **body, int *b_size, char **ctype) | |
| 2636 | (void)req; | 2885 | { |
| 2886 | (void)p_size; | ||
| 2637 | (void)body; | 2887 | (void)body; |
| 2638 | (void)b_size; | 2888 | (void)b_size; |
| 2639 | (void)ctype; | 2889 | (void)ctype; |
| @@ -2641,13 +2891,76 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, | |||
| 2641 | if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) | 2891 | if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) |
| 2642 | return 0; | 2892 | return 0; |
| 2643 | 2893 | ||
| 2644 | srv_debug(1, xs_fmt("mastoapi_delete_handler %s", q_path)); | 2894 | int status = 404; |
| 2895 | xs *args = NULL; | ||
| 2896 | const char *i_ctype = xs_dict_get(req, "content-type"); | ||
| 2897 | |||
| 2898 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { | ||
| 2899 | if (!xs_is_null(payload)) | ||
| 2900 | args = xs_json_loads(payload); | ||
| 2901 | } | ||
| 2902 | else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded")) | ||
| 2903 | { | ||
| 2904 | // Some apps send form data instead of json so we should cater for those | ||
| 2905 | if (!xs_is_null(payload)) { | ||
| 2906 | xs *upl = xs_url_dec(payload); | ||
| 2907 | args = xs_url_vars(upl); | ||
| 2908 | } | ||
| 2909 | } | ||
| 2910 | else | ||
| 2911 | args = xs_dup(xs_dict_get(req, "p_vars")); | ||
| 2912 | |||
| 2913 | if (args == NULL) | ||
| 2914 | return 400; | ||
| 2915 | |||
| 2916 | snac snac = {0}; | ||
| 2917 | int logged_in = process_auth_token(&snac, req); | ||
| 2918 | |||
| 2645 | xs *cmd = xs_replace_n(q_path, "/api", "", 1); | 2919 | xs *cmd = xs_replace_n(q_path, "/api", "", 1); |
| 2920 | |||
| 2646 | if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/ | 2921 | if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/ |
| 2647 | // pretend we deleted it, since it doesn't exist anyway | 2922 | // pretend we deleted it, since it doesn't exist anyway |
| 2648 | return 200; | 2923 | status = 200; |
| 2924 | } | ||
| 2925 | else | ||
| 2926 | if (xs_startswith(cmd, "/v1/lists/")) { | ||
| 2927 | if (logged_in) { | ||
| 2928 | xs *l = xs_split(cmd, "/"); | ||
| 2929 | const char *p = xs_list_get(l, -1); | ||
| 2930 | |||
| 2931 | if (p) { | ||
| 2932 | if (strcmp(p, "accounts") == 0) { | ||
| 2933 | /* delete account from list */ | ||
| 2934 | p = xs_list_get(l, -2); | ||
| 2935 | const xs_list *accts = xs_dict_get(args, "account_ids[]"); | ||
| 2936 | int c = 0; | ||
| 2937 | const char *v; | ||
| 2938 | |||
| 2939 | while (xs_list_next(accts, &v, &c)) { | ||
| 2940 | list_content(&snac, p, v, 2); | ||
| 2941 | } | ||
| 2942 | } | ||
| 2943 | else { | ||
| 2944 | /* delete list */ | ||
| 2945 | if (xs_is_hex(p)) { | ||
| 2946 | list_maint(&snac, p, 2); | ||
| 2947 | } | ||
| 2948 | } | ||
| 2949 | } | ||
| 2950 | |||
| 2951 | status = 200; | ||
| 2952 | } | ||
| 2953 | else | ||
| 2954 | status = 401; | ||
| 2649 | } | 2955 | } |
| 2650 | return 0; | 2956 | |
| 2957 | /* user cleanup */ | ||
| 2958 | if (logged_in) | ||
| 2959 | user_free(&snac); | ||
| 2960 | |||
| 2961 | srv_debug(1, xs_fmt("mastoapi_delete_handler %s %d", q_path, status)); | ||
| 2962 | |||
| 2963 | return status; | ||
| 2651 | } | 2964 | } |
| 2652 | 2965 | ||
| 2653 | 2966 | ||
| @@ -2663,7 +2976,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path, | |||
| 2663 | 2976 | ||
| 2664 | int status = 404; | 2977 | int status = 404; |
| 2665 | xs *args = NULL; | 2978 | xs *args = NULL; |
| 2666 | char *i_ctype = xs_dict_get(req, "content-type"); | 2979 | const char *i_ctype = xs_dict_get(req, "content-type"); |
| 2667 | 2980 | ||
| 2668 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { | 2981 | if (i_ctype && xs_startswith(i_ctype, "application/json")) { |
| 2669 | if (!xs_is_null(payload)) | 2982 | if (!xs_is_null(payload)) |
| @@ -2770,7 +3083,7 @@ void mastoapi_purge(void) | |||
| 2770 | xs *spec = xs_fmt("%s/app/" "*.json", srv_basedir); | 3083 | xs *spec = xs_fmt("%s/app/" "*.json", srv_basedir); |
| 2771 | xs *files = xs_glob(spec, 1, 0); | 3084 | xs *files = xs_glob(spec, 1, 0); |
| 2772 | xs_list *p = files; | 3085 | xs_list *p = files; |
| 2773 | xs_str *v; | 3086 | const xs_str *v; |
| 2774 | 3087 | ||
| 2775 | time_t mt = time(NULL) - 3600; | 3088 | time_t mt = time(NULL) - 3600; |
| 2776 | 3089 | ||
| @@ -1,7 +1,7 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #define VERSION "2.52-dev" | 4 | #define VERSION "2.53" |
| 5 | 5 | ||
| 6 | #define USER_AGENT "snac/" VERSION | 6 | #define USER_AGENT "snac/" VERSION |
| 7 | 7 | ||
| @@ -29,6 +29,8 @@ extern int dbglevel; | |||
| 29 | 29 | ||
| 30 | #define L(s) (s) | 30 | #define L(s) (s) |
| 31 | 31 | ||
| 32 | #define POSTLIKE_OBJECT_TYPE "Note|Question|Page|Article|Video|Event" | ||
| 33 | |||
| 32 | int mkdirx(const char *pathname); | 34 | int mkdirx(const char *pathname); |
| 33 | 35 | ||
| 34 | int valid_status(int status); | 36 | int valid_status(int status); |
| @@ -67,7 +69,7 @@ void snac_log(snac *user, xs_str *str); | |||
| 67 | #define snac_debug(user, level, str) do { if (dbglevel >= (level)) \ | 69 | #define snac_debug(user, level, str) do { if (dbglevel >= (level)) \ |
| 68 | { snac_log((user), (str)); } } while (0) | 70 | { snac_log((user), (str)); } } while (0) |
| 69 | 71 | ||
| 70 | int srv_open(char *basedir, int auto_upgrade); | 72 | int srv_open(const char *basedir, int auto_upgrade); |
| 71 | void srv_free(void); | 73 | void srv_free(void); |
| 72 | 74 | ||
| 73 | int user_open(snac *snac, const char *uid); | 75 | int user_open(snac *snac, const char *uid); |
| @@ -86,7 +88,7 @@ void srv_archive(const char *direction, const char *url, xs_dict *req, | |||
| 86 | const char *body, int b_size); | 88 | const char *body, int b_size); |
| 87 | void srv_archive_error(const char *prefix, const xs_str *err, | 89 | void srv_archive_error(const char *prefix, const xs_str *err, |
| 88 | const xs_dict *req, const xs_val *data); | 90 | const xs_dict *req, const xs_val *data); |
| 89 | void srv_archive_qitem(char *prefix, xs_dict *q_item); | 91 | void srv_archive_qitem(const char *prefix, xs_dict *q_item); |
| 90 | 92 | ||
| 91 | double mtime_nl(const char *fn, int *n_link); | 93 | double mtime_nl(const char *fn, int *n_link); |
| 92 | #define mtime(fn) mtime_nl(fn, NULL) | 94 | #define mtime(fn) mtime_nl(fn, NULL) |
| @@ -137,13 +139,13 @@ double timeline_mtime(snac *snac); | |||
| 137 | int timeline_touch(snac *snac); | 139 | int timeline_touch(snac *snac); |
| 138 | int timeline_here(snac *snac, const char *md5); | 140 | int timeline_here(snac *snac, const char *md5); |
| 139 | int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg); | 141 | int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg); |
| 140 | int timeline_del(snac *snac, char *id); | 142 | int timeline_del(snac *snac, const char *id); |
| 141 | xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show); | 143 | xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show); |
| 142 | xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show); | 144 | xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show); |
| 143 | int timeline_add(snac *snac, const char *id, const xs_dict *o_msg); | 145 | int timeline_add(snac *snac, const char *id, const xs_dict *o_msg); |
| 144 | int timeline_admire(snac *snac, const char *id, const char *admirer, int like); | 146 | int timeline_admire(snac *snac, const char *id, const char *admirer, int like); |
| 145 | 147 | ||
| 146 | xs_list *timeline_top_level(snac *snac, xs_list *list); | 148 | xs_list *timeline_top_level(snac *snac, const xs_list *list); |
| 147 | xs_list *local_list(snac *snac, int max); | 149 | xs_list *local_list(snac *snac, int max); |
| 148 | xs_list *timeline_instance_list(int skip, int show); | 150 | xs_list *timeline_instance_list(int skip, int show); |
| 149 | 151 | ||
| @@ -172,9 +174,14 @@ void hide(snac *snac, const char *id); | |||
| 172 | int is_hidden(snac *snac, const char *id); | 174 | int is_hidden(snac *snac, const char *id); |
| 173 | 175 | ||
| 174 | void tag_index(const char *id, const xs_dict *obj); | 176 | void tag_index(const char *id, const xs_dict *obj); |
| 175 | xs_list *tag_search(char *tag, int skip, int show); | 177 | xs_list *tag_search(const char *tag, int skip, int show); |
| 178 | |||
| 179 | xs_val *list_maint(snac *user, const char *list, int op); | ||
| 180 | xs_list *list_timeline(snac *user, const char *list, int skip, int show); | ||
| 181 | xs_val *list_content(snac *user, const char *list_id, const char *actor_md5, int op); | ||
| 182 | void list_distribute(snac *user, const char *who, const xs_dict *post); | ||
| 176 | 183 | ||
| 177 | int actor_add(const char *actor, xs_dict *msg); | 184 | int actor_add(const char *actor, const xs_dict *msg); |
| 178 | int actor_get(const char *actor, xs_dict **data); | 185 | int actor_get(const char *actor, xs_dict **data); |
| 179 | int actor_get_refresh(snac *user, const char *actor, xs_dict **data); | 186 | int actor_get_refresh(snac *user, const char *actor, xs_dict **data); |
| 180 | 187 | ||
| @@ -209,22 +216,27 @@ int is_instance_blocked(const char *instance); | |||
| 209 | int instance_block(const char *instance); | 216 | int instance_block(const char *instance); |
| 210 | int instance_unblock(const char *instance); | 217 | int instance_unblock(const char *instance); |
| 211 | 218 | ||
| 212 | int content_check(const char *file, const xs_dict *msg); | 219 | int content_match(const char *file, const xs_dict *msg); |
| 220 | xs_list *content_search(snac *user, const char *regex, | ||
| 221 | int priv, int skip, int show, int max_secs, int *timeout); | ||
| 213 | 222 | ||
| 214 | void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries); | 223 | void enqueue_input(snac *snac, 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); | 224 | void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries); |
| 216 | void enqueue_output_raw(const char *keyid, const char *seckey, | 225 | void enqueue_output_raw(const char *keyid, const char *seckey, |
| 217 | xs_dict *msg, xs_str *inbox, int retries, int p_status); | 226 | const xs_dict *msg, const xs_str *inbox, |
| 218 | void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries, int p_status); | 227 | int retries, int p_status); |
| 219 | void enqueue_output_by_actor(snac *snac, xs_dict *msg, const xs_str *actor, int retries); | 228 | void enqueue_output(snac *snac, const xs_dict *msg, |
| 220 | void enqueue_email(xs_str *msg, int retries); | 229 | const xs_str *inbox, int retries, int p_status); |
| 230 | void enqueue_output_by_actor(snac *snac, const xs_dict *msg, | ||
| 231 | const xs_str *actor, int retries); | ||
| 232 | void enqueue_email(const xs_str *msg, int retries); | ||
| 221 | void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id); | 233 | void enqueue_telegram(const xs_str *msg, const char *bot, const char *chat_id); |
| 222 | void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_token); | 234 | void enqueue_ntfy(const xs_str *msg, const char *ntfy_server, const char *ntfy_token); |
| 223 | void enqueue_message(snac *snac, const xs_dict *msg); | 235 | void enqueue_message(snac *snac, const xs_dict *msg); |
| 224 | void enqueue_close_question(snac *user, const char *id, int end_secs); | 236 | void enqueue_close_question(snac *user, const char *id, int end_secs); |
| 237 | void enqueue_object_request(snac *user, const char *id, int forward_secs); | ||
| 225 | void enqueue_verify_links(snac *user); | 238 | void enqueue_verify_links(snac *user); |
| 226 | void enqueue_actor_refresh(snac *user, const char *actor); | 239 | void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs); |
| 227 | void enqueue_request_replies(snac *user, const char *id); | ||
| 228 | int was_question_voted(snac *user, const char *id); | 240 | int was_question_voted(snac *user, const char *id); |
| 229 | 241 | ||
| 230 | xs_list *user_queue(snac *snac); | 242 | xs_list *user_queue(snac *snac); |
| @@ -237,16 +249,16 @@ void purge_all(void); | |||
| 237 | 249 | ||
| 238 | xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, | 250 | xs_dict *http_signed_request_raw(const char *keyid, const char *seckey, |
| 239 | const char *method, const char *url, | 251 | const char *method, const char *url, |
| 240 | xs_dict *headers, | 252 | const xs_dict *headers, |
| 241 | const char *body, int b_size, | 253 | const char *body, int b_size, |
| 242 | int *status, xs_str **payload, int *p_size, | 254 | int *status, xs_str **payload, int *p_size, |
| 243 | int timeout); | 255 | int timeout); |
| 244 | xs_dict *http_signed_request(snac *snac, const char *method, const char *url, | 256 | xs_dict *http_signed_request(snac *snac, const char *method, const char *url, |
| 245 | xs_dict *headers, | 257 | const xs_dict *headers, |
| 246 | const char *body, int b_size, | 258 | const char *body, int b_size, |
| 247 | int *status, xs_str **payload, int *p_size, | 259 | int *status, xs_str **payload, int *p_size, |
| 248 | int timeout); | 260 | int timeout); |
| 249 | int check_signature(xs_dict *req, xs_str **err); | 261 | int check_signature(const xs_dict *req, xs_str **err); |
| 250 | 262 | ||
| 251 | srv_state *srv_state_op(xs_str **fname, int op); | 263 | srv_state *srv_state_op(xs_str **fname, int op); |
| 252 | void httpd(void); | 264 | void httpd(void); |
| @@ -260,21 +272,21 @@ const char *default_avatar_base64(void); | |||
| 260 | 272 | ||
| 261 | xs_str *process_tags(snac *snac, const char *content, xs_list **tag); | 273 | xs_str *process_tags(snac *snac, const char *content, xs_list **tag); |
| 262 | 274 | ||
| 263 | char *get_atto(const xs_dict *msg); | 275 | const char *get_atto(const xs_dict *msg); |
| 264 | xs_list *get_attachments(const xs_dict *msg); | 276 | xs_list *get_attachments(const xs_dict *msg); |
| 265 | 277 | ||
| 266 | xs_dict *msg_admiration(snac *snac, char *object, char *type); | 278 | xs_dict *msg_admiration(snac *snac, const char *object, const char *type); |
| 267 | xs_dict *msg_repulsion(snac *user, char *id, char *type); | 279 | xs_dict *msg_repulsion(snac *user, const char *id, const char *type); |
| 268 | xs_dict *msg_create(snac *snac, const xs_dict *object); | 280 | xs_dict *msg_create(snac *snac, const xs_dict *object); |
| 269 | xs_dict *msg_follow(snac *snac, const char *actor); | 281 | xs_dict *msg_follow(snac *snac, const char *actor); |
| 270 | 282 | ||
| 271 | xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | 283 | xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, |
| 272 | xs_str *in_reply_to, xs_list *attach, int priv); | 284 | const xs_str *in_reply_to, const xs_list *attach, int priv); |
| 273 | 285 | ||
| 274 | xs_dict *msg_undo(snac *snac, char *object); | 286 | xs_dict *msg_undo(snac *snac, const xs_val *object); |
| 275 | xs_dict *msg_delete(snac *snac, char *id); | 287 | xs_dict *msg_delete(snac *snac, const char *id); |
| 276 | xs_dict *msg_actor(snac *snac); | 288 | xs_dict *msg_actor(snac *snac); |
| 277 | xs_dict *msg_update(snac *snac, xs_dict *object); | 289 | xs_dict *msg_update(snac *snac, const xs_dict *object); |
| 278 | xs_dict *msg_ping(snac *user, const char *rcpt); | 290 | xs_dict *msg_ping(snac *user, const char *rcpt); |
| 279 | xs_dict *msg_pong(snac *user, const char *rcpt, const char *object); | 291 | xs_dict *msg_pong(snac *user, const char *rcpt, const char *object); |
| 280 | xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | 292 | xs_dict *msg_question(snac *user, const char *content, xs_list *attach, |
| @@ -282,7 +294,6 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | |||
| 282 | 294 | ||
| 283 | int activitypub_request(snac *snac, const char *url, xs_dict **data); | 295 | int activitypub_request(snac *snac, const char *url, xs_dict **data); |
| 284 | int actor_request(snac *user, const char *actor, xs_dict **data); | 296 | int actor_request(snac *user, const char *actor, xs_dict **data); |
| 285 | void timeline_request_replies(snac *user, const char *id); | ||
| 286 | int send_to_inbox_raw(const char *keyid, const char *seckey, | 297 | int send_to_inbox_raw(const char *keyid, const char *seckey, |
| 287 | const xs_str *inbox, const xs_dict *msg, | 298 | const xs_str *inbox, const xs_dict *msg, |
| 288 | xs_val **payload, int *p_size, int timeout); | 299 | xs_val **payload, int *p_size, int timeout); |
| @@ -312,13 +323,14 @@ xs_str *encode_html(const char *str); | |||
| 312 | 323 | ||
| 313 | xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | 324 | xs_str *html_timeline(snac *user, const xs_list *list, int read_only, |
| 314 | int skip, int show, int show_more, | 325 | int skip, int show, int show_more, |
| 315 | char *tag, char *page, int utl); | 326 | char *title, char *page, int utl); |
| 316 | 327 | ||
| 317 | int html_get_handler(const xs_dict *req, const char *q_path, | 328 | int html_get_handler(const xs_dict *req, const char *q_path, |
| 318 | char **body, int *b_size, char **ctype, xs_str **etag); | 329 | char **body, int *b_size, char **ctype, xs_str **etag); |
| 319 | int html_post_handler(const xs_dict *req, const char *q_path, | 330 | int html_post_handler(const xs_dict *req, const char *q_path, |
| 320 | char *payload, int p_size, | 331 | char *payload, int p_size, |
| 321 | char **body, int *b_size, char **ctype); | 332 | char **body, int *b_size, char **ctype); |
| 333 | xs_str *timeline_to_rss(snac *user, const xs_list *timeline, char *title, char *link, char *desc); | ||
| 322 | 334 | ||
| 323 | int snac_init(const char *_basedir); | 335 | int snac_init(const char *_basedir); |
| 324 | int adduser(const char *uid); | 336 | int adduser(const char *uid); |
| @@ -337,11 +349,12 @@ int oauth_post_handler(const xs_dict *req, const char *q_path, | |||
| 337 | char **body, int *b_size, char **ctype); | 349 | char **body, int *b_size, char **ctype); |
| 338 | int mastoapi_get_handler(const xs_dict *req, const char *q_path, | 350 | int mastoapi_get_handler(const xs_dict *req, const char *q_path, |
| 339 | char **body, int *b_size, char **ctype); | 351 | char **body, int *b_size, char **ctype); |
| 340 | int mastoapi_delete_handler(const xs_dict *req, const char *q_path, | ||
| 341 | char **body, int *b_size, char **ctype); | ||
| 342 | int mastoapi_post_handler(const xs_dict *req, const char *q_path, | 352 | int mastoapi_post_handler(const xs_dict *req, const char *q_path, |
| 343 | const char *payload, int p_size, | 353 | const char *payload, int p_size, |
| 344 | char **body, int *b_size, char **ctype); | 354 | char **body, int *b_size, char **ctype); |
| 355 | int mastoapi_delete_handler(const xs_dict *req, const char *q_path, | ||
| 356 | const char *payload, int p_size, | ||
| 357 | char **body, int *b_size, char **ctype); | ||
| 345 | int mastoapi_put_handler(const xs_dict *req, const char *q_path, | 358 | int mastoapi_put_handler(const xs_dict *req, const char *q_path, |
| 346 | const char *payload, int p_size, | 359 | const char *payload, int p_size, |
| 347 | char **body, int *b_size, char **ctype); | 360 | char **body, int *b_size, char **ctype); |
| @@ -18,7 +18,7 @@ int snac_upgrade(xs_str **error) | |||
| 18 | double f = 0.0; | 18 | double f = 0.0; |
| 19 | 19 | ||
| 20 | for (;;) { | 20 | for (;;) { |
| 21 | char *layout = xs_dict_get(srv_config, "layout"); | 21 | const char *layout = xs_dict_get(srv_config, "layout"); |
| 22 | double nf; | 22 | double nf; |
| 23 | 23 | ||
| 24 | f = nf = xs_number_get(layout); | 24 | f = nf = xs_number_get(layout); |
| @@ -43,7 +43,8 @@ int snac_upgrade(xs_str **error) | |||
| 43 | else | 43 | else |
| 44 | if (f < 2.2) { | 44 | if (f < 2.2) { |
| 45 | xs *users = user_list(); | 45 | xs *users = user_list(); |
| 46 | char *p, *v; | 46 | char *p; |
| 47 | const char *v; | ||
| 47 | 48 | ||
| 48 | p = users; | 49 | p = users; |
| 49 | while (xs_list_iter(&p, &v)) { | 50 | while (xs_list_iter(&p, &v)) { |
| @@ -52,12 +53,13 @@ int snac_upgrade(xs_str **error) | |||
| 52 | if (user_open(&snac, v)) { | 53 | if (user_open(&snac, v)) { |
| 53 | xs *spec = xs_fmt("%s/actors/" "*.json", snac.basedir); | 54 | xs *spec = xs_fmt("%s/actors/" "*.json", snac.basedir); |
| 54 | xs *list = xs_glob(spec, 0, 0); | 55 | xs *list = xs_glob(spec, 0, 0); |
| 55 | char *g, *fn; | 56 | char *g; |
| 57 | const char *fn; | ||
| 56 | 58 | ||
| 57 | g = list; | 59 | g = list; |
| 58 | while (xs_list_iter(&g, &fn)) { | 60 | while (xs_list_iter(&g, &fn)) { |
| 59 | xs *l = xs_split(fn, "/"); | 61 | xs *l = xs_split(fn, "/"); |
| 60 | char *b = xs_list_get(l, -1); | 62 | const char *b = xs_list_get(l, -1); |
| 61 | xs *dir = xs_fmt("%s/object/%c%c", srv_basedir, b[0], b[1]); | 63 | xs *dir = xs_fmt("%s/object/%c%c", srv_basedir, b[0], b[1]); |
| 62 | xs *nfn = xs_fmt("%s/%s", dir, b); | 64 | xs *nfn = xs_fmt("%s/%s", dir, b); |
| 63 | 65 | ||
| @@ -77,14 +79,16 @@ int snac_upgrade(xs_str **error) | |||
| 77 | else | 79 | else |
| 78 | if (f < 2.3) { | 80 | if (f < 2.3) { |
| 79 | xs *users = user_list(); | 81 | xs *users = user_list(); |
| 80 | char *p, *v; | 82 | char *p; |
| 83 | const char *v; | ||
| 81 | 84 | ||
| 82 | p = users; | 85 | p = users; |
| 83 | while (xs_list_iter(&p, &v)) { | 86 | while (xs_list_iter(&p, &v)) { |
| 84 | snac snac; | 87 | snac snac; |
| 85 | 88 | ||
| 86 | if (user_open(&snac, v)) { | 89 | if (user_open(&snac, v)) { |
| 87 | char *p, *v; | 90 | char *p; |
| 91 | const char *v; | ||
| 88 | xs *dir = xs_fmt("%s/hidden", snac.basedir); | 92 | xs *dir = xs_fmt("%s/hidden", snac.basedir); |
| 89 | 93 | ||
| 90 | /* create the hidden directory */ | 94 | /* create the hidden directory */ |
| @@ -109,7 +113,8 @@ int snac_upgrade(xs_str **error) | |||
| 109 | else | 113 | else |
| 110 | if (f < 2.4) { | 114 | if (f < 2.4) { |
| 111 | xs *users = user_list(); | 115 | xs *users = user_list(); |
| 112 | char *p, *v; | 116 | char *p; |
| 117 | const char *v; | ||
| 113 | 118 | ||
| 114 | p = users; | 119 | p = users; |
| 115 | while (xs_list_iter(&p, &v)) { | 120 | while (xs_list_iter(&p, &v)) { |
| @@ -132,7 +137,8 @@ int snac_upgrade(xs_str **error) | |||
| 132 | if (f < 2.5) { | 137 | if (f < 2.5) { |
| 133 | /* upgrade followers */ | 138 | /* upgrade followers */ |
| 134 | xs *users = user_list(); | 139 | xs *users = user_list(); |
| 135 | char *p, *v; | 140 | char *p; |
| 141 | const char *v; | ||
| 136 | 142 | ||
| 137 | p = users; | 143 | p = users; |
| 138 | while (xs_list_iter(&p, &v)) { | 144 | while (xs_list_iter(&p, &v)) { |
| @@ -141,7 +147,8 @@ int snac_upgrade(xs_str **error) | |||
| 141 | if (user_open(&snac, v)) { | 147 | if (user_open(&snac, v)) { |
| 142 | xs *spec = xs_fmt("%s/followers/" "*.json", snac.basedir); | 148 | xs *spec = xs_fmt("%s/followers/" "*.json", snac.basedir); |
| 143 | xs *dir = xs_glob(spec, 0, 0); | 149 | xs *dir = xs_glob(spec, 0, 0); |
| 144 | char *p, *v; | 150 | char *p; |
| 151 | const char *v; | ||
| 145 | 152 | ||
| 146 | p = dir; | 153 | p = dir; |
| 147 | while (xs_list_iter(&p, &v)) { | 154 | while (xs_list_iter(&p, &v)) { |
| @@ -152,12 +159,12 @@ int snac_upgrade(xs_str **error) | |||
| 152 | xs *o = xs_json_loads(s); | 159 | xs *o = xs_json_loads(s); |
| 153 | fclose(f); | 160 | fclose(f); |
| 154 | 161 | ||
| 155 | char *type = xs_dict_get(o, "type"); | 162 | const char *type = xs_dict_get(o, "type"); |
| 156 | 163 | ||
| 157 | if (!xs_is_null(type) && strcmp(type, "Follow") == 0) { | 164 | if (!xs_is_null(type) && strcmp(type, "Follow") == 0) { |
| 158 | unlink(v); | 165 | unlink(v); |
| 159 | 166 | ||
| 160 | char *actor = xs_dict_get(o, "actor"); | 167 | const char *actor = xs_dict_get(o, "actor"); |
| 161 | 168 | ||
| 162 | if (!xs_is_null(actor)) | 169 | if (!xs_is_null(actor)) |
| 163 | follower_add(&snac, actor); | 170 | follower_add(&snac, actor); |
| @@ -175,7 +182,8 @@ int snac_upgrade(xs_str **error) | |||
| 175 | if (f < 2.6) { | 182 | if (f < 2.6) { |
| 176 | /* upgrade local/ to public/ */ | 183 | /* upgrade local/ to public/ */ |
| 177 | xs *users = user_list(); | 184 | xs *users = user_list(); |
| 178 | char *p, *v; | 185 | char *p; |
| 186 | const char *v; | ||
| 179 | 187 | ||
| 180 | p = users; | 188 | p = users; |
| 181 | while (xs_list_iter(&p, &v)) { | 189 | while (xs_list_iter(&p, &v)) { |
| @@ -184,7 +192,8 @@ int snac_upgrade(xs_str **error) | |||
| 184 | if (user_open(&snac, v)) { | 192 | if (user_open(&snac, v)) { |
| 185 | xs *spec = xs_fmt("%s/local/" "*.json", snac.basedir); | 193 | xs *spec = xs_fmt("%s/local/" "*.json", snac.basedir); |
| 186 | xs *dir = xs_glob(spec, 0, 0); | 194 | xs *dir = xs_glob(spec, 0, 0); |
| 187 | char *p, *v; | 195 | char *p; |
| 196 | const char *v; | ||
| 188 | 197 | ||
| 189 | p = dir; | 198 | p = dir; |
| 190 | while (xs_list_iter(&p, &v)) { | 199 | while (xs_list_iter(&p, &v)) { |
| @@ -198,22 +207,29 @@ int snac_upgrade(xs_str **error) | |||
| 198 | xs *meta = xs_dup(xs_dict_get(o, "_snac")); | 207 | xs *meta = xs_dup(xs_dict_get(o, "_snac")); |
| 199 | o = xs_dict_del(o, "_snac"); | 208 | o = xs_dict_del(o, "_snac"); |
| 200 | 209 | ||
| 201 | char *id = xs_dict_get(o, "id"); | 210 | const char *id = xs_dict_get(o, "id"); |
| 202 | 211 | ||
| 203 | /* store object */ | 212 | /* store object */ |
| 204 | object_add_ow(id, o); | 213 | object_add_ow(id, o); |
| 205 | 214 | ||
| 206 | /* if it's from us, add to public */ | 215 | /* if it's from us, add to public */ |
| 207 | if (xs_startswith(id, snac.actor)) { | 216 | if (xs_startswith(id, snac.actor)) { |
| 208 | char *p, *v; | 217 | const xs_list *p; |
| 218 | const char *v; | ||
| 219 | int c; | ||
| 209 | 220 | ||
| 210 | object_user_cache_add(&snac, id, "public"); | 221 | object_user_cache_add(&snac, id, "public"); |
| 211 | 222 | ||
| 212 | p = xs_dict_get(meta, "announced_by"); | 223 | p = xs_dict_get(meta, "announced_by"); |
| 213 | while (xs_list_iter(&p, &v)) | 224 | |
| 225 | c = 0; | ||
| 226 | while (xs_list_next(p, &v, &c)) | ||
| 214 | object_admire(id, v, 0); | 227 | object_admire(id, v, 0); |
| 228 | |||
| 215 | p = xs_dict_get(meta, "liked_by"); | 229 | p = xs_dict_get(meta, "liked_by"); |
| 216 | while (xs_list_iter(&p, &v)) | 230 | |
| 231 | c = 0; | ||
| 232 | while (xs_list_next(p, &v, &c)) | ||
| 217 | object_admire(id, v, 1); | 233 | object_admire(id, v, 1); |
| 218 | } | 234 | } |
| 219 | 235 | ||
| @@ -234,7 +250,8 @@ int snac_upgrade(xs_str **error) | |||
| 234 | if (f < 2.7) { | 250 | if (f < 2.7) { |
| 235 | /* upgrade timeline/ to private/ */ | 251 | /* upgrade timeline/ to private/ */ |
| 236 | xs *users = user_list(); | 252 | xs *users = user_list(); |
| 237 | char *p, *v; | 253 | char *p; |
| 254 | const char *v; | ||
| 238 | 255 | ||
| 239 | p = users; | 256 | p = users; |
| 240 | while (xs_list_iter(&p, &v)) { | 257 | while (xs_list_iter(&p, &v)) { |
| @@ -243,7 +260,8 @@ int snac_upgrade(xs_str **error) | |||
| 243 | if (user_open(&snac, v)) { | 260 | if (user_open(&snac, v)) { |
| 244 | xs *spec = xs_fmt("%s/timeline/" "*.json", snac.basedir); | 261 | xs *spec = xs_fmt("%s/timeline/" "*.json", snac.basedir); |
| 245 | xs *dir = xs_glob(spec, 0, 0); | 262 | xs *dir = xs_glob(spec, 0, 0); |
| 246 | char *p, *v; | 263 | char *p; |
| 264 | const char *v; | ||
| 247 | 265 | ||
| 248 | p = dir; | 266 | p = dir; |
| 249 | while (xs_list_iter(&p, &v)) { | 267 | while (xs_list_iter(&p, &v)) { |
| @@ -257,21 +275,28 @@ int snac_upgrade(xs_str **error) | |||
| 257 | xs *meta = xs_dup(xs_dict_get(o, "_snac")); | 275 | xs *meta = xs_dup(xs_dict_get(o, "_snac")); |
| 258 | o = xs_dict_del(o, "_snac"); | 276 | o = xs_dict_del(o, "_snac"); |
| 259 | 277 | ||
| 260 | char *id = xs_dict_get(o, "id"); | 278 | const char *id = xs_dict_get(o, "id"); |
| 261 | 279 | ||
| 262 | /* store object */ | 280 | /* store object */ |
| 263 | object_add_ow(id, o); | 281 | object_add_ow(id, o); |
| 264 | 282 | ||
| 265 | { | 283 | { |
| 266 | char *p, *v; | 284 | const xs_list *p; |
| 285 | const char *v; | ||
| 286 | int c = 0; | ||
| 267 | 287 | ||
| 268 | object_user_cache_add(&snac, id, "private"); | 288 | object_user_cache_add(&snac, id, "private"); |
| 269 | 289 | ||
| 270 | p = xs_dict_get(meta, "announced_by"); | 290 | p = xs_dict_get(meta, "announced_by"); |
| 271 | while (xs_list_iter(&p, &v)) | 291 | |
| 292 | c = 0; | ||
| 293 | while (xs_list_next(p, &v, &c)) | ||
| 272 | object_admire(id, v, 0); | 294 | object_admire(id, v, 0); |
| 295 | |||
| 273 | p = xs_dict_get(meta, "liked_by"); | 296 | p = xs_dict_get(meta, "liked_by"); |
| 274 | while (xs_list_iter(&p, &v)) | 297 | |
| 298 | c = 0; | ||
| 299 | while (xs_list_next(p, &v, &c)) | ||
| 275 | object_admire(id, v, 1); | 300 | object_admire(id, v, 1); |
| 276 | } | 301 | } |
| 277 | 302 | ||
| @@ -25,6 +25,8 @@ static const char *default_srv_config = "{" | |||
| 25 | "\"dbglevel\": 0," | 25 | "\"dbglevel\": 0," |
| 26 | "\"queue_retry_minutes\": 2," | 26 | "\"queue_retry_minutes\": 2," |
| 27 | "\"queue_retry_max\": 10," | 27 | "\"queue_retry_max\": 10," |
| 28 | "\"queue_timeout\": 6," | ||
| 29 | "\"queue_timeout_2\": 8," | ||
| 28 | "\"cssurls\": [\"\"]," | 30 | "\"cssurls\": [\"\"]," |
| 29 | "\"max_timeline_entries\": 50," | 31 | "\"max_timeline_entries\": 50," |
| 30 | "\"timeline_purge_days\": 120," | 32 | "\"timeline_purge_days\": 120," |
| @@ -34,6 +36,7 @@ static const char *default_srv_config = "{" | |||
| 34 | "\"admin_account\": \"\"," | 36 | "\"admin_account\": \"\"," |
| 35 | "\"title\": \"\"," | 37 | "\"title\": \"\"," |
| 36 | "\"short_description\": \"\"," | 38 | "\"short_description\": \"\"," |
| 39 | "\"protocol\": \"https\"," | ||
| 37 | "\"fastcgi\": false" | 40 | "\"fastcgi\": false" |
| 38 | "}"; | 41 | "}"; |
| 39 | 42 | ||
| @@ -46,7 +49,7 @@ static const char *default_css = | |||
| 46 | ".snac-top-user { text-align: center; padding-bottom: 2em }\n" | 49 | ".snac-top-user { text-align: center; padding-bottom: 2em }\n" |
| 47 | ".snac-top-user-name { font-size: 200% }\n" | 50 | ".snac-top-user-name { font-size: 200% }\n" |
| 48 | ".snac-top-user-id { font-size: 150% }\n" | 51 | ".snac-top-user-id { font-size: 150% }\n" |
| 49 | ".snac-avatar { float: left; height: 2.5em; padding: 0.25em }\n" | 52 | ".snac-avatar { float: left; height: 2.5em; width: 2.5em; padding: 0.25em }\n" |
| 50 | ".snac-author { font-size: 90%; text-decoration: none }\n" | 53 | ".snac-author { font-size: 90%; text-decoration: none }\n" |
| 51 | ".snac-author-tag { font-size: 80% }\n" | 54 | ".snac-author-tag { font-size: 80% }\n" |
| 52 | ".snac-pubdate { color: #a0a0a0; font-size: 90% }\n" | 55 | ".snac-pubdate { color: #a0a0a0; font-size: 90% }\n" |
| @@ -355,7 +358,7 @@ void rm_rf(const char *dir) | |||
| 355 | xs *d = xs_str_cat(xs_dup(dir), "/" "*"); | 358 | xs *d = xs_str_cat(xs_dup(dir), "/" "*"); |
| 356 | xs *l = xs_glob(d, 0, 0); | 359 | xs *l = xs_glob(d, 0, 0); |
| 357 | xs_list *p = l; | 360 | xs_list *p = l; |
| 358 | xs_str *v; | 361 | const xs_str *v; |
| 359 | 362 | ||
| 360 | if (dbglevel >= 1) | 363 | if (dbglevel >= 1) |
| 361 | printf("Deleting directory %s\n", dir); | 364 | printf("Deleting directory %s\n", dir); |
| @@ -390,7 +393,7 @@ int deluser(snac *user) | |||
| 390 | int ret = 0; | 393 | int ret = 0; |
| 391 | xs *fwers = following_list(user); | 394 | xs *fwers = following_list(user); |
| 392 | xs_list *p = fwers; | 395 | xs_list *p = fwers; |
| 393 | xs_str *v; | 396 | const xs_str *v; |
| 394 | 397 | ||
| 395 | while (xs_list_iter(&p, &v)) { | 398 | while (xs_list_iter(&p, &v)) { |
| 396 | xs *object = NULL; | 399 | xs *object = NULL; |
| @@ -415,8 +418,8 @@ int deluser(snac *user) | |||
| 415 | void verify_links(snac *user) | 418 | void verify_links(snac *user) |
| 416 | /* verifies a user's links */ | 419 | /* verifies a user's links */ |
| 417 | { | 420 | { |
| 418 | xs_dict *p = xs_dict_get(user->config, "metadata"); | 421 | const xs_dict *p = xs_dict_get(user->config, "metadata"); |
| 419 | char *k, *v; | 422 | const char *k, *v; |
| 420 | int changed = 0; | 423 | int changed = 0; |
| 421 | 424 | ||
| 422 | xs *headers = xs_dict_new(); | 425 | xs *headers = xs_dict_new(); |
| @@ -446,7 +449,7 @@ void verify_links(snac *user) | |||
| 446 | xs *ls = xs_regex_select(payload, "< *(a|link) +[^>]+>"); | 449 | xs *ls = xs_regex_select(payload, "< *(a|link) +[^>]+>"); |
| 447 | 450 | ||
| 448 | xs_list *lp = ls; | 451 | xs_list *lp = ls; |
| 449 | char *ll; | 452 | const char *ll; |
| 450 | int vfied = 0; | 453 | int vfied = 0; |
| 451 | 454 | ||
| 452 | while (!vfied && xs_list_iter(&lp, &ll)) { | 455 | while (!vfied && xs_list_iter(&lp, &ll)) { |
| @@ -460,7 +463,7 @@ void verify_links(snac *user) | |||
| 460 | xs *href = NULL; | 463 | xs *href = NULL; |
| 461 | int is_rel_me = 0; | 464 | int is_rel_me = 0; |
| 462 | xs_list *pr = r; | 465 | xs_list *pr = r; |
| 463 | char *ar; | 466 | const char *ar; |
| 464 | 467 | ||
| 465 | while (xs_list_iter(&pr, &ar)) { | 468 | while (xs_list_iter(&pr, &ar)) { |
| 466 | xs *nq = xs_dup(ar); | 469 | xs *nq = xs_dup(ar); |
diff --git a/webfinger.c b/webfinger.c index a883d7f..c79fd44 100644 --- a/webfinger.c +++ b/webfinger.c | |||
| @@ -16,12 +16,13 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us | |||
| 16 | int p_size = 0; | 16 | int p_size = 0; |
| 17 | xs *headers = xs_dict_new(); | 17 | xs *headers = xs_dict_new(); |
| 18 | xs *l = NULL; | 18 | xs *l = NULL; |
| 19 | xs_str *host = NULL; | 19 | const char *host = NULL; |
| 20 | xs *resource = NULL; | 20 | xs *resource = NULL; |
| 21 | 21 | ||
| 22 | if (xs_startswith(qs, "https:/" "/")) { | 22 | if (xs_startswith(qs, "https:/") || xs_startswith(qs, "http:/")) { |
| 23 | /* actor query: pick the host */ | 23 | /* actor query: pick the host */ |
| 24 | xs *s = xs_replace_n(qs, "https:/" "/", "", 1); | 24 | xs *s1 = xs_replace_n(qs, "http:/" "/", "", 1); |
| 25 | xs *s = xs_replace_n(s1, "https:/" "/", "", 1); | ||
| 25 | 26 | ||
| 26 | l = xs_split_n(s, "/", 1); | 27 | l = xs_split_n(s, "/", 1); |
| 27 | 28 | ||
| @@ -69,7 +70,9 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us | |||
| 69 | &payload, &p_size, &ctype); | 70 | &payload, &p_size, &ctype); |
| 70 | } | 71 | } |
| 71 | else { | 72 | else { |
| 72 | xs *url = xs_fmt("https:/" "/%s/.well-known/webfinger?resource=%s", host, resource); | 73 | const char *proto = xs_dict_get_def(srv_config, "protocol", "https"); |
| 74 | |||
| 75 | xs *url = xs_fmt("%s:/" "/%s/.well-known/webfinger?resource=%s", proto, host, resource); | ||
| 73 | 76 | ||
| 74 | if (snac == NULL) | 77 | if (snac == NULL) |
| 75 | xs_http_request("GET", url, headers, NULL, 0, &status, &payload, &p_size, 0); | 78 | xs_http_request("GET", url, headers, NULL, 0, &status, &payload, &p_size, 0); |
| @@ -84,22 +87,24 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us | |||
| 84 | 87 | ||
| 85 | if (obj) { | 88 | if (obj) { |
| 86 | if (user != NULL) { | 89 | if (user != NULL) { |
| 87 | char *subject = xs_dict_get(obj, "subject"); | 90 | const char *subject = xs_dict_get(obj, "subject"); |
| 88 | 91 | ||
| 89 | if (subject) | 92 | if (subject) |
| 90 | *user = xs_replace_n(subject, "acct:", "", 1); | 93 | *user = xs_replace_n(subject, "acct:", "", 1); |
| 91 | } | 94 | } |
| 92 | 95 | ||
| 93 | if (actor != NULL) { | 96 | if (actor != NULL) { |
| 94 | char *list = xs_dict_get(obj, "links"); | 97 | const xs_list *list = xs_dict_get(obj, "links"); |
| 95 | char *v; | 98 | int c = 0; |
| 99 | const char *v; | ||
| 96 | 100 | ||
| 97 | while (xs_list_iter(&list, &v)) { | 101 | while (xs_list_next(list, &v, &c)) { |
| 98 | if (xs_type(v) == XSTYPE_DICT) { | 102 | if (xs_type(v) == XSTYPE_DICT) { |
| 99 | char *type = xs_dict_get(v, "type"); | 103 | const char *type = xs_dict_get(v, "type"); |
| 100 | 104 | ||
| 101 | if (type && (strcmp(type, "application/activity+json") == 0 || | 105 | if (type && (strcmp(type, "application/activity+json") == 0 || |
| 102 | strcmp(type, "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") == 0)) { | 106 | strcmp(type, "application/ld+json; profile=\"https:/" |
| 107 | "/www.w3.org/ns/activitystreams\"") == 0)) { | ||
| 103 | *actor = xs_dup(xs_dict_get(v, "href")); | 108 | *actor = xs_dup(xs_dict_get(v, "href")); |
| 104 | break; | 109 | break; |
| 105 | } | 110 | } |
| @@ -130,8 +135,8 @@ int webfinger_get_handler(xs_dict *req, char *q_path, | |||
| 130 | if (strcmp(q_path, "/.well-known/webfinger") != 0) | 135 | if (strcmp(q_path, "/.well-known/webfinger") != 0) |
| 131 | return 0; | 136 | return 0; |
| 132 | 137 | ||
| 133 | char *q_vars = xs_dict_get(req, "q_vars"); | 138 | const char *q_vars = xs_dict_get(req, "q_vars"); |
| 134 | char *resource = xs_dict_get(q_vars, "resource"); | 139 | const char *resource = xs_dict_get(q_vars, "resource"); |
| 135 | 140 | ||
| 136 | if (resource == NULL) | 141 | if (resource == NULL) |
| 137 | return 400; | 142 | return 400; |
| @@ -139,10 +144,10 @@ int webfinger_get_handler(xs_dict *req, char *q_path, | |||
| 139 | snac snac; | 144 | snac snac; |
| 140 | int found = 0; | 145 | int found = 0; |
| 141 | 146 | ||
| 142 | if (xs_startswith(resource, "https:/" "/")) { | 147 | if (xs_startswith(resource, "https:/") || xs_startswith(resource, "http:/")) { |
| 143 | /* actor search: find a user with this actor */ | 148 | /* actor search: find a user with this actor */ |
| 144 | xs *l = xs_split(resource, "/"); | 149 | xs *l = xs_split(resource, "/"); |
| 145 | char *uid = xs_list_get(l, -1); | 150 | const char *uid = xs_list_get(l, -1); |
| 146 | 151 | ||
| 147 | if (uid) | 152 | if (uid) |
| 148 | found = user_open(&snac, uid); | 153 | found = user_open(&snac, uid); |
| @@ -160,8 +165,8 @@ int webfinger_get_handler(xs_dict *req, char *q_path, | |||
| 160 | l = xs_split_n(an, "@", 1); | 165 | l = xs_split_n(an, "@", 1); |
| 161 | 166 | ||
| 162 | if (xs_list_len(l) == 2) { | 167 | if (xs_list_len(l) == 2) { |
| 163 | char *uid = xs_list_get(l, 0); | 168 | const char *uid = xs_list_get(l, 0); |
| 164 | char *host = xs_list_get(l, 1); | 169 | const char *host = xs_list_get(l, 1); |
| 165 | 170 | ||
| 166 | if (strcmp(host, xs_dict_get(srv_config, "host")) == 0) | 171 | if (strcmp(host, xs_dict_get(srv_config, "host")) == 0) |
| 167 | found = user_open(&snac, uid); | 172 | found = user_open(&snac, uid); |
| @@ -185,13 +190,19 @@ int webfinger_get_handler(xs_dict *req, char *q_path, | |||
| 185 | 190 | ||
| 186 | links = xs_list_append(links, aaj); | 191 | links = xs_list_append(links, aaj); |
| 187 | 192 | ||
| 193 | /* duplicate with the ld+json type */ | ||
| 194 | aaj = xs_dict_set(aaj, "type", "application/ld+json; profile=\"https:/" | ||
| 195 | "/www.w3.org/ns/activitystreams\""); | ||
| 196 | |||
| 197 | links = xs_list_append(links, aaj); | ||
| 198 | |||
| 188 | prof = xs_dict_append(prof, "rel", "http://webfinger.net/rel/profile-page"); | 199 | prof = xs_dict_append(prof, "rel", "http://webfinger.net/rel/profile-page"); |
| 189 | prof = xs_dict_append(prof, "type", "text/html"); | 200 | prof = xs_dict_append(prof, "type", "text/html"); |
| 190 | prof = xs_dict_append(prof, "href", snac.actor); | 201 | prof = xs_dict_append(prof, "href", snac.actor); |
| 191 | 202 | ||
| 192 | links = xs_list_append(links, prof); | 203 | links = xs_list_append(links, prof); |
| 193 | 204 | ||
| 194 | char *avatar = xs_dict_get(snac.config, "avatar"); | 205 | const char *avatar = xs_dict_get(snac.config, "avatar"); |
| 195 | if (!xs_is_null(avatar) && *avatar) { | 206 | if (!xs_is_null(avatar) && *avatar) { |
| 196 | xs *d = xs_dict_new(); | 207 | xs *d = xs_dict_new(); |
| 197 | 208 | ||
| @@ -211,10 +222,12 @@ int webfinger_get_handler(xs_dict *req, char *q_path, | |||
| 211 | 222 | ||
| 212 | status = 200; | 223 | status = 200; |
| 213 | *body = j; | 224 | *body = j; |
| 214 | *ctype = "application/json"; | 225 | *ctype = "application/jrd+json"; |
| 215 | } | 226 | } |
| 216 | else | 227 | else |
| 217 | status = 404; | 228 | status = 404; |
| 218 | 229 | ||
| 230 | srv_debug(1, xs_fmt("webfinger_get_handler resource=%s %d", resource, status)); | ||
| 231 | |||
| 219 | return status; | 232 | return status; |
| 220 | } | 233 | } |
| @@ -21,8 +21,8 @@ typedef enum { | |||
| 21 | XSTYPE_FALSE = 0x15, /* Boolean */ | 21 | XSTYPE_FALSE = 0x15, /* Boolean */ |
| 22 | XSTYPE_LIST = 0x1d, /* Sequence of LITEMs up to EOM (with size) */ | 22 | XSTYPE_LIST = 0x1d, /* Sequence of LITEMs up to EOM (with size) */ |
| 23 | XSTYPE_LITEM = 0x1f, /* Element of a list (any type) */ | 23 | XSTYPE_LITEM = 0x1f, /* Element of a list (any type) */ |
| 24 | XSTYPE_DICT = 0x1c, /* Sequence of DITEMs up to EOM (with size) */ | 24 | XSTYPE_DICT = 0x1c, /* Sequence of KEYVALs up to EOM (with size) */ |
| 25 | XSTYPE_DITEM = 0x1e, /* Element of a dict (STRING key + any type) */ | 25 | XSTYPE_KEYVAL = 0x1e, /* key + value (STRING key + any type) */ |
| 26 | XSTYPE_EOM = 0x19, /* End of Multiple (LIST or DICT) */ | 26 | XSTYPE_EOM = 0x19, /* End of Multiple (LIST or DICT) */ |
| 27 | XSTYPE_DATA = 0x10 /* A block of anonymous data */ | 27 | XSTYPE_DATA = 0x10 /* A block of anonymous data */ |
| 28 | } xstype; | 28 | } xstype; |
| @@ -32,6 +32,7 @@ typedef enum { | |||
| 32 | typedef char xs_val; | 32 | typedef char xs_val; |
| 33 | typedef char xs_str; | 33 | typedef char xs_str; |
| 34 | typedef char xs_list; | 34 | typedef char xs_list; |
| 35 | typedef char xs_keyval; | ||
| 35 | typedef char xs_dict; | 36 | typedef char xs_dict; |
| 36 | typedef char xs_number; | 37 | typedef char xs_number; |
| 37 | typedef char xs_data; | 38 | typedef char xs_data; |
| @@ -45,6 +46,10 @@ typedef char xs_data; | |||
| 45 | /* not really all, just very much */ | 46 | /* not really all, just very much */ |
| 46 | #define XS_ALL 0xfffffff | 47 | #define XS_ALL 0xfffffff |
| 47 | 48 | ||
| 49 | #ifndef xs_countof | ||
| 50 | #define xs_countof(a) (sizeof((a)) / sizeof((*a))) | ||
| 51 | #endif | ||
| 52 | |||
| 48 | void *xs_free(void *ptr); | 53 | void *xs_free(void *ptr); |
| 49 | void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func); | 54 | 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__) | 55 | #define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__) |
| @@ -89,9 +94,10 @@ xs_list *xs_list_new(void); | |||
| 89 | xs_list *xs_list_append_m(xs_list *list, const char *mem, int dsz); | 94 | xs_list *xs_list_append_m(xs_list *list, const char *mem, int dsz); |
| 90 | xs_list *_xs_list_append(xs_list *list, const xs_val *vals[]); | 95 | xs_list *_xs_list_append(xs_list *list, const xs_val *vals[]); |
| 91 | #define xs_list_append(list, ...) _xs_list_append(list, (const xs_val *[]){ __VA_ARGS__, NULL }) | 96 | #define xs_list_append(list, ...) _xs_list_append(list, (const xs_val *[]){ __VA_ARGS__, NULL }) |
| 92 | int xs_list_iter(xs_list **list, xs_val **value); | 97 | int xs_list_iter(xs_list **list, const xs_val **value); |
| 98 | int xs_list_next(const xs_list *list, const xs_val **value, int *ctxt); | ||
| 93 | int xs_list_len(const xs_list *list); | 99 | int xs_list_len(const xs_list *list); |
| 94 | xs_val *xs_list_get(const xs_list *list, int num); | 100 | const xs_val *xs_list_get(const xs_list *list, int num); |
| 95 | xs_list *xs_list_del(xs_list *list, int num); | 101 | xs_list *xs_list_del(xs_list *list, int num); |
| 96 | xs_list *xs_list_insert(xs_list *list, int num, const xs_val *data); | 102 | xs_list *xs_list_insert(xs_list *list, int num, const xs_val *data); |
| 97 | xs_list *xs_list_set(xs_list *list, int num, const xs_val *data); | 103 | xs_list *xs_list_set(xs_list *list, int num, const xs_val *data); |
| @@ -104,17 +110,20 @@ xs_list *xs_split_n(const char *str, const char *sep, int times); | |||
| 104 | #define xs_split(str, sep) xs_split_n(str, sep, XS_ALL) | 110 | #define xs_split(str, sep) xs_split_n(str, sep, XS_ALL) |
| 105 | xs_list *xs_list_cat(xs_list *l1, const xs_list *l2); | 111 | xs_list *xs_list_cat(xs_list *l1, const xs_list *l2); |
| 106 | 112 | ||
| 113 | int xs_keyval_size(const xs_str *key, const xs_val *value); | ||
| 114 | xs_str *xs_keyval_key(const xs_keyval *keyval); | ||
| 115 | xs_val *xs_keyval_value(const xs_keyval *keyval); | ||
| 116 | xs_keyval *xs_keyval_make(xs_keyval *keyval, const xs_str *key, const xs_val *value); | ||
| 117 | |||
| 107 | xs_dict *xs_dict_new(void); | 118 | xs_dict *xs_dict_new(void); |
| 108 | xs_dict *xs_dict_append_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz); | 119 | xs_dict *xs_dict_append(xs_dict *dict, const xs_str *key, const xs_val *value); |
| 109 | #define xs_dict_append(dict, key, data) xs_dict_append_m(dict, key, data, xs_size(data)) | 120 | xs_dict *xs_dict_prepend(xs_dict *dict, const xs_str *key, const xs_val *value); |
| 110 | xs_dict *xs_dict_prepend_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz); | 121 | int xs_dict_next(const xs_dict *dict, const xs_str **key, const xs_val **value, int *ctxt); |
| 111 | #define xs_dict_prepend(dict, key, data) xs_dict_prepend_m(dict, key, data, xs_size(data)) | 122 | const xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def); |
| 112 | int xs_dict_iter(xs_dict **dict, xs_str **key, xs_val **value); | ||
| 113 | int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt); | ||
| 114 | xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def); | ||
| 115 | #define xs_dict_get(dict, key) xs_dict_get_def(dict, key, NULL) | 123 | #define xs_dict_get(dict, key) xs_dict_get_def(dict, key, NULL) |
| 116 | xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key); | 124 | xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key); |
| 117 | xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data); | 125 | xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data); |
| 126 | xs_dict *xs_dict_gc(xs_dict *dict); | ||
| 118 | 127 | ||
| 119 | xs_val *xs_val_new(xstype t); | 128 | xs_val *xs_val_new(xstype t); |
| 120 | xs_number *xs_number_new(double f); | 129 | xs_number *xs_number_new(double f); |
| @@ -242,7 +251,7 @@ xstype xs_type(const xs_val *data) | |||
| 242 | case XSTYPE_LIST: | 251 | case XSTYPE_LIST: |
| 243 | case XSTYPE_LITEM: | 252 | case XSTYPE_LITEM: |
| 244 | case XSTYPE_DICT: | 253 | case XSTYPE_DICT: |
| 245 | case XSTYPE_DITEM: | 254 | case XSTYPE_KEYVAL: |
| 246 | case XSTYPE_NUMBER: | 255 | case XSTYPE_NUMBER: |
| 247 | case XSTYPE_EOM: | 256 | case XSTYPE_EOM: |
| 248 | case XSTYPE_DATA: | 257 | case XSTYPE_DATA: |
| @@ -260,7 +269,7 @@ xstype xs_type(const xs_val *data) | |||
| 260 | void _xs_put_size(xs_val *ptr, int i) | 269 | void _xs_put_size(xs_val *ptr, int i) |
| 261 | /* must match _XS_TYPE_SIZE */ | 270 | /* must match _XS_TYPE_SIZE */ |
| 262 | { | 271 | { |
| 263 | memcpy(ptr, &i, sizeof(i)); | 272 | memcpy(ptr + 1, &i, sizeof(i)); |
| 264 | } | 273 | } |
| 265 | 274 | ||
| 266 | 275 | ||
| @@ -294,7 +303,7 @@ int xs_size(const xs_val *data) | |||
| 294 | 303 | ||
| 295 | break; | 304 | break; |
| 296 | 305 | ||
| 297 | case XSTYPE_DITEM: | 306 | case XSTYPE_KEYVAL: |
| 298 | /* calculate the size of the key and the value */ | 307 | /* calculate the size of the key and the value */ |
| 299 | p = data + 1; | 308 | p = data + 1; |
| 300 | p += xs_size(p); | 309 | p += xs_size(p); |
| @@ -378,7 +387,7 @@ xs_val *xs_expand(xs_val *data, int offset, int size) | |||
| 378 | if (xs_type(data) == XSTYPE_LIST || | 387 | if (xs_type(data) == XSTYPE_LIST || |
| 379 | xs_type(data) == XSTYPE_DICT || | 388 | xs_type(data) == XSTYPE_DICT || |
| 380 | xs_type(data) == XSTYPE_DATA) | 389 | xs_type(data) == XSTYPE_DATA) |
| 381 | _xs_put_size(data + 1, sz); | 390 | _xs_put_size(data, sz); |
| 382 | 391 | ||
| 383 | return data; | 392 | return data; |
| 384 | } | 393 | } |
| @@ -403,7 +412,7 @@ xs_val *xs_collapse(xs_val *data, int offset, int size) | |||
| 403 | if (xs_type(data) == XSTYPE_LIST || | 412 | if (xs_type(data) == XSTYPE_LIST || |
| 404 | xs_type(data) == XSTYPE_DICT || | 413 | xs_type(data) == XSTYPE_DICT || |
| 405 | xs_type(data) == XSTYPE_DATA) | 414 | xs_type(data) == XSTYPE_DATA) |
| 406 | _xs_put_size(data + 1, sz); | 415 | _xs_put_size(data, sz); |
| 407 | 416 | ||
| 408 | return xs_realloc(data, _xs_blk_size(sz)); | 417 | return xs_realloc(data, _xs_blk_size(sz)); |
| 409 | } | 418 | } |
| @@ -664,10 +673,10 @@ xs_list *xs_list_new(void) | |||
| 664 | { | 673 | { |
| 665 | int sz = 1 + _XS_TYPE_SIZE + 1; | 674 | int sz = 1 + _XS_TYPE_SIZE + 1; |
| 666 | xs_list *l = xs_realloc(NULL, sz); | 675 | xs_list *l = xs_realloc(NULL, sz); |
| 667 | memset(l, '\0', sz); | 676 | memset(l, XSTYPE_EOM, sz); |
| 668 | 677 | ||
| 669 | l[0] = XSTYPE_LIST; | 678 | l[0] = XSTYPE_LIST; |
| 670 | _xs_put_size(&l[1], sz); | 679 | _xs_put_size(l, sz); |
| 671 | 680 | ||
| 672 | return l; | 681 | return l; |
| 673 | } | 682 | } |
| @@ -717,7 +726,7 @@ xs_list *_xs_list_append(xs_list *list, const xs_val *vals[]) | |||
| 717 | } | 726 | } |
| 718 | 727 | ||
| 719 | 728 | ||
| 720 | int xs_list_iter(xs_list **list, xs_val **value) | 729 | int xs_list_iter(xs_list **list, const xs_val **value) |
| 721 | /* iterates a list value */ | 730 | /* iterates a list value */ |
| 722 | { | 731 | { |
| 723 | int goon = 1; | 732 | int goon = 1; |
| @@ -748,23 +757,58 @@ int xs_list_iter(xs_list **list, xs_val **value) | |||
| 748 | } | 757 | } |
| 749 | 758 | ||
| 750 | 759 | ||
| 760 | int xs_list_next(const xs_list *list, const xs_val **value, int *ctxt) | ||
| 761 | /* iterates a list, with context */ | ||
| 762 | { | ||
| 763 | if (xs_type(list) != XSTYPE_LIST) | ||
| 764 | return 0; | ||
| 765 | |||
| 766 | int goon = 1; | ||
| 767 | |||
| 768 | const char *p = list; | ||
| 769 | |||
| 770 | /* skip the start of the list */ | ||
| 771 | if (*ctxt == 0) | ||
| 772 | *ctxt = 1 + _XS_TYPE_SIZE; | ||
| 773 | |||
| 774 | p += *ctxt; | ||
| 775 | |||
| 776 | /* an element? */ | ||
| 777 | if (xs_type(p) == XSTYPE_LITEM) { | ||
| 778 | p++; | ||
| 779 | |||
| 780 | *value = p; | ||
| 781 | |||
| 782 | p += xs_size(*value); | ||
| 783 | } | ||
| 784 | else { | ||
| 785 | /* end of list */ | ||
| 786 | goon = 0; | ||
| 787 | } | ||
| 788 | |||
| 789 | /* update the context */ | ||
| 790 | *ctxt = p - list; | ||
| 791 | |||
| 792 | return goon; | ||
| 793 | } | ||
| 794 | |||
| 795 | |||
| 751 | int xs_list_len(const xs_list *list) | 796 | int xs_list_len(const xs_list *list) |
| 752 | /* returns the number of elements in the list */ | 797 | /* returns the number of elements in the list */ |
| 753 | { | 798 | { |
| 754 | XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST); | 799 | XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST); |
| 755 | 800 | ||
| 756 | int c = 0; | 801 | int c = 0, ct = 0; |
| 757 | xs_list *p = (xs_list *)list; | 802 | const xs_val *v; |
| 758 | xs_val *v; | ||
| 759 | 803 | ||
| 760 | while (xs_list_iter(&p, &v)) | 804 | while (xs_list_next(list, &v, &ct)) |
| 761 | c++; | 805 | c++; |
| 762 | 806 | ||
| 763 | return c; | 807 | return c; |
| 764 | } | 808 | } |
| 765 | 809 | ||
| 766 | 810 | ||
| 767 | xs_val *xs_list_get(const xs_list *list, int num) | 811 | const xs_val *xs_list_get(const xs_list *list, int num) |
| 768 | /* returns the element #num */ | 812 | /* returns the element #num */ |
| 769 | { | 813 | { |
| 770 | XS_ASSERT_TYPE(list, XSTYPE_LIST); | 814 | XS_ASSERT_TYPE(list, XSTYPE_LIST); |
| @@ -772,11 +816,10 @@ xs_val *xs_list_get(const xs_list *list, int num) | |||
| 772 | if (num < 0) | 816 | if (num < 0) |
| 773 | num = xs_list_len(list) + num; | 817 | num = xs_list_len(list) + num; |
| 774 | 818 | ||
| 775 | int c = 0; | 819 | int c = 0, ct = 0; |
| 776 | xs_list *p = (xs_list *)list; | 820 | const xs_val *v; |
| 777 | xs_val *v; | ||
| 778 | 821 | ||
| 779 | while (xs_list_iter(&p, &v)) { | 822 | while (xs_list_next(list, &v, &ct)) { |
| 780 | if (c == num) | 823 | if (c == num) |
| 781 | return v; | 824 | return v; |
| 782 | 825 | ||
| @@ -792,7 +835,7 @@ xs_list *xs_list_del(xs_list *list, int num) | |||
| 792 | { | 835 | { |
| 793 | XS_ASSERT_TYPE(list, XSTYPE_LIST); | 836 | XS_ASSERT_TYPE(list, XSTYPE_LIST); |
| 794 | 837 | ||
| 795 | xs_val *v; | 838 | const xs_val *v; |
| 796 | 839 | ||
| 797 | if ((v = xs_list_get(list, num)) != NULL) | 840 | if ((v = xs_list_get(list, num)) != NULL) |
| 798 | list = xs_collapse(list, v - 1 - list, xs_size(v - 1)); | 841 | list = xs_collapse(list, v - 1 - list, xs_size(v - 1)); |
| @@ -806,7 +849,7 @@ xs_list *xs_list_insert(xs_list *list, int num, const xs_val *data) | |||
| 806 | { | 849 | { |
| 807 | XS_ASSERT_TYPE(list, XSTYPE_LIST); | 850 | XS_ASSERT_TYPE(list, XSTYPE_LIST); |
| 808 | 851 | ||
| 809 | xs_val *v; | 852 | const xs_val *v; |
| 810 | int offset; | 853 | int offset; |
| 811 | 854 | ||
| 812 | if ((v = xs_list_get(list, num)) != NULL) | 855 | if ((v = xs_list_get(list, num)) != NULL) |
| @@ -835,16 +878,16 @@ xs_list *xs_list_dequeue(xs_list *list, xs_val **data, int last) | |||
| 835 | { | 878 | { |
| 836 | XS_ASSERT_TYPE(list, XSTYPE_LIST); | 879 | XS_ASSERT_TYPE(list, XSTYPE_LIST); |
| 837 | 880 | ||
| 838 | xs_list *p = list; | 881 | int ct = 0; |
| 839 | xs_val *v = NULL; | 882 | const xs_val *v = NULL; |
| 840 | 883 | ||
| 841 | if (!last) { | 884 | if (!last) { |
| 842 | /* get the first */ | 885 | /* get the first */ |
| 843 | xs_list_iter(&p, &v); | 886 | xs_list_next(list, &v, &ct); |
| 844 | } | 887 | } |
| 845 | else { | 888 | else { |
| 846 | /* iterate to the end */ | 889 | /* iterate to the end */ |
| 847 | while (xs_list_iter(&p, &v)); | 890 | while (xs_list_next(list, &v, &ct)); |
| 848 | } | 891 | } |
| 849 | 892 | ||
| 850 | if (v != NULL) { | 893 | if (v != NULL) { |
| @@ -864,11 +907,11 @@ int xs_list_in(const xs_list *list, const xs_val *val) | |||
| 864 | XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST); | 907 | XS_ASSERT_TYPE_NULL(list, XSTYPE_LIST); |
| 865 | 908 | ||
| 866 | int n = 0; | 909 | int n = 0; |
| 867 | xs_list *p = (xs_list *)list; | 910 | int ct = 0; |
| 868 | xs_val *v; | 911 | const xs_val *v; |
| 869 | int sz = xs_size(val); | 912 | int sz = xs_size(val); |
| 870 | 913 | ||
| 871 | while (xs_list_iter(&p, &v)) { | 914 | while (xs_list_next(list, &v, &ct)) { |
| 872 | if (sz == xs_size(v) && memcmp(val, v, sz) == 0) | 915 | if (sz == xs_size(v) && memcmp(val, v, sz) == 0) |
| 873 | return n; | 916 | return n; |
| 874 | 917 | ||
| @@ -885,13 +928,13 @@ xs_str *xs_join(const xs_list *list, const char *sep) | |||
| 885 | XS_ASSERT_TYPE(list, XSTYPE_LIST); | 928 | XS_ASSERT_TYPE(list, XSTYPE_LIST); |
| 886 | 929 | ||
| 887 | xs_str *s = NULL; | 930 | xs_str *s = NULL; |
| 888 | xs_list *p = (xs_list *)list; | 931 | const xs_val *v; |
| 889 | xs_val *v; | ||
| 890 | int c = 0; | 932 | int c = 0; |
| 933 | int ct = 0; | ||
| 891 | int offset = 0; | 934 | int offset = 0; |
| 892 | int ssz = strlen(sep); | 935 | int ssz = strlen(sep); |
| 893 | 936 | ||
| 894 | while (xs_list_iter(&p, &v)) { | 937 | while (xs_list_next(list, &v, &ct)) { |
| 895 | /* refuse to join non-string values */ | 938 | /* refuse to join non-string values */ |
| 896 | if (xs_type(v) == XSTYPE_STRING) { | 939 | if (xs_type(v) == XSTYPE_STRING) { |
| 897 | int sz; | 940 | int sz; |
| @@ -961,6 +1004,40 @@ xs_list *xs_list_cat(xs_list *l1, const xs_list *l2) | |||
| 961 | } | 1004 | } |
| 962 | 1005 | ||
| 963 | 1006 | ||
| 1007 | /** keyvals **/ | ||
| 1008 | |||
| 1009 | int xs_keyval_size(const xs_str *key, const xs_val *value) | ||
| 1010 | /* returns the needed size for a keyval */ | ||
| 1011 | { | ||
| 1012 | return 1 + xs_size(key) + xs_size(value); | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | |||
| 1016 | xs_str *xs_keyval_key(const xs_keyval *keyval) | ||
| 1017 | /* returns a pointer to the key of the keyval */ | ||
| 1018 | { | ||
| 1019 | return (xs_str *)&keyval[1]; | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | |||
| 1023 | xs_val *xs_keyval_value(const xs_keyval *keyval) | ||
| 1024 | /* returns a pointer to the value of the keyval */ | ||
| 1025 | { | ||
| 1026 | return (xs_val *)&keyval[1 + xs_size(xs_keyval_key(keyval))]; | ||
| 1027 | } | ||
| 1028 | |||
| 1029 | |||
| 1030 | xs_keyval *xs_keyval_make(xs_keyval *keyval, const xs_str *key, const xs_val *value) | ||
| 1031 | /* builds a keyval into mem (should have enough size) */ | ||
| 1032 | { | ||
| 1033 | keyval[0] = XSTYPE_KEYVAL; | ||
| 1034 | memcpy(xs_keyval_key(keyval), key, xs_size(key)); | ||
| 1035 | memcpy(xs_keyval_value(keyval), value, xs_size(value)); | ||
| 1036 | |||
| 1037 | return keyval; | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | |||
| 964 | /** dicts **/ | 1041 | /** dicts **/ |
| 965 | 1042 | ||
| 966 | xs_dict *xs_dict_new(void) | 1043 | xs_dict *xs_dict_new(void) |
| @@ -968,87 +1045,47 @@ xs_dict *xs_dict_new(void) | |||
| 968 | { | 1045 | { |
| 969 | int sz = 1 + _XS_TYPE_SIZE + 1; | 1046 | int sz = 1 + _XS_TYPE_SIZE + 1; |
| 970 | xs_dict *d = xs_realloc(NULL, sz); | 1047 | xs_dict *d = xs_realloc(NULL, sz); |
| 971 | memset(d, '\0', sz); | 1048 | memset(d, XSTYPE_EOM, sz); |
| 972 | 1049 | ||
| 973 | d[0] = XSTYPE_DICT; | 1050 | d[0] = XSTYPE_DICT; |
| 974 | _xs_put_size(&d[1], sz); | 1051 | _xs_put_size(d, sz); |
| 975 | 1052 | ||
| 976 | return d; | 1053 | return d; |
| 977 | } | 1054 | } |
| 978 | 1055 | ||
| 979 | 1056 | ||
| 980 | xs_dict *_xs_dict_write_ditem(xs_dict *dict, int offset, const xs_str *key, | 1057 | xs_dict *_xs_dict_write_keyval(xs_dict *dict, int offset, const xs_str *key, const xs_val *value) |
| 981 | const xs_val *data, int dsz) | 1058 | /* adds a new keyval to the dict */ |
| 982 | /* inserts a memory block into the dict */ | ||
| 983 | { | 1059 | { |
| 984 | XS_ASSERT_TYPE(dict, XSTYPE_DICT); | 1060 | XS_ASSERT_TYPE(dict, XSTYPE_DICT); |
| 985 | XS_ASSERT_TYPE(key, XSTYPE_STRING); | 1061 | XS_ASSERT_TYPE(key, XSTYPE_STRING); |
| 986 | 1062 | ||
| 987 | if (data == NULL) { | 1063 | if (value == NULL) |
| 988 | data = xs_stock(XSTYPE_NULL); | 1064 | value = xs_stock(XSTYPE_NULL); |
| 989 | dsz = xs_size(data); | ||
| 990 | } | ||
| 991 | |||
| 992 | int ksz = xs_size(key); | ||
| 993 | 1065 | ||
| 994 | dict = xs_expand(dict, offset, 1 + ksz + dsz); | 1066 | dict = xs_expand(dict, offset, xs_keyval_size(key, value)); |
| 995 | 1067 | ||
| 996 | dict[offset] = XSTYPE_DITEM; | 1068 | xs_keyval_make(&dict[offset], key, value); |
| 997 | memcpy(&dict[offset + 1], key, ksz); | ||
| 998 | memcpy(&dict[offset + 1 + ksz], data, dsz); | ||
| 999 | 1069 | ||
| 1000 | return dict; | 1070 | return dict; |
| 1001 | } | 1071 | } |
| 1002 | 1072 | ||
| 1003 | 1073 | ||
| 1004 | xs_dict *xs_dict_append_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz) | 1074 | xs_dict *xs_dict_append(xs_dict *dict, const xs_str *key, const xs_val *value) |
| 1005 | /* appends a memory block to the dict */ | 1075 | /* appends a memory block to the dict */ |
| 1006 | { | 1076 | { |
| 1007 | return _xs_dict_write_ditem(dict, xs_size(dict) - 1, key, mem, dsz); | 1077 | return _xs_dict_write_keyval(dict, xs_size(dict) - 1, key, value); |
| 1008 | } | 1078 | } |
| 1009 | 1079 | ||
| 1010 | 1080 | ||
| 1011 | xs_dict *xs_dict_prepend_m(xs_dict *dict, const xs_str *key, const xs_val *mem, int dsz) | 1081 | xs_dict *xs_dict_prepend(xs_dict *dict, const xs_str *key, const xs_val *value) |
| 1012 | /* prepends a memory block to the dict */ | 1082 | /* prepends a memory block to the dict */ |
| 1013 | { | 1083 | { |
| 1014 | return _xs_dict_write_ditem(dict, 4, key, mem, dsz); | 1084 | return _xs_dict_write_keyval(dict, 1 + _XS_TYPE_SIZE, key, value); |
| 1015 | } | ||
| 1016 | |||
| 1017 | |||
| 1018 | int xs_dict_iter(xs_dict **dict, xs_str **key, xs_val **value) | ||
| 1019 | /* iterates a dict value */ | ||
| 1020 | { | ||
| 1021 | int goon = 1; | ||
| 1022 | |||
| 1023 | xs_val *p = *dict; | ||
| 1024 | |||
| 1025 | /* skip the start of the list */ | ||
| 1026 | if (xs_type(p) == XSTYPE_DICT) | ||
| 1027 | p += 1 + _XS_TYPE_SIZE; | ||
| 1028 | |||
| 1029 | /* an element? */ | ||
| 1030 | if (xs_type(p) == XSTYPE_DITEM) { | ||
| 1031 | p++; | ||
| 1032 | |||
| 1033 | *key = p; | ||
| 1034 | p += xs_size(*key); | ||
| 1035 | |||
| 1036 | *value = p; | ||
| 1037 | p += xs_size(*value); | ||
| 1038 | } | ||
| 1039 | else { | ||
| 1040 | /* end of list */ | ||
| 1041 | goon = 0; | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | /* store back the pointer */ | ||
| 1045 | *dict = p; | ||
| 1046 | |||
| 1047 | return goon; | ||
| 1048 | } | 1085 | } |
| 1049 | 1086 | ||
| 1050 | 1087 | ||
| 1051 | int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt) | 1088 | int xs_dict_next(const xs_dict *dict, const xs_str **key, const xs_val **value, int *ctxt) |
| 1052 | /* iterates a dict, with context */ | 1089 | /* iterates a dict, with context */ |
| 1053 | { | 1090 | { |
| 1054 | if (xs_type(dict) != XSTYPE_DICT) | 1091 | if (xs_type(dict) != XSTYPE_DICT) |
| @@ -1065,7 +1102,7 @@ int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt) | |||
| 1065 | p += *ctxt; | 1102 | p += *ctxt; |
| 1066 | 1103 | ||
| 1067 | /* an element? */ | 1104 | /* an element? */ |
| 1068 | if (xs_type(p) == XSTYPE_DITEM) { | 1105 | if (xs_type(p) == XSTYPE_KEYVAL) { |
| 1069 | p++; | 1106 | p++; |
| 1070 | 1107 | ||
| 1071 | *key = p; | 1108 | *key = p; |
| @@ -1086,14 +1123,14 @@ int xs_dict_next(const xs_dict *dict, xs_str **key, xs_val **value, int *ctxt) | |||
| 1086 | } | 1123 | } |
| 1087 | 1124 | ||
| 1088 | 1125 | ||
| 1089 | xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def) | 1126 | const xs_val *xs_dict_get_def(const xs_dict *dict, const xs_str *key, const xs_val *def) |
| 1090 | /* returns the value directed by key, or the default value */ | 1127 | /* returns the value directed by key, or the default value */ |
| 1091 | { | 1128 | { |
| 1092 | XS_ASSERT_TYPE(dict, XSTYPE_DICT); | 1129 | XS_ASSERT_TYPE(dict, XSTYPE_DICT); |
| 1093 | XS_ASSERT_TYPE(key, XSTYPE_STRING); | 1130 | XS_ASSERT_TYPE(key, XSTYPE_STRING); |
| 1094 | 1131 | ||
| 1095 | xs_str *k; | 1132 | const xs_str *k; |
| 1096 | xs_val *v; | 1133 | const xs_val *v; |
| 1097 | int c = 0; | 1134 | int c = 0; |
| 1098 | 1135 | ||
| 1099 | while (xs_dict_next(dict, &k, &v, &c)) { | 1136 | while (xs_dict_next(dict, &k, &v, &c)) { |
| @@ -1111,14 +1148,14 @@ xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key) | |||
| 1111 | XS_ASSERT_TYPE(dict, XSTYPE_DICT); | 1148 | XS_ASSERT_TYPE(dict, XSTYPE_DICT); |
| 1112 | XS_ASSERT_TYPE(key, XSTYPE_STRING); | 1149 | XS_ASSERT_TYPE(key, XSTYPE_STRING); |
| 1113 | 1150 | ||
| 1114 | xs_str *k; | 1151 | const xs_str *k; |
| 1115 | xs_val *v; | 1152 | const xs_val *v; |
| 1116 | int c = 0; | 1153 | int c = 0; |
| 1117 | 1154 | ||
| 1118 | while (xs_dict_next(dict, &k, &v, &c)) { | 1155 | while (xs_dict_next(dict, &k, &v, &c)) { |
| 1119 | if (strcmp(k, key) == 0) { | 1156 | if (strcmp(k, key) == 0) { |
| 1120 | /* the address of the item is just behind the key */ | 1157 | /* the address of the item is just behind the key */ |
| 1121 | char *i = k - 1; | 1158 | char *i = (char *)k - 1; |
| 1122 | 1159 | ||
| 1123 | dict = xs_collapse(dict, i - dict, xs_size(i)); | 1160 | dict = xs_collapse(dict, i - dict, xs_size(i)); |
| 1124 | break; | 1161 | break; |
| @@ -1145,6 +1182,14 @@ xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data) | |||
| 1145 | } | 1182 | } |
| 1146 | 1183 | ||
| 1147 | 1184 | ||
| 1185 | xs_dict *xs_dict_gc(xs_dict *dict) | ||
| 1186 | /* collects garbage (leaked values) inside a dict */ | ||
| 1187 | { | ||
| 1188 | /* this kind of dicts does not get garbage */ | ||
| 1189 | return dict; | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | |||
| 1148 | /** other values **/ | 1193 | /** other values **/ |
| 1149 | 1194 | ||
| 1150 | xs_val *xs_val_new(xstype t) | 1195 | xs_val *xs_val_new(xstype t) |
| @@ -1195,8 +1240,11 @@ double xs_number_get(const xs_number *v) | |||
| 1195 | { | 1240 | { |
| 1196 | double f = 0.0; | 1241 | double f = 0.0; |
| 1197 | 1242 | ||
| 1198 | if (v != NULL && v[0] == XSTYPE_NUMBER) | 1243 | if (xs_type(v) == XSTYPE_NUMBER) |
| 1199 | f = atof(&v[1]); | 1244 | f = atof(&v[1]); |
| 1245 | else | ||
| 1246 | if (xs_type(v) == XSTYPE_STRING) | ||
| 1247 | f = atof(v); | ||
| 1200 | 1248 | ||
| 1201 | return f; | 1249 | return f; |
| 1202 | } | 1250 | } |
| @@ -1207,7 +1255,7 @@ const char *xs_number_str(const xs_number *v) | |||
| 1207 | { | 1255 | { |
| 1208 | const char *p = NULL; | 1256 | const char *p = NULL; |
| 1209 | 1257 | ||
| 1210 | if (v != NULL && v[0] == XSTYPE_NUMBER) | 1258 | if (xs_type(v) == XSTYPE_NUMBER) |
| 1211 | p = &v[1]; | 1259 | p = &v[1]; |
| 1212 | 1260 | ||
| 1213 | return p; | 1261 | return p; |
| @@ -1227,7 +1275,7 @@ xs_data *xs_data_new(const void *data, int size) | |||
| 1227 | v = xs_realloc(NULL, _xs_blk_size(total_size)); | 1275 | v = xs_realloc(NULL, _xs_blk_size(total_size)); |
| 1228 | v[0] = XSTYPE_DATA; | 1276 | v[0] = XSTYPE_DATA; |
| 1229 | 1277 | ||
| 1230 | _xs_put_size(v + 1, total_size); | 1278 | _xs_put_size(v, total_size); |
| 1231 | 1279 | ||
| 1232 | memcpy(&v[1 + _XS_TYPE_SIZE], data, size); | 1280 | memcpy(&v[1 + _XS_TYPE_SIZE], data, size); |
| 1233 | 1281 | ||
| @@ -28,7 +28,7 @@ static size_t _header_callback(char *buffer, size_t size, | |||
| 28 | if (xs_str_in(l, ": ") != -1) { | 28 | if (xs_str_in(l, ": ") != -1) { |
| 29 | xs *knv = xs_split_n(l, ": ", 1); | 29 | xs *knv = xs_split_n(l, ": ", 1); |
| 30 | 30 | ||
| 31 | xs_tolower_i(xs_list_get(knv, 0)); | 31 | xs_tolower_i((xs_str *)xs_list_get(knv, 0)); |
| 32 | 32 | ||
| 33 | headers = xs_dict_set(headers, xs_list_get(knv, 0), xs_list_get(knv, 1)); | 33 | headers = xs_dict_set(headers, xs_list_get(knv, 0), xs_list_get(knv, 1)); |
| 34 | } | 34 | } |
| @@ -93,8 +93,8 @@ xs_dict *xs_http_request(const char *method, const char *url, | |||
| 93 | xs_dict *response; | 93 | xs_dict *response; |
| 94 | CURL *curl; | 94 | CURL *curl; |
| 95 | struct curl_slist *list = NULL; | 95 | struct curl_slist *list = NULL; |
| 96 | xs_str *k; | 96 | const xs_str *k; |
| 97 | xs_val *v; | 97 | const xs_val *v; |
| 98 | long lstatus = 0; | 98 | long lstatus = 0; |
| 99 | struct _payload_data pd; | 99 | struct _payload_data pd; |
| 100 | 100 | ||
| @@ -293,8 +293,8 @@ void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b | |||
| 293 | struct fcgi_record_header hdr = {0}; | 293 | struct fcgi_record_header hdr = {0}; |
| 294 | struct fcgi_end_request ereq = {0}; | 294 | struct fcgi_end_request ereq = {0}; |
| 295 | xs *out = xs_str_new(NULL); | 295 | xs *out = xs_str_new(NULL); |
| 296 | xs_str *k; | 296 | const xs_str *k; |
| 297 | xs_str *v; | 297 | const xs_str *v; |
| 298 | 298 | ||
| 299 | /* no previous id? it's an error */ | 299 | /* no previous id? it's an error */ |
| 300 | if (fcgi_id == -1) | 300 | if (fcgi_id == -1) |
| @@ -6,26 +6,26 @@ | |||
| 6 | 6 | ||
| 7 | typedef struct xs_html xs_html; | 7 | typedef struct xs_html xs_html; |
| 8 | 8 | ||
| 9 | xs_str *xs_html_encode(char *str); | 9 | xs_str *xs_html_encode(const char *str); |
| 10 | 10 | ||
| 11 | xs_html *xs_html_attr(char *key, char *value); | 11 | xs_html *xs_html_attr(const char *key, const char *value); |
| 12 | xs_html *xs_html_text(char *content); | 12 | xs_html *xs_html_text(const char *content); |
| 13 | xs_html *xs_html_raw(char *content); | 13 | xs_html *xs_html_raw(const char *content); |
| 14 | 14 | ||
| 15 | xs_html *_xs_html_add(xs_html *tag, xs_html *var[]); | 15 | xs_html *_xs_html_add(xs_html *tag, xs_html *var[]); |
| 16 | #define xs_html_add(tag, ...) _xs_html_add(tag, (xs_html *[]) { __VA_ARGS__, NULL }) | 16 | #define xs_html_add(tag, ...) _xs_html_add(tag, (xs_html *[]) { __VA_ARGS__, NULL }) |
| 17 | 17 | ||
| 18 | xs_html *_xs_html_tag(char *tag, xs_html *var[]); | 18 | xs_html *_xs_html_tag(const char *tag, xs_html *var[]); |
| 19 | #define xs_html_tag(tag, ...) _xs_html_tag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) | 19 | #define xs_html_tag(tag, ...) _xs_html_tag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) |
| 20 | 20 | ||
| 21 | xs_html *_xs_html_sctag(char *tag, xs_html *var[]); | 21 | xs_html *_xs_html_sctag(const char *tag, xs_html *var[]); |
| 22 | #define xs_html_sctag(tag, ...) _xs_html_sctag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) | 22 | #define xs_html_sctag(tag, ...) _xs_html_sctag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) |
| 23 | 23 | ||
| 24 | xs_html *_xs_html_container(xs_html *var[]); | 24 | xs_html *_xs_html_container(xs_html *var[]); |
| 25 | #define xs_html_container(...) _xs_html_container((xs_html *[]) { __VA_ARGS__, NULL }) | 25 | #define xs_html_container(...) _xs_html_container((xs_html *[]) { __VA_ARGS__, NULL }) |
| 26 | 26 | ||
| 27 | void xs_html_render_f(xs_html *h, FILE *f); | 27 | void xs_html_render_f(xs_html *h, FILE *f); |
| 28 | xs_str *xs_html_render_s(xs_html *tag, char *prefix); | 28 | xs_str *xs_html_render_s(xs_html *tag, const char *prefix); |
| 29 | #define xs_html_render(tag) xs_html_render_s(tag, NULL) | 29 | #define xs_html_render(tag) xs_html_render_s(tag, NULL) |
| 30 | 30 | ||
| 31 | 31 | ||
| @@ -47,16 +47,16 @@ struct xs_html { | |||
| 47 | xs_html *next; | 47 | xs_html *next; |
| 48 | }; | 48 | }; |
| 49 | 49 | ||
| 50 | xs_str *xs_html_encode(char *str) | 50 | xs_str *xs_html_encode(const char *str) |
| 51 | /* encodes str using HTML entities */ | 51 | /* encodes str using HTML entities */ |
| 52 | { | 52 | { |
| 53 | xs_str *s = xs_str_new(NULL); | 53 | xs_str *s = xs_str_new(NULL); |
| 54 | int o = 0; | 54 | int o = 0; |
| 55 | char *e = str + strlen(str); | 55 | const char *e = str + strlen(str); |
| 56 | 56 | ||
| 57 | for (;;) { | 57 | for (;;) { |
| 58 | char *ec = "<>\"'&"; /* characters to escape */ | 58 | char *ec = "<>\"'&"; /* characters to escape */ |
| 59 | char *q = e; | 59 | const char *q = e; |
| 60 | int z; | 60 | int z; |
| 61 | 61 | ||
| 62 | /* find the nearest happening of a char */ | 62 | /* find the nearest happening of a char */ |
| @@ -90,7 +90,7 @@ xs_str *xs_html_encode(char *str) | |||
| 90 | 90 | ||
| 91 | #define XS_HTML_NEW() memset(xs_realloc(NULL, sizeof(xs_html)), '\0', sizeof(xs_html)) | 91 | #define XS_HTML_NEW() memset(xs_realloc(NULL, sizeof(xs_html)), '\0', sizeof(xs_html)) |
| 92 | 92 | ||
| 93 | xs_html *xs_html_attr(char *key, char *value) | 93 | xs_html *xs_html_attr(const char *key, const char *value) |
| 94 | /* creates an HTML block with an attribute */ | 94 | /* creates an HTML block with an attribute */ |
| 95 | { | 95 | { |
| 96 | xs_html *a = XS_HTML_NEW(); | 96 | xs_html *a = XS_HTML_NEW(); |
| @@ -108,7 +108,7 @@ xs_html *xs_html_attr(char *key, char *value) | |||
| 108 | } | 108 | } |
| 109 | 109 | ||
| 110 | 110 | ||
| 111 | xs_html *xs_html_text(char *content) | 111 | xs_html *xs_html_text(const char *content) |
| 112 | /* creates an HTML block of text, escaping it previously */ | 112 | /* creates an HTML block of text, escaping it previously */ |
| 113 | { | 113 | { |
| 114 | xs_html *a = XS_HTML_NEW(); | 114 | xs_html *a = XS_HTML_NEW(); |
| @@ -120,7 +120,7 @@ xs_html *xs_html_text(char *content) | |||
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | 122 | ||
| 123 | xs_html *xs_html_raw(char *content) | 123 | xs_html *xs_html_raw(const char *content) |
| 124 | /* creates an HTML block without escaping (for pre-formatted HTML, comments, etc) */ | 124 | /* creates an HTML block without escaping (for pre-formatted HTML, comments, etc) */ |
| 125 | { | 125 | { |
| 126 | xs_html *a = XS_HTML_NEW(); | 126 | xs_html *a = XS_HTML_NEW(); |
| @@ -152,7 +152,7 @@ xs_html *_xs_html_add(xs_html *tag, xs_html *var[]) | |||
| 152 | } | 152 | } |
| 153 | 153 | ||
| 154 | 154 | ||
| 155 | static xs_html *_xs_html_tag_t(xs_html_type type, char *tag, xs_html *var[]) | 155 | static xs_html *_xs_html_tag_t(xs_html_type type, const char *tag, xs_html *var[]) |
| 156 | /* creates a tag with a variable list of attributes and subtags */ | 156 | /* creates a tag with a variable list of attributes and subtags */ |
| 157 | { | 157 | { |
| 158 | xs_html *a = XS_HTML_NEW(); | 158 | xs_html *a = XS_HTML_NEW(); |
| @@ -169,13 +169,13 @@ static xs_html *_xs_html_tag_t(xs_html_type type, char *tag, xs_html *var[]) | |||
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | 171 | ||
| 172 | xs_html *_xs_html_tag(char *tag, xs_html *var[]) | 172 | xs_html *_xs_html_tag(const char *tag, xs_html *var[]) |
| 173 | { | 173 | { |
| 174 | return _xs_html_tag_t(XS_HTML_TAG, tag, var); | 174 | return _xs_html_tag_t(XS_HTML_TAG, tag, var); |
| 175 | } | 175 | } |
| 176 | 176 | ||
| 177 | 177 | ||
| 178 | xs_html *_xs_html_sctag(char *tag, xs_html *var[]) | 178 | xs_html *_xs_html_sctag(const char *tag, xs_html *var[]) |
| 179 | { | 179 | { |
| 180 | return _xs_html_tag_t(XS_HTML_SCTAG, tag, var); | 180 | return _xs_html_tag_t(XS_HTML_SCTAG, tag, var); |
| 181 | } | 181 | } |
| @@ -239,7 +239,7 @@ void xs_html_render_f(xs_html *h, FILE *f) | |||
| 239 | } | 239 | } |
| 240 | 240 | ||
| 241 | 241 | ||
| 242 | xs_str *xs_html_render_s(xs_html *tag, char *prefix) | 242 | xs_str *xs_html_render_s(xs_html *tag, const char *prefix) |
| 243 | /* renders to a string */ | 243 | /* renders to a string */ |
| 244 | { | 244 | { |
| 245 | xs_str *s = NULL; | 245 | xs_str *s = NULL; |
| @@ -16,7 +16,7 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size) | |||
| 16 | xs *q_vars = NULL; | 16 | xs *q_vars = NULL; |
| 17 | xs *p_vars = NULL; | 17 | xs *p_vars = NULL; |
| 18 | xs *l1, *l2; | 18 | xs *l1, *l2; |
| 19 | char *v; | 19 | const char *v; |
| 20 | 20 | ||
| 21 | xs_socket_timeout(fileno(f), 2.0, 0.0); | 21 | xs_socket_timeout(fileno(f), 2.0, 0.0); |
| 22 | 22 | ||
| @@ -60,7 +60,8 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size) | |||
| 60 | p = xs_split_n(l, ": ", 1); | 60 | p = xs_split_n(l, ": ", 1); |
| 61 | 61 | ||
| 62 | if (xs_list_len(p) == 2) | 62 | if (xs_list_len(p) == 2) |
| 63 | req = xs_dict_append(req, xs_tolower_i(xs_list_get(p, 0)), xs_list_get(p, 1)); | 63 | req = xs_dict_append(req, xs_tolower_i( |
| 64 | (xs_str *)xs_list_get(p, 0)), xs_list_get(p, 1)); | ||
| 64 | } | 65 | } |
| 65 | 66 | ||
| 66 | xs_socket_timeout(fileno(f), 5.0, 0.0); | 67 | xs_socket_timeout(fileno(f), 5.0, 0.0); |
| @@ -98,8 +99,8 @@ void xs_httpd_response(FILE *f, int status, xs_dict *headers, xs_str *body, int | |||
| 98 | /* sends an httpd response */ | 99 | /* sends an httpd response */ |
| 99 | { | 100 | { |
| 100 | xs *proto; | 101 | xs *proto; |
| 101 | xs_str *k; | 102 | const xs_str *k; |
| 102 | xs_val *v; | 103 | const xs_val *v; |
| 103 | 104 | ||
| 104 | proto = xs_fmt("HTTP/1.1 %d %s", status, status / 100 == 2 ? "OK" : "ERROR"); | 105 | proto = xs_fmt("HTTP/1.1 %d %s", status, status / 100 == 2 ? "OK" : "ERROR"); |
| 105 | fprintf(f, "%s\r\n", proto); | 106 | fprintf(f, "%s\r\n", proto); |
| @@ -71,12 +71,12 @@ static void _xs_json_indent(int level, int indent, FILE *f) | |||
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | 73 | ||
| 74 | static void _xs_json_dump(const xs_val *s_data, int level, int indent, FILE *f) | 74 | static void _xs_json_dump(const xs_val *data, int level, int indent, FILE *f) |
| 75 | /* dumps partial data as JSON */ | 75 | /* dumps partial data as JSON */ |
| 76 | { | 76 | { |
| 77 | int c = 0; | 77 | int c = 0; |
| 78 | xs_val *v; | 78 | int ct = 0; |
| 79 | xs_val *data = (xs_val *)s_data; | 79 | const xs_val *v; |
| 80 | 80 | ||
| 81 | switch (xs_type(data)) { | 81 | switch (xs_type(data)) { |
| 82 | case XSTYPE_NULL: | 82 | case XSTYPE_NULL: |
| @@ -98,7 +98,7 @@ static void _xs_json_dump(const xs_val *s_data, int level, int indent, FILE *f) | |||
| 98 | case XSTYPE_LIST: | 98 | case XSTYPE_LIST: |
| 99 | fputc('[', f); | 99 | fputc('[', f); |
| 100 | 100 | ||
| 101 | while (xs_list_iter(&data, &v)) { | 101 | while (xs_list_next(data, &v, &ct)) { |
| 102 | if (c != 0) | 102 | if (c != 0) |
| 103 | fputc(',', f); | 103 | fputc(',', f); |
| 104 | 104 | ||
| @@ -116,10 +116,9 @@ static void _xs_json_dump(const xs_val *s_data, int level, int indent, FILE *f) | |||
| 116 | case XSTYPE_DICT: | 116 | case XSTYPE_DICT: |
| 117 | fputc('{', f); | 117 | fputc('{', f); |
| 118 | 118 | ||
| 119 | xs_str *k; | 119 | const xs_str *k; |
| 120 | int ct = 0; | ||
| 121 | 120 | ||
| 122 | while (xs_dict_next(s_data, &k, &v, &ct)) { | 121 | while (xs_dict_next(data, &k, &v, &ct)) { |
| 123 | if (c != 0) | 122 | if (c != 0) |
| 124 | fputc(',', f); | 123 | fputc(',', f); |
| 125 | 124 | ||
| @@ -328,7 +327,7 @@ static xs_val *_xs_json_load_lexer(FILE *f, js_type *t) | |||
| 328 | 327 | ||
| 329 | int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c) | 328 | int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c) |
| 330 | /* loads the next scalar value from the JSON stream */ | 329 | /* loads the next scalar value from the JSON stream */ |
| 331 | /* if the value ahead is complex, value is NULL and pt is filled */ | 330 | /* if the value ahead is compound, value is NULL and pt is set */ |
| 332 | { | 331 | { |
| 333 | js_type t; | 332 | js_type t; |
| 334 | 333 | ||
| @@ -348,7 +347,7 @@ int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c) | |||
| 348 | } | 347 | } |
| 349 | 348 | ||
| 350 | if (*value == NULL) { | 349 | if (*value == NULL) { |
| 351 | /* possible complex type ahead */ | 350 | /* possible compound type ahead */ |
| 352 | if (t == JS_OBRACK) | 351 | if (t == JS_OBRACK) |
| 353 | *pt = XSTYPE_LIST; | 352 | *pt = XSTYPE_LIST; |
| 354 | else | 353 | else |
| @@ -365,7 +364,7 @@ int xs_json_load_array_iter(FILE *f, xs_val **value, xstype *pt, int *c) | |||
| 365 | 364 | ||
| 366 | 365 | ||
| 367 | xs_list *xs_json_load_array(FILE *f) | 366 | xs_list *xs_json_load_array(FILE *f) |
| 368 | /* loads a JSON array (after the initial OBRACK) */ | 367 | /* loads a full JSON array (after the initial OBRACK) */ |
| 369 | { | 368 | { |
| 370 | xstype t; | 369 | xstype t; |
| 371 | xs_list *l = xs_list_new(); | 370 | xs_list *l = xs_list_new(); |
| @@ -406,7 +405,7 @@ xs_list *xs_json_load_array(FILE *f) | |||
| 406 | 405 | ||
| 407 | int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, int *c) | 406 | 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 */ | 407 | /* loads the next key and scalar value from the JSON stream */ |
| 409 | /* if the value ahead is complex, value is NULL and pt is filled */ | 408 | /* if the value ahead is compound, value is NULL and pt is set */ |
| 410 | { | 409 | { |
| 411 | js_type t; | 410 | js_type t; |
| 412 | 411 | ||
| @@ -453,7 +452,7 @@ int xs_json_load_object_iter(FILE *f, xs_str **key, xs_val **value, xstype *pt, | |||
| 453 | 452 | ||
| 454 | 453 | ||
| 455 | xs_dict *xs_json_load_object(FILE *f) | 454 | xs_dict *xs_json_load_object(FILE *f) |
| 456 | /* loads a JSON object (after the initial OCURLY) */ | 455 | /* loads a full JSON object (after the initial OCURLY) */ |
| 457 | { | 456 | { |
| 458 | xstype t; | 457 | xstype t; |
| 459 | xs_dict *d = xs_dict_new(); | 458 | xs_dict *d = xs_dict_new(); |
| @@ -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 | ||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #define _XS_REGEX_H | 5 | #define _XS_REGEX_H |
| 6 | 6 | ||
| 7 | int xs_regex_match(const char *str, const char *rx); | ||
| 7 | xs_list *xs_regex_split_n(const char *str, const char *rx, int count); | 8 | xs_list *xs_regex_split_n(const char *str, const char *rx, int count); |
| 8 | #define xs_regex_split(str, rx) xs_regex_split_n(str, rx, XS_ALL) | 9 | #define xs_regex_split(str, rx) xs_regex_split_n(str, rx, XS_ALL) |
| 9 | xs_list *xs_regex_select_n(const char *str, const char *rx, int count); | 10 | xs_list *xs_regex_select_n(const char *str, const char *rx, int count); |
| @@ -15,21 +16,29 @@ xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int c | |||
| 15 | 16 | ||
| 16 | #ifdef XS_IMPLEMENTATION | 17 | #ifdef XS_IMPLEMENTATION |
| 17 | 18 | ||
| 19 | #ifdef __TINYC__ | ||
| 20 | /* fix a compilation error in tcc */ | ||
| 21 | #define _REGEX_NELTS(n) | ||
| 22 | #endif | ||
| 23 | |||
| 18 | #include <regex.h> | 24 | #include <regex.h> |
| 19 | 25 | ||
| 20 | xs_list *xs_regex_split_n(const char *str, const char *rx, int count) | 26 | xs_list *xs_regex_split_n(const char *str, const char *rx, int count) |
| 21 | /* splits str by regex */ | 27 | /* splits str using regex as a separator, at most count times. |
| 28 | Always returns a list: | ||
| 29 | len == 0: regcomp error | ||
| 30 | len == 1: full string (no matches) | ||
| 31 | len == odd: first part [ separator / next part ]... | ||
| 32 | */ | ||
| 22 | { | 33 | { |
| 23 | regex_t re; | 34 | regex_t re; |
| 24 | regmatch_t rm; | 35 | regmatch_t rm; |
| 25 | int offset = 0; | 36 | int offset = 0; |
| 26 | xs_list *list = NULL; | 37 | xs_list *list = xs_list_new(); |
| 27 | const char *p; | 38 | const char *p; |
| 28 | 39 | ||
| 29 | if (regcomp(&re, rx, REG_EXTENDED)) | 40 | if (regcomp(&re, rx, REG_EXTENDED)) |
| 30 | return NULL; | 41 | return list; |
| 31 | |||
| 32 | list = xs_list_new(); | ||
| 33 | 42 | ||
| 34 | while (count > 0 && !regexec(&re, (p = str + offset), 1, &rm, offset > 0 ? REG_NOTBOL : 0)) { | 43 | while (count > 0 && !regexec(&re, (p = str + offset), 1, &rm, offset > 0 ? REG_NOTBOL : 0)) { |
| 35 | /* add first the leading part of the string */ | 44 | /* add first the leading part of the string */ |
| @@ -60,16 +69,15 @@ xs_list *xs_regex_select_n(const char *str, const char *rx, int count) | |||
| 60 | { | 69 | { |
| 61 | xs_list *list = xs_list_new(); | 70 | xs_list *list = xs_list_new(); |
| 62 | xs *split = NULL; | 71 | xs *split = NULL; |
| 63 | xs_list *p; | 72 | const xs_val *v; |
| 64 | xs_val *v; | ||
| 65 | int n = 0; | 73 | int n = 0; |
| 74 | int c = 0; | ||
| 66 | 75 | ||
| 67 | /* split */ | 76 | /* split */ |
| 68 | split = xs_regex_split_n(str, rx, count); | 77 | split = xs_regex_split_n(str, rx, count); |
| 69 | 78 | ||
| 70 | /* now iterate to get only the 'separators' (odd ones) */ | 79 | /* now iterate to get only the 'separators' (odd ones) */ |
| 71 | p = split; | 80 | while (xs_list_next(split, &v, &c)) { |
| 72 | while (xs_list_iter(&p, &v)) { | ||
| 73 | if (n & 0x1) | 81 | if (n & 0x1) |
| 74 | list = xs_list_append(list, v); | 82 | list = xs_list_append(list, v); |
| 75 | 83 | ||
| @@ -86,13 +94,12 @@ xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int c | |||
| 86 | { | 94 | { |
| 87 | xs_str *s = xs_str_new(NULL); | 95 | xs_str *s = xs_str_new(NULL); |
| 88 | xs *split = xs_regex_split_n(str, rx, count); | 96 | xs *split = xs_regex_split_n(str, rx, count); |
| 89 | xs_list *p; | 97 | const xs_val *v; |
| 90 | xs_val *v; | ||
| 91 | int n = 0; | 98 | int n = 0; |
| 99 | int c = 0; | ||
| 92 | int pholder = !!strchr(rep, '&'); | 100 | int pholder = !!strchr(rep, '&'); |
| 93 | 101 | ||
| 94 | p = split; | 102 | while (xs_list_next(split, &v, &c)) { |
| 95 | while (xs_list_iter(&p, &v)) { | ||
| 96 | if (n & 0x1) { | 103 | if (n & 0x1) { |
| 97 | if (pholder) { | 104 | if (pholder) { |
| 98 | /* rep has a placeholder; process char by char */ | 105 | /* rep has a placeholder; process char by char */ |
| @@ -128,6 +135,16 @@ xs_list *xs_regex_replace_in(xs_str *str, const char *rx, const char *rep, int c | |||
| 128 | return s; | 135 | return s; |
| 129 | } | 136 | } |
| 130 | 137 | ||
| 138 | |||
| 139 | int xs_regex_match(const char *str, const char *rx) | ||
| 140 | /* returns if str matches the regex at least once */ | ||
| 141 | { | ||
| 142 | xs *l = xs_regex_select_n(str, rx, 1); | ||
| 143 | |||
| 144 | return xs_list_len(l) == 1; | ||
| 145 | } | ||
| 146 | |||
| 147 | |||
| 131 | #endif /* XS_IMPLEMENTATION */ | 148 | #endif /* XS_IMPLEMENTATION */ |
| 132 | 149 | ||
| 133 | #endif /* XS_REGEX_H */ | 150 | #endif /* XS_REGEX_H */ |
| @@ -85,7 +85,7 @@ int xs_set_add(xs_set *s, const xs_val *data) | |||
| 85 | { | 85 | { |
| 86 | /* is it 'full'? */ | 86 | /* is it 'full'? */ |
| 87 | if (s->used >= s->elems / 2) { | 87 | if (s->used >= s->elems / 2) { |
| 88 | char *p, *v; | 88 | const xs_val *v; |
| 89 | 89 | ||
| 90 | /* expand! */ | 90 | /* expand! */ |
| 91 | s->elems *= 2; | 91 | s->elems *= 2; |
| @@ -95,8 +95,8 @@ int xs_set_add(xs_set *s, const xs_val *data) | |||
| 95 | memset(s->hash, '\0', s->elems * sizeof(int)); | 95 | memset(s->hash, '\0', s->elems * sizeof(int)); |
| 96 | 96 | ||
| 97 | /* add the list elements back */ | 97 | /* add the list elements back */ |
| 98 | p = s->list; | 98 | int ct = 0; |
| 99 | while (xs_list_iter(&p, &v)) | 99 | while (xs_list_next(s->list, &v, &ct)) |
| 100 | _store_hash(s, v, v - s->list); | 100 | _store_hash(s, v, v - s->list); |
| 101 | } | 101 | } |
| 102 | 102 | ||
| @@ -104,7 +104,7 @@ int xs_set_add(xs_set *s, const xs_val *data) | |||
| 104 | 104 | ||
| 105 | /* if it's new, add the data */ | 105 | /* if it's new, add the data */ |
| 106 | if (ret) | 106 | if (ret) |
| 107 | s->list = xs_list_append_m(s->list, data, xs_size(data)); | 107 | s->list = xs_list_append(s->list, data); |
| 108 | 108 | ||
| 109 | return ret; | 109 | return ret; |
| 110 | } | 110 | } |
diff --git a/xs_unicode.h b/xs_unicode.h index 47e1101..1799d89 100644 --- a/xs_unicode.h +++ b/xs_unicode.h | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | int _xs_utf8_enc(char buf[4], unsigned int cpoint); | 7 | int _xs_utf8_enc(char buf[4], unsigned int cpoint); |
| 8 | int xs_is_utf8_cont_byte(char c); | 8 | int xs_is_utf8_cont_byte(char c); |
| 9 | unsigned int xs_utf8_dec(char **str); | 9 | unsigned int xs_utf8_dec(const char **str); |
| 10 | int xs_unicode_width(unsigned int cpoint); | 10 | int xs_unicode_width(unsigned int cpoint); |
| 11 | int xs_is_surrogate(unsigned int cpoint); | 11 | int xs_is_surrogate(unsigned int cpoint); |
| 12 | unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2); | 12 | unsigned int xs_surrogate_dec(unsigned int p1, unsigned int p2); |
| @@ -27,8 +27,8 @@ | |||
| 27 | 27 | ||
| 28 | #ifdef XS_IMPLEMENTATION | 28 | #ifdef XS_IMPLEMENTATION |
| 29 | 29 | ||
| 30 | #ifndef countof | 30 | #ifndef xs_countof |
| 31 | #define countof(a) (sizeof((a)) / sizeof((*a))) | 31 | #define xs_countof(a) (sizeof((a)) / sizeof((*a))) |
| 32 | #endif | 32 | #endif |
| 33 | 33 | ||
| 34 | int _xs_utf8_enc(char buf[4], unsigned int cpoint) | 34 | int _xs_utf8_enc(char buf[4], unsigned int cpoint) |
| @@ -66,10 +66,10 @@ int xs_is_utf8_cont_byte(char c) | |||
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | 68 | ||
| 69 | unsigned int xs_utf8_dec(char **str) | 69 | unsigned int xs_utf8_dec(const char **str) |
| 70 | /* decodes an utf-8 char inside str and updates the pointer */ | 70 | /* decodes an utf-8 char inside str and updates the pointer */ |
| 71 | { | 71 | { |
| 72 | char *p = *str; | 72 | const char *p = *str; |
| 73 | unsigned int cpoint = 0; | 73 | unsigned int cpoint = 0; |
| 74 | unsigned char c = *p++; | 74 | unsigned char c = *p++; |
| 75 | int cb = 0; | 75 | int cb = 0; |
| @@ -125,7 +125,7 @@ int xs_unicode_width(unsigned int cpoint) | |||
| 125 | /* returns the width in columns of a Unicode codepoint (somewhat simplified) */ | 125 | /* returns the width in columns of a Unicode codepoint (somewhat simplified) */ |
| 126 | { | 126 | { |
| 127 | int b = 0; | 127 | int b = 0; |
| 128 | int t = countof(xs_unicode_width_table) / 3 - 1; | 128 | int t = xs_countof(xs_unicode_width_table) / 3 - 1; |
| 129 | 129 | ||
| 130 | while (t >= b) { | 130 | while (t >= b) { |
| 131 | int n = (b + t) / 2; | 131 | int n = (b + t) / 2; |
| @@ -193,7 +193,7 @@ unsigned int *_xs_unicode_upper_search(unsigned int cpoint) | |||
| 193 | /* searches for an uppercase codepoint in the case fold table */ | 193 | /* searches for an uppercase codepoint in the case fold table */ |
| 194 | { | 194 | { |
| 195 | int b = 0; | 195 | int b = 0; |
| 196 | int t = countof(xs_unicode_case_fold_table) / 2 + 1; | 196 | int t = xs_countof(xs_unicode_case_fold_table) / 2 + 1; |
| 197 | 197 | ||
| 198 | while (t >= b) { | 198 | while (t >= b) { |
| 199 | int n = (b + t) / 2; | 199 | int n = (b + t) / 2; |
| @@ -216,7 +216,7 @@ unsigned int *_xs_unicode_lower_search(unsigned int cpoint) | |||
| 216 | /* searches for a lowercase codepoint in the case fold table */ | 216 | /* searches for a lowercase codepoint in the case fold table */ |
| 217 | { | 217 | { |
| 218 | unsigned int *p = xs_unicode_case_fold_table; | 218 | unsigned int *p = xs_unicode_case_fold_table; |
| 219 | unsigned int *e = p + countof(xs_unicode_case_fold_table); | 219 | unsigned int *e = p + xs_countof(xs_unicode_case_fold_table); |
| 220 | 220 | ||
| 221 | while (p < e) { | 221 | while (p < e) { |
| 222 | if (cpoint == p[1]) | 222 | if (cpoint == p[1]) |
| @@ -251,7 +251,7 @@ int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac) | |||
| 251 | /* applies unicode Normalization Form D */ | 251 | /* applies unicode Normalization Form D */ |
| 252 | { | 252 | { |
| 253 | int b = 0; | 253 | int b = 0; |
| 254 | int t = countof(xs_unicode_nfd_table) / 3 - 1; | 254 | int t = xs_countof(xs_unicode_nfd_table) / 3 - 1; |
| 255 | 255 | ||
| 256 | while (t >= b) { | 256 | while (t >= b) { |
| 257 | int n = (b + t) / 2; | 257 | int n = (b + t) / 2; |
| @@ -279,7 +279,7 @@ int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint) | |||
| 279 | /* applies unicode Normalization Form C */ | 279 | /* applies unicode Normalization Form C */ |
| 280 | { | 280 | { |
| 281 | unsigned int *p = xs_unicode_nfd_table; | 281 | unsigned int *p = xs_unicode_nfd_table; |
| 282 | unsigned int *e = p + countof(xs_unicode_nfd_table); | 282 | unsigned int *e = p + xs_countof(xs_unicode_nfd_table); |
| 283 | 283 | ||
| 284 | while (p < e) { | 284 | while (p < e) { |
| 285 | if (p[1] == base && p[2] == diac) { | 285 | if (p[1] == base && p[2] == diac) { |
| @@ -298,7 +298,7 @@ int xs_unicode_is_alpha(unsigned int cpoint) | |||
| 298 | /* checks if a codepoint is an alpha (i.e. a letter) */ | 298 | /* checks if a codepoint is an alpha (i.e. a letter) */ |
| 299 | { | 299 | { |
| 300 | int b = 0; | 300 | int b = 0; |
| 301 | int t = countof(xs_unicode_alpha_table) / 2 - 1; | 301 | int t = xs_countof(xs_unicode_alpha_table) / 2 - 1; |
| 302 | 302 | ||
| 303 | while (t >= b) { | 303 | while (t >= b) { |
| 304 | int n = (b + t) / 2; | 304 | int n = (b + t) / 2; |
| @@ -51,12 +51,11 @@ xs_dict *xs_url_vars(const char *str) | |||
| 51 | /* split by arguments */ | 51 | /* split by arguments */ |
| 52 | xs *args = xs_split(str, "&"); | 52 | xs *args = xs_split(str, "&"); |
| 53 | 53 | ||
| 54 | xs_list *l; | 54 | int ct = 0; |
| 55 | xs_val *v; | 55 | const xs_val *v; |
| 56 | 56 | ||
| 57 | l = args; | 57 | while (xs_list_next(args, &v, &ct)) { |
| 58 | while (xs_list_iter(&l, &v)) { | 58 | xs *kv = xs_split_n(v, "=", 1); |
| 59 | xs *kv = xs_split_n(v, "=", 2); | ||
| 60 | 59 | ||
| 61 | if (xs_list_len(kv) == 2) { | 60 | if (xs_list_len(kv) == 2) { |
| 62 | const char *key = xs_list_get(kv, 0); | 61 | const char *key = xs_list_get(kv, 0); |
| @@ -119,8 +118,8 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea | |||
| 119 | while ((p = xs_memmem(payload + offset, p_size - offset, boundary, bsz)) != NULL) { | 118 | while ((p = xs_memmem(payload + offset, p_size - offset, boundary, bsz)) != NULL) { |
| 120 | xs *s1 = NULL; | 119 | xs *s1 = NULL; |
| 121 | xs *l1 = NULL; | 120 | xs *l1 = NULL; |
| 122 | char *vn = NULL; | 121 | const char *vn = NULL; |
| 123 | char *fn = NULL; | 122 | const char *fn = NULL; |
| 124 | char *q; | 123 | char *q; |
| 125 | int po, ps; | 124 | int po, ps; |
| 126 | 125 | ||
diff --git a/xs_version.h b/xs_version.h index ef52120..7a7ba53 100644 --- a/xs_version.h +++ b/xs_version.h | |||
| @@ -1 +1 @@ | |||
| /* 0df383371d207b0adfda40912ee54b37e5b01152 2024-03-15T03:45:39+01:00 */ | /* 65769f25ed99b886a643522bef21628396cd118d 2024-05-25T08:18:51+02:00 */ | ||