summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--RELEASE_NOTES.md10
-rw-r--r--activitypub.c56
-rw-r--r--data.c121
-rw-r--r--doc/snac.130
-rwxr-xr-xexamples/docker-entrypoint.sh2
-rw-r--r--html.c27
-rw-r--r--http.c2
-rw-r--r--httpd.c8
-rw-r--r--main.c22
-rw-r--r--mastoapi.c201
-rw-r--r--snac.h31
-rw-r--r--webfinger.c8
-rw-r--r--xs.h12
-rw-r--r--xs_encdec.h221
-rw-r--r--xs_version.h2
16 files changed, 610 insertions, 147 deletions
diff --git a/Makefile b/Makefile
index c0e4170..7dbd9e7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
1PREFIX=/usr/local 1PREFIX=/usr/local
2PREFIX_MAN=$(PREFIX)/man 2PREFIX_MAN=$(PREFIX)/man
3CFLAGS?=-g -Wall 3CFLAGS?=-g -Wall -Wextra
4 4
5all: snac 5all: snac
6 6
@@ -40,7 +40,7 @@ httpd.o: httpd.c xs.h xs_io.h xs_encdec.h xs_json.h xs_socket.h \
40 xs_httpd.h xs_mime.h snac.h 40 xs_httpd.h xs_mime.h snac.h
41main.o: main.c xs.h xs_io.h xs_encdec.h xs_json.h snac.h 41main.o: main.c xs.h xs_io.h xs_encdec.h xs_json.h snac.h
42mastoapi.o: mastoapi.c xs.h xs_encdec.h xs_openssl.h xs_json.h xs_io.h \ 42mastoapi.o: mastoapi.c xs.h xs_encdec.h xs_openssl.h xs_json.h xs_io.h \
43 xs_time.h snac.h 43 xs_time.h xs_glob.h snac.h
44snac.o: snac.c xs.h xs_io.h xs_encdec.h xs_json.h xs_curl.h xs_openssl.h \ 44snac.o: snac.c xs.h xs_io.h xs_encdec.h xs_json.h xs_curl.h xs_openssl.h \
45 xs_socket.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h \ 45 xs_socket.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h \
46 snac.h 46 snac.h
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 8a153e9..1af4b73 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,15 @@
1# Release Notes 1# Release Notes
2 2
3## 2.30
4
5Fixed a bug that made some notifications to be missed.
6
7New Mastodon API features: the instance public timeline is now a real one, unfavourite / unreblog is supported (somewhat). Some regression bugs regarding image posting were also fixed.
8
9The non-standard `Ping` and `Pong` #ActivityPub activities have been implemented as proposed by @tedu@honk.tedunangst.com in the https://humungus.tedunangst.com/r/honk/v/tip/f/docs/ping.txt document (with a minor diversion: retries are managed in the same way as the rest of #snac messages).
10
11The build process now includes the `-Wextra` flag.
12
3## 2.29 13## 2.29
4 14
5New Mastodon API features: account search, relationships (so the Follow/Unfollow buttons now appear for each account), follow and unfollow accounts, an instance-level timeline (very kludgy), custom emojis for accounts and statuses, many bug fixes (sadly, the Mastodon official app still does not work). 15New Mastodon API features: account search, relationships (so the Follow/Unfollow buttons now appear for each account), follow and unfollow accounts, an instance-level timeline (very kludgy), custom emojis for accounts and statuses, many bug fixes (sadly, the Mastodon official app still does not work).
diff --git a/activitypub.c b/activitypub.c
index c8c166d..9069d03 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -109,7 +109,7 @@ int actor_request(snac *snac, const char *actor, xs_dict **data)
109 109
110 if (valid_status(status2)) { 110 if (valid_status(status2)) {
111 /* renew data */ 111 /* renew data */
112 status = actor_add(snac, actor, payload); 112 status = actor_add(actor, payload);
113 113
114 if (data != NULL) { 114 if (data != NULL) {
115 *data = payload; 115 *data = payload;
@@ -437,7 +437,8 @@ void process_tags(snac *snac, const char *content, d_char **n_content, d_char **
437 437
438/** messages **/ 438/** messages **/
439 439
440d_char *msg_base(snac *snac, char *type, char *id, char *actor, char *date, char *object) 440xs_dict *msg_base(snac *snac, const char *type, const char *id,
441 const char *actor, const char *date, const char *object)
441/* creates a base ActivityPub message */ 442/* creates a base ActivityPub message */
442{ 443{
443 xs *did = NULL; 444 xs *did = NULL;
@@ -467,7 +468,7 @@ d_char *msg_base(snac *snac, char *type, char *id, char *actor, char *date, char
467 } 468 }
468 } 469 }
469 470
470 d_char *msg = xs_dict_new(); 471 xs_dict *msg = xs_dict_new();
471 472
472 msg = xs_dict_append(msg, "@context", "https:/" "/www.w3.org/ns/activitystreams"); 473 msg = xs_dict_append(msg, "@context", "https:/" "/www.w3.org/ns/activitystreams");
473 msg = xs_dict_append(msg, "type", type); 474 msg = xs_dict_append(msg, "type", type);
@@ -845,6 +846,28 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
845} 846}
846 847
847 848
849xs_dict *msg_ping(snac *user, const char *rcpt)
850/* creates a Ping message (https://humungus.tedunangst.com/r/honk/v/tip/f/docs/ping.txt) */
851{
852 xs_dict *msg = msg_base(user, "Ping", "@dummy", user->actor, NULL, NULL);
853
854 msg = xs_dict_append(msg, "to", rcpt);
855
856 return msg;
857}
858
859
860xs_dict *msg_pong(snac *user, const char *rcpt, const char *object)
861/* creates a Pong message (https://humungus.tedunangst.com/r/honk/v/tip/f/docs/ping.txt) */
862{
863 xs_dict *msg = msg_base(user, "Pong", "@dummy", user->actor, NULL, object);
864
865 msg = xs_dict_append(msg, "to", rcpt);
866
867 return msg;
868}
869
870
848void notify(snac *snac, xs_str *type, xs_str *utype, xs_str *actor, xs_dict *msg) 871void notify(snac *snac, xs_str *type, xs_str *utype, xs_str *actor, xs_dict *msg)
849/* notifies the user of relevant events */ 872/* notifies the user of relevant events */
850{ 873{
@@ -1121,7 +1144,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1121 else 1144 else
1122 if (strcmp(type, "Update") == 0) { 1145 if (strcmp(type, "Update") == 0) {
1123 if (strcmp(utype, "Person") == 0) { 1146 if (strcmp(utype, "Person") == 0) {
1124 actor_add(snac, actor, xs_dict_get(msg, "object")); 1147 actor_add(actor, xs_dict_get(msg, "object"));
1125 1148
1126 snac_log(snac, xs_fmt("updated actor %s", actor)); 1149 snac_log(snac, xs_fmt("updated actor %s", actor));
1127 } 1150 }
@@ -1147,7 +1170,19 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1147 snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object)); 1170 snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object));
1148 } 1171 }
1149 else 1172 else
1150 snac_debug(snac, 1, xs_fmt("process_message type '%s' ignored", type)); 1173 if (strcmp(type, "Pong") == 0) {
1174 snac_log(snac, xs_fmt("'Pong' received from %s", actor));
1175 }
1176 else
1177 if (strcmp(type, "Ping") == 0) {
1178 snac_log(snac, xs_fmt("'Ping' requested from %s", actor));
1179
1180 xs *rsp = msg_pong(snac, actor, xs_dict_get(msg, "id"));
1181
1182 enqueue_output_by_actor(snac, rsp, actor, 0);
1183 }
1184 else
1185 snac_debug(snac, 1, xs_fmt("process_input_message type '%s' ignored", type));
1151 1186
1152 if (do_notify) { 1187 if (do_notify) {
1153 notify(snac, type, utype, actor, msg); 1188 notify(snac, type, utype, actor, msg);
@@ -1384,8 +1419,9 @@ void process_queue_item(xs_dict *q_item)
1384 xs *headers = xs_dict_new(); 1419 xs *headers = xs_dict_new();
1385 headers = xs_dict_append(headers, "content-type", "application/json"); 1420 headers = xs_dict_append(headers, "content-type", "application/json");
1386 1421
1387 xs *rsp = xs_http_request("POST", url, headers, 1422 xs *rsp = xs_http_request("POST", url, headers,
1388 body, strlen(body), &status, NULL, NULL, 0); 1423 body, strlen(body), &status, NULL, NULL, 0);
1424 rsp = xs_free(rsp);
1389 1425
1390 srv_debug(0, xs_fmt("telegram post %d", status)); 1426 srv_debug(0, xs_fmt("telegram post %d", status));
1391 } 1427 }
@@ -1426,7 +1462,7 @@ int process_queue(void)
1426 1462
1427/** HTTP handlers */ 1463/** HTTP handlers */
1428 1464
1429int activitypub_get_handler(d_char *req, char *q_path, 1465int activitypub_get_handler(const xs_dict *req, const char *q_path,
1430 char **body, int *b_size, char **ctype) 1466 char **body, int *b_size, char **ctype)
1431{ 1467{
1432 int status = 200; 1468 int status = 200;
@@ -1519,11 +1555,13 @@ int activitypub_get_handler(d_char *req, char *q_path,
1519} 1555}
1520 1556
1521 1557
1522int activitypub_post_handler(d_char *req, char *q_path, 1558int activitypub_post_handler(const xs_dict *req, const char *q_path,
1523 d_char *payload, int p_size, 1559 char *payload, int p_size,
1524 char **body, int *b_size, char **ctype) 1560 char **body, int *b_size, char **ctype)
1525/* processes an input message */ 1561/* processes an input message */
1526{ 1562{
1563 (void)b_size;
1564
1527 int status = 202; /* accepted */ 1565 int status = 202; /* accepted */
1528 char *i_ctype = xs_dict_get(req, "content-type"); 1566 char *i_ctype = xs_dict_get(req, "content-type");
1529 snac snac; 1567 snac snac;
diff --git a/data.c b/data.c
index c2d29c1..72b63f8 100644
--- a/data.c
+++ b/data.c
@@ -328,6 +328,51 @@ int index_add(const char *fn, const char *id)
328} 328}
329 329
330 330
331int index_del_md5(const char *fn, const char *md5)
332/* deletes an md5 from an index */
333{
334 int status = 404;
335 FILE *f;
336
337 pthread_mutex_lock(&data_mutex);
338
339 if ((f = fopen(fn, "r+")) != NULL) {
340 char line[256];
341
342 while (fgets(line, sizeof(line), f) != NULL) {
343 line[32] = '\0';
344
345 if (strcmp(line, md5) == 0) {
346 /* found! just rewind, overwrite it with garbage
347 and an eventual call to index_gc() will clean it
348 [yes: this breaks index_len()] */
349 fseek(f, -33, SEEK_CUR);
350 fwrite("-", 1, 1, f);
351 status = 200;
352
353 break;
354 }
355 }
356
357 fclose(f);
358 }
359 else
360 status = 500;
361
362 pthread_mutex_unlock(&data_mutex);
363
364 return status;
365}
366
367
368int index_del(const char *fn, const char *id)
369/* deletes an id from an index */
370{
371 xs *md5 = xs_md5_hex(id, strlen(id));
372 return index_del_md5(fn, md5);
373}
374
375
331int index_gc(const char *fn) 376int index_gc(const char *fn)
332/* garbage-collects an index, deleting objects that are not here */ 377/* garbage-collects an index, deleting objects that are not here */
333{ 378{
@@ -772,6 +817,23 @@ int object_admire(const char *id, const char *actor, int like)
772} 817}
773 818
774 819
820int object_unadmire(const char *id, const char *actor, int like)
821/* actor no longer likes or announces this object */
822{
823 int status;
824 xs *fn = _object_fn(id);
825
826 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx");
827
828 status = index_del(fn, actor);
829
830 srv_debug(0,
831 xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status));
832
833 return status;
834}
835
836
775int _object_user_cache(snac *snac, const char *id, const char *cachedir, int del) 837int _object_user_cache(snac *snac, const char *id, const char *cachedir, int del)
776/* adds or deletes from a user cache */ 838/* adds or deletes from a user cache */
777{ 839{
@@ -969,8 +1031,13 @@ void timeline_update_indexes(snac *snac, const char *id)
969 1031
970 if (valid_status(object_get(id, &msg))) { 1032 if (valid_status(object_get(id, &msg))) {
971 /* if its ours and is public, also store in public */ 1033 /* if its ours and is public, also store in public */
972 if (is_msg_public(snac, msg)) 1034 if (is_msg_public(snac, msg)) {
973 object_user_cache_add(snac, id, "public"); 1035 object_user_cache_add(snac, id, "public");
1036
1037 /* also add it to the instance public timeline */
1038 xs *ipt = xs_fmt("%s/public.idx", srv_basedir);
1039 index_add(ipt, id);
1040 }
974 } 1041 }
975 } 1042 }
976} 1043}
@@ -1041,7 +1108,7 @@ xs_list *timeline_top_level(snac *snac, xs_list *list)
1041} 1108}
1042 1109
1043 1110
1044d_char *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show) 1111xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show)
1045/* returns a timeline (with all entries) */ 1112/* returns a timeline (with all entries) */
1046{ 1113{
1047 int c_max; 1114 int c_max;
@@ -1059,7 +1126,7 @@ d_char *timeline_simple_list(snac *snac, const char *idx_name, int skip, int sho
1059} 1126}
1060 1127
1061 1128
1062d_char *timeline_list(snac *snac, const char *idx_name, int skip, int show) 1129xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show)
1063/* returns a timeline (only top level entries) */ 1130/* returns a timeline (only top level entries) */
1064{ 1131{
1065 xs *list = timeline_simple_list(snac, idx_name, skip, show); 1132 xs *list = timeline_simple_list(snac, idx_name, skip, show);
@@ -1068,6 +1135,15 @@ d_char *timeline_list(snac *snac, const char *idx_name, int skip, int show)
1068} 1135}
1069 1136
1070 1137
1138xs_list *timeline_instance_list(int skip, int show)
1139/* returns the timeline for the full instance */
1140{
1141 xs *idx = xs_fmt("%s/public.idx", srv_basedir);
1142
1143 return index_list_desc(idx, skip, show);
1144}
1145
1146
1071/** following **/ 1147/** following **/
1072 1148
1073/* this needs special treatment and cannot use the object db as is, 1149/* this needs special treatment and cannot use the object db as is,
@@ -1273,30 +1349,49 @@ int is_hidden(snac *snac, const char *id)
1273} 1349}
1274 1350
1275 1351
1276int actor_add(snac *snac, const char *actor, d_char *msg) 1352int actor_add(const char *actor, xs_dict *msg)
1277/* adds an actor */ 1353/* adds an actor */
1278{ 1354{
1279 return object_add_ow(actor, msg); 1355 return object_add_ow(actor, msg);
1280} 1356}
1281 1357
1282 1358
1283int actor_get(snac *snac, const char *actor, d_char **data) 1359int actor_get(snac *snac1, const char *actor, xs_dict **data)
1284/* returns an already downloaded actor */ 1360/* returns an already downloaded actor */
1285{ 1361{
1286 int status = 200; 1362 int status = 200;
1287 d_char *d; 1363 xs_dict *d = NULL;
1288 1364
1289 if (strcmp(actor, snac->actor) == 0) { 1365 if (strcmp(actor, snac1->actor) == 0) {
1290 /* this actor */ 1366 /* this actor */
1291 if (data) 1367 if (data)
1292 *data = msg_actor(snac); 1368 *data = msg_actor(snac1);
1293 1369
1294 return status; 1370 return status;
1295 } 1371 }
1296 1372
1373 if (xs_startswith(actor, srv_baseurl)) {
1374 /* it's a (possible) local user */
1375 xs *l = xs_split(actor, "/");
1376 const char *uid = xs_list_get(l, -1);
1377 snac user;
1378
1379 if (!xs_is_null(uid) && user_open(&user, uid)) {
1380 if (data)
1381 *data = msg_actor(&user);
1382
1383 user_free(&user);
1384 return 200;
1385 }
1386 else
1387 return 404;
1388 }
1389
1297 /* read the object */ 1390 /* read the object */
1298 if (!valid_status(status = object_get(actor, &d))) 1391 if (!valid_status(status = object_get(actor, &d))) {
1392 d = xs_free(d);
1299 return status; 1393 return status;
1394 }
1300 1395
1301 if (data) 1396 if (data)
1302 *data = d; 1397 *data = d;
@@ -1719,7 +1814,7 @@ static xs_dict *_new_qmsg(const char *type, const xs_val *msg, int retries)
1719} 1814}
1720 1815
1721 1816
1722void enqueue_input(snac *snac, xs_dict *msg, xs_dict *req, int retries) 1817void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries)
1723/* enqueues an input message */ 1818/* enqueues an input message */
1724{ 1819{
1725 xs *qmsg = _new_qmsg("input", msg, retries); 1820 xs *qmsg = _new_qmsg("input", msg, retries);
@@ -2020,7 +2115,11 @@ void purge_server(void)
2020 xs *ib_dir = xs_fmt("%s/inbox", srv_basedir); 2115 xs *ib_dir = xs_fmt("%s/inbox", srv_basedir);
2021 _purge_dir(ib_dir, 7); 2116 _purge_dir(ib_dir, 7);
2022 2117
2023 srv_debug(1, xs_fmt("purge: global (obj: %d, idx: %d)", cnt, icnt)); 2118 /* purge the instance timeline */
2119 xs *itl_fn = xs_fmt("%s/public.idx", srv_basedir);
2120 int itl_gc = index_gc(itl_fn);
2121
2122 srv_debug(1, xs_fmt("purge: global (obj: %d, idx: %d, itl: %d)", cnt, icnt, itl_gc));
2024} 2123}
2025 2124
2026 2125
diff --git a/doc/snac.1 b/doc/snac.1
index ec8df64..ef84f3d 100644
--- a/doc/snac.1
+++ b/doc/snac.1
@@ -220,6 +220,36 @@ Please take note that they will show your timeline in a 'Mastodon fashion'
220post display with the most active threads at the top that the web interface of 220post display with the most active threads at the top that the web interface of
221.Nm 221.Nm
222provides. 222provides.
223.Ss Implementing post bots
224.Nm
225makes very easy to post messages in a non-interactive manner. This example
226posts a string:
227.Bd -literal -offset indent
228uptime | snac note $SNAC_BASEDIR $SNAC_USER -
229.Ed
230.Pp
231You can setup a line like this from a
232.Xr crontab 5
233or similar. Take note that you need a) command-line access to the same machine
234that hosts the
235.Nm
236instance, and b) write permissions to the storage directories and files.
237.Pp
238You can also post non-interactively using the Mastodon API and a command-line
239http tool like
240.Xr curl 1
241or similar. This has the advantage that you can do it remotely from any host,
242anywhere; the only thing you need is an API Token. This is an example:
243.Bd -literal -offset indent
244curl -X POST https://$SNAC_HOST/api/v1/statuses \\
245--header "Authorization: Bearer ${TOKEN}" -d "status=$(uptime)"
246.Ed
247.Pp
248You can obtain an API Token by connecting to the following URL:
249.Bd -literal -offset indent
250https://$SNAC_HOST/oauth/x-snac-get-token
251.Ed
252.Pp
223.Sh ENVIRONMENT 253.Sh ENVIRONMENT
224.Bl -tag -width Ds 254.Bl -tag -width Ds
225.It Ev DEBUG 255.It Ev DEBUG
diff --git a/examples/docker-entrypoint.sh b/examples/docker-entrypoint.sh
index 639b692..a6216b2 100755
--- a/examples/docker-entrypoint.sh
+++ b/examples/docker-entrypoint.sh
@@ -1,7 +1,7 @@
1#! /bin/sh 1#! /bin/sh
2if [ ! -e /data/data/server.json ] 2if [ ! -e /data/data/server.json ]
3then 3then
4 echo -ne "0.0.0.0\r\n8001\r\nlocalhost\r\n\r\n" | snac init /data/data 4 echo -ne "0.0.0.0\r\n8001\r\nlocalhost\r\n\r\n\r\n" | snac init /data/data
5 snac adduser /data/data testuser 5 snac adduser /data/data testuser
6fi 6fi
7SSLKEYLOGFILE=/data/key snac httpd /data/data 7SSLKEYLOGFILE=/data/key snac httpd /data/data
diff --git a/html.c b/html.c
index 5f57262..8d4203d 100644
--- a/html.c
+++ b/html.c
@@ -79,7 +79,7 @@ xs_str *actor_name(xs_dict *actor)
79} 79}
80 80
81 81
82d_char *html_actor_icon(snac *snac, d_char *os, char *actor, 82xs_str *html_actor_icon(xs_str *os, char *actor,
83 const char *date, const char *udate, const char *url, int priv) 83 const char *date, const char *udate, const char *url, int priv)
84{ 84{
85 xs *s = xs_str_new(NULL); 85 xs *s = xs_str_new(NULL);
@@ -168,7 +168,7 @@ d_char *html_msg_icon(snac *snac, d_char *os, char *msg)
168 date = xs_dict_get(msg, "published"); 168 date = xs_dict_get(msg, "published");
169 udate = xs_dict_get(msg, "updated"); 169 udate = xs_dict_get(msg, "updated");
170 170
171 os = html_actor_icon(snac, os, actor, date, udate, url, priv); 171 os = html_actor_icon(os, actor, date, udate, url, priv);
172 } 172 }
173 173
174 return os; 174 return os;
@@ -983,7 +983,7 @@ d_char *html_entry(snac *snac, d_char *os, char *msg, int local,
983} 983}
984 984
985 985
986d_char *html_user_footer(snac *snac, d_char *s) 986xs_str *html_user_footer(xs_str *s)
987{ 987{
988 xs *s1 = xs_fmt( 988 xs *s1 = xs_fmt(
989 "<div class=\"snac-footer\">\n" 989 "<div class=\"snac-footer\">\n"
@@ -1064,7 +1064,7 @@ d_char *html_timeline(snac *snac, char *list, int local, int skip, int show, int
1064 s = xs_str_cat(s, s1); 1064 s = xs_str_cat(s, s1);
1065 } 1065 }
1066 1066
1067 s = html_user_footer(snac, s); 1067 s = html_user_footer(s);
1068 1068
1069 s = xs_str_cat(s, "</body>\n</html>\n"); 1069 s = xs_str_cat(s, "</body>\n</html>\n");
1070 1070
@@ -1088,8 +1088,7 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade
1088 if (valid_status(actor_get(snac, actor_id, &actor))) { 1088 if (valid_status(actor_get(snac, actor_id, &actor))) {
1089 s = xs_str_cat(s, "<div class=\"snac-post\">\n"); 1089 s = xs_str_cat(s, "<div class=\"snac-post\">\n");
1090 1090
1091 s = html_actor_icon(snac, s, actor, xs_dict_get(actor, "published"), NULL, NULL, 0); 1091 s = html_actor_icon(s, actor, xs_dict_get(actor, "published"), NULL, NULL, 0);
1092
1093 1092
1094 /* content (user bio) */ 1093 /* content (user bio) */
1095 char *c = xs_dict_get(actor, "summary"); 1094 char *c = xs_dict_get(actor, "summary");
@@ -1182,7 +1181,7 @@ d_char *html_people(snac *snac)
1182 1181
1183 s = html_people_list(snac, s, wers, L("People that follows you"), "e"); 1182 s = html_people_list(snac, s, wers, L("People that follows you"), "e");
1184 1183
1185 s = html_user_footer(snac, s); 1184 s = html_user_footer(s);
1186 1185
1187 s = xs_str_cat(s, "</body>\n</html>\n"); 1186 s = xs_str_cat(s, "</body>\n</html>\n");
1188 1187
@@ -1226,7 +1225,7 @@ xs_str *html_notifications(snac *snac)
1226 const char *actor_id = xs_dict_get(noti, "actor"); 1225 const char *actor_id = xs_dict_get(noti, "actor");
1227 xs *actor = NULL; 1226 xs *actor = NULL;
1228 1227
1229 if (!valid_status(object_get(actor_id, &actor))) 1228 if (!valid_status(actor_get(snac, actor_id, &actor)))
1230 continue; 1229 continue;
1231 1230
1232 xs *a_name = actor_name(actor); 1231 xs *a_name = actor_name(actor);
@@ -1277,12 +1276,13 @@ xs_str *html_notifications(snac *snac)
1277 s = xs_str_cat(s, s1); 1276 s = xs_str_cat(s, s1);
1278 } 1277 }
1279 1278
1280 s = html_user_footer(snac, s); 1279 s = html_user_footer(s);
1281 1280
1282 s = xs_str_cat(s, "</body>\n</html>\n"); 1281 s = xs_str_cat(s, "</body>\n</html>\n");
1283 1282
1284 /* set the check time to now */ 1283 /* set the check time to now */
1285 xs *dummy = notify_check_time(snac, 1); 1284 xs *dummy = notify_check_time(snac, 1);
1285 dummy = xs_free(dummy);
1286 1286
1287 timeline_touch(snac); 1287 timeline_touch(snac);
1288 1288
@@ -1290,7 +1290,8 @@ xs_str *html_notifications(snac *snac)
1290} 1290}
1291 1291
1292 1292
1293int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char **ctype) 1293int html_get_handler(const xs_dict *req, const char *q_path,
1294 char **body, int *b_size, char **ctype)
1294{ 1295{
1295 char *accept = xs_dict_get(req, "accept"); 1296 char *accept = xs_dict_get(req, "accept");
1296 int status = 404; 1297 int status = 404;
@@ -1547,9 +1548,13 @@ int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char *
1547} 1548}
1548 1549
1549 1550
1550int html_post_handler(d_char *req, char *q_path, d_char *payload, int p_size, 1551int html_post_handler(const xs_dict *req, const char *q_path,
1552 char *payload, int p_size,
1551 char **body, int *b_size, char **ctype) 1553 char **body, int *b_size, char **ctype)
1552{ 1554{
1555 (void)p_size;
1556 (void)ctype;
1557
1553 int status = 0; 1558 int status = 0;
1554 snac snac; 1559 snac snac;
1555 char *uid, *p_path; 1560 char *uid, *p_path;
diff --git a/http.c b/http.c
index ae46001..58d188c 100644
--- a/http.c
+++ b/http.c
@@ -33,7 +33,7 @@ xs_dict *http_signed_request_raw(const char *keyid, const char *seckey,
33 date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT"); 33 date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT");
34 34
35 { 35 {
36 xs *s = xs_replace(url, "https:/" "/", ""); 36 xs *s = xs_replace_n(url, "https:/" "/", "", 1);
37 l1 = xs_split_n(s, "/", 1); 37 l1 = xs_split_n(s, "/", 1);
38 } 38 }
39 39
diff --git a/httpd.c b/httpd.c
index 70083a1..fe93727 100644
--- a/httpd.c
+++ b/httpd.c
@@ -43,12 +43,14 @@ d_char *nodeinfo_2_0(void)
43} 43}
44 44
45 45
46int server_get_handler(d_char *req, char *q_path, 46int server_get_handler(xs_dict *req, char *q_path,
47 char **body, int *b_size, char **ctype) 47 char **body, int *b_size, char **ctype)
48/* basic server services */ 48/* basic server services */
49{ 49{
50 int status = 0; 50 int status = 0;
51 51
52 (void)req;
53
52 /* is it the server root? */ 54 /* is it the server root? */
53 if (*q_path == '\0') { 55 if (*q_path == '\0') {
54 /* try to open greeting.html */ 56 /* try to open greeting.html */
@@ -285,6 +287,8 @@ static jmp_buf on_break;
285 287
286void term_handler(int s) 288void term_handler(int s)
287{ 289{
290 (void)s;
291
288 longjmp(on_break, 1); 292 longjmp(on_break, 1);
289} 293}
290 294
@@ -401,6 +405,8 @@ static void *background_thread(void *arg)
401{ 405{
402 time_t purge_time; 406 time_t purge_time;
403 407
408 (void)arg;
409
404 /* first purge time */ 410 /* first purge time */
405 purge_time = time(NULL) + 10 * 60; 411 purge_time = time(NULL) + 10 * 60;
406 412
diff --git a/main.c b/main.c
index 6a0bb74..3f34673 100644
--- a/main.c
+++ b/main.c
@@ -30,6 +30,7 @@ int usage(void)
30 printf("actor {basedir} {uid} {url} Requests an actor\n"); 30 printf("actor {basedir} {uid} {url} Requests an actor\n");
31 printf("note {basedir} {uid} {'text'} Sends a note to followers\n"); 31 printf("note {basedir} {uid} {'text'} Sends a note to followers\n");
32 printf("resetpwd {basedir} {uid} Resets the password of a user\n"); 32 printf("resetpwd {basedir} {uid} Resets the password of a user\n");
33 printf("ping {basedir} {uid} {actor} Pings an actor\n");
33 34
34 return 1; 35 return 1;
35} 36}
@@ -228,6 +229,27 @@ int main(int argc, char *argv[])
228 return 0; 229 return 0;
229 } 230 }
230 231
232 if (strcmp(cmd, "ping") == 0) {
233 xs *actor_o = NULL;
234
235 if (valid_status(actor_request(&snac, url, &actor_o))) {
236 xs *msg = msg_ping(&snac, url);
237
238 enqueue_output_by_actor(&snac, msg, url, 0);
239
240 if (dbglevel) {
241 xs *j = xs_json_dumps_pp(msg, 4);
242 printf("%s\n", j);
243 }
244 }
245 else {
246 srv_log(xs_fmt("Error getting actor %s", url));
247 return 1;
248 }
249
250 return 0;
251 }
252
231 if (strcmp(cmd, "request") == 0) { 253 if (strcmp(cmd, "request") == 0) {
232 int status; 254 int status;
233 xs *data = NULL; 255 xs *data = NULL;
diff --git a/mastoapi.c b/mastoapi.c
index 001c0bc..b82ecfa 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -162,7 +162,7 @@ const char *login_page = ""
162"<!DOCTYPE html>\n" 162"<!DOCTYPE html>\n"
163"<body><h1>%s OAuth identify</h1>\n" 163"<body><h1>%s OAuth identify</h1>\n"
164"<div style=\"background-color: red; color: white\">%s</div>\n" 164"<div style=\"background-color: red; color: white\">%s</div>\n"
165"<form method=\"post\" action=\"https:/" "/%s/oauth/x-snac-login\">\n" 165"<form method=\"post\" action=\"https:/" "/%s/%s\">\n"
166"<p>Login: <input type=\"text\" name=\"login\"></p>\n" 166"<p>Login: <input type=\"text\" name=\"login\"></p>\n"
167"<p>Password: <input type=\"password\" name=\"passwd\"></p>\n" 167"<p>Password: <input type=\"password\" name=\"passwd\"></p>\n"
168"<input type=\"hidden\" name=\"redir\" value=\"%s\">\n" 168"<input type=\"hidden\" name=\"redir\" value=\"%s\">\n"
@@ -175,6 +175,8 @@ const char *login_page = ""
175int oauth_get_handler(const xs_dict *req, const char *q_path, 175int oauth_get_handler(const xs_dict *req, const char *q_path,
176 char **body, int *b_size, char **ctype) 176 char **body, int *b_size, char **ctype)
177{ 177{
178 (void)b_size;
179
178 if (!xs_startswith(q_path, "/oauth/")) 180 if (!xs_startswith(q_path, "/oauth/"))
179 return 0; 181 return 0;
180 182
@@ -185,7 +187,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
185 187
186 int status = 404; 188 int status = 404;
187 xs_dict *msg = xs_dict_get(req, "q_vars"); 189 xs_dict *msg = xs_dict_get(req, "q_vars");
188 xs *cmd = xs_replace(q_path, "/oauth", ""); 190 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
189 191
190 srv_debug(1, xs_fmt("oauth_get_handler %s", q_path)); 192 srv_debug(1, xs_fmt("oauth_get_handler %s", q_path));
191 193
@@ -206,17 +208,28 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
206 if (xs_is_null(state)) 208 if (xs_is_null(state))
207 state = ""; 209 state = "";
208 210
209 *body = xs_fmt(login_page, host, "", host, ruri, cid, state, USER_AGENT); 211 *body = xs_fmt(login_page, host, "", host, "oauth/x-snac-login",
212 ruri, cid, state, USER_AGENT);
210 *ctype = "text/html"; 213 *ctype = "text/html";
211 status = 200; 214 status = 200;
212 215
213 srv_debug(0, xs_fmt("oauth authorize: generating login page")); 216 srv_debug(1, xs_fmt("oauth authorize: generating login page"));
214 } 217 }
215 else 218 else
216 srv_debug(0, xs_fmt("oauth authorize: bad client_id %s", cid)); 219 srv_debug(1, xs_fmt("oauth authorize: bad client_id %s", cid));
217 } 220 }
218 else 221 else
219 srv_debug(0, xs_fmt("oauth authorize: invalid or unset arguments")); 222 srv_debug(1, xs_fmt("oauth authorize: invalid or unset arguments"));
223 }
224 else
225 if (strcmp(cmd, "/x-snac-get-token") == 0) {
226 const char *host = xs_dict_get(srv_config, "host");
227
228 *body = xs_fmt(login_page, host, "", host, "oauth/x-snac-get-token",
229 "", "", "", USER_AGENT);
230 *ctype = "text/html";
231 status = 200;
232
220 } 233 }
221 234
222 return status; 235 return status;
@@ -227,6 +240,9 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
227 const char *payload, int p_size, 240 const char *payload, int p_size,
228 char **body, int *b_size, char **ctype) 241 char **body, int *b_size, char **ctype)
229{ 242{
243 (void)p_size;
244 (void)b_size;
245
230 if (!xs_startswith(q_path, "/oauth/")) 246 if (!xs_startswith(q_path, "/oauth/"))
231 return 0; 247 return 0;
232 248
@@ -245,7 +261,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
245 else 261 else
246 args = xs_dup(xs_dict_get(req, "p_vars")); 262 args = xs_dup(xs_dict_get(req, "p_vars"));
247 263
248 xs *cmd = xs_replace(q_path, "/oauth", ""); 264 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
249 265
250 srv_debug(1, xs_fmt("oauth_post_handler %s", q_path)); 266 srv_debug(1, xs_fmt("oauth_post_handler %s", q_path));
251 267
@@ -259,7 +275,8 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
259 const char *host = xs_dict_get(srv_config, "host"); 275 const char *host = xs_dict_get(srv_config, "host");
260 276
261 /* by default, generate another login form with an error */ 277 /* by default, generate another login form with an error */
262 *body = xs_fmt(login_page, host, "LOGIN INCORRECT", host, redir, cid, state, USER_AGENT); 278 *body = xs_fmt(login_page, host, "LOGIN INCORRECT", host, "oauth/x-snac-login",
279 redir, cid, state, USER_AGENT);
263 *ctype = "text/html"; 280 *ctype = "text/html";
264 status = 200; 281 status = 200;
265 282
@@ -268,8 +285,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
268 285
269 if (user_open(&snac, login)) { 286 if (user_open(&snac, login)) {
270 /* check the login + password */ 287 /* check the login + password */
271 if (check_password(login, passwd, 288 if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) {
272 xs_dict_get(snac.config, "passwd"))) {
273 /* success! redirect to the desired uri */ 289 /* success! redirect to the desired uri */
274 xs *code = random_str(); 290 xs *code = random_str();
275 291
@@ -328,7 +344,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
328 const char *auhdr = xs_dict_get(req, "authorization"); 344 const char *auhdr = xs_dict_get(req, "authorization");
329 345
330 if (!xs_is_null(auhdr) && xs_startswith(auhdr, "Basic ")) { 346 if (!xs_is_null(auhdr) && xs_startswith(auhdr, "Basic ")) {
331 xs *s1 = xs_replace(auhdr, "Basic ", ""); 347 xs *s1 = xs_replace_n(auhdr, "Basic ", "", 1);
332 int size; 348 int size;
333 xs *s2 = xs_base64_dec(s1, &size); 349 xs *s2 = xs_base64_dec(s1, &size);
334 350
@@ -373,7 +389,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
373 389
374 const char *uid = xs_dict_get(app, "uid"); 390 const char *uid = xs_dict_get(app, "uid");
375 391
376 srv_debug(0, xs_fmt("oauth token: " 392 srv_debug(1, xs_fmt("oauth token: "
377 "successful login for %s, new token %s", uid, tokid)); 393 "successful login for %s, new token %s", uid, tokid));
378 394
379 xs *token = xs_dict_new(); 395 xs *token = xs_dict_new();
@@ -387,7 +403,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
387 } 403 }
388 } 404 }
389 else { 405 else {
390 srv_debug(0, xs_fmt("oauth token: invalid or unset arguments")); 406 srv_debug(1, xs_fmt("oauth token: invalid or unset arguments"));
391 status = 400; 407 status = 400;
392 } 408 }
393 } 409 }
@@ -409,7 +425,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
409 } 425 }
410 else { 426 else {
411 token_del(tokid); 427 token_del(tokid);
412 srv_debug(0, xs_fmt("oauth revoke: revoked token %s", tokid)); 428 srv_debug(1, xs_fmt("oauth revoke: revoked token %s", tokid));
413 status = 200; 429 status = 200;
414 430
415 /* also delete the app, as it serves no purpose from now on */ 431 /* also delete the app, as it serves no purpose from now on */
@@ -417,10 +433,52 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
417 } 433 }
418 } 434 }
419 else { 435 else {
420 srv_debug(0, xs_fmt("oauth revoke: invalid or unset arguments")); 436 srv_debug(1, xs_fmt("oauth revoke: invalid or unset arguments"));
421 status = 403; 437 status = 403;
422 } 438 }
423 } 439 }
440 if (strcmp(cmd, "/x-snac-get-token") == 0) {
441 const char *login = xs_dict_get(args, "login");
442 const char *passwd = xs_dict_get(args, "passwd");
443
444 const char *host = xs_dict_get(srv_config, "host");
445
446 /* by default, generate another login form with an error */
447 *body = xs_fmt(login_page, host, "LOGIN INCORRECT", host, "oauth/x-snac-get-token",
448 "", "", "", USER_AGENT);
449 *ctype = "text/html";
450 status = 200;
451
452 if (login && passwd) {
453 snac user;
454
455 if (user_open(&user, login)) {
456 /* check the login + password */
457 if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) {
458 /* success! create a new token */
459 xs *tokid = random_str();
460
461 srv_debug(1, xs_fmt("x-snac-new-token: "
462 "successful login for %s, new token %s", login, tokid));
463
464 xs *token = xs_dict_new();
465 token = xs_dict_append(token, "token", tokid);
466 token = xs_dict_append(token, "client_id", "snac-client");
467 token = xs_dict_append(token, "client_secret", "");
468 token = xs_dict_append(token, "uid", login);
469 token = xs_dict_append(token, "code", "");
470
471 token_add(tokid, token);
472
473 *ctype = "text/plain";
474 xs_free(*body);
475 *body = xs_dup(tokid);
476 }
477
478 user_free(&user);
479 }
480 }
481 }
424 482
425 return status; 483 return status;
426} 484}
@@ -537,7 +595,6 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
537 xs *f = xs_val_new(XSTYPE_FALSE); 595 xs *f = xs_val_new(XSTYPE_FALSE);
538 xs *t = xs_val_new(XSTYPE_TRUE); 596 xs *t = xs_val_new(XSTYPE_TRUE);
539 xs *n = xs_val_new(XSTYPE_NULL); 597 xs *n = xs_val_new(XSTYPE_NULL);
540 xs *el = xs_list_new();
541 xs *idx = NULL; 598 xs *idx = NULL;
542 xs *ixc = NULL; 599 xs *ixc = NULL;
543 600
@@ -787,7 +844,7 @@ int process_auth_token(snac *snac, const xs_dict *req)
787 844
788 /* if there is an authorization field, try to validate it */ 845 /* if there is an authorization field, try to validate it */
789 if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) { 846 if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) {
790 xs *tokid = xs_replace(v, "Bearer ", ""); 847 xs *tokid = xs_replace_n(v, "Bearer ", "", 1);
791 xs *token = token_get(tokid); 848 xs *token = token_get(tokid);
792 849
793 if (token != NULL) { 850 if (token != NULL) {
@@ -815,6 +872,8 @@ int process_auth_token(snac *snac, const xs_dict *req)
815int mastoapi_get_handler(const xs_dict *req, const char *q_path, 872int mastoapi_get_handler(const xs_dict *req, const char *q_path,
816 char **body, int *b_size, char **ctype) 873 char **body, int *b_size, char **ctype)
817{ 874{
875 (void)b_size;
876
818 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 877 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
819 return 0; 878 return 0;
820 879
@@ -826,7 +885,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
826 885
827 int status = 404; 886 int status = 404;
828 xs_dict *args = xs_dict_get(req, "q_vars"); 887 xs_dict *args = xs_dict_get(req, "q_vars");
829 xs *cmd = xs_replace(q_path, "/api", ""); 888 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
830 889
831 snac snac1 = {0}; 890 snac snac1 = {0};
832 int logged_in = process_auth_token(&snac1, req); 891 int logged_in = process_auth_token(&snac1, req);
@@ -1036,11 +1095,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1036 } 1095 }
1037 else 1096 else
1038 if (strcmp(cmd, "/v1/timelines/public") == 0) { 1097 if (strcmp(cmd, "/v1/timelines/public") == 0) {
1039 /* the public timeline (public timelines for all users) */ 1098 /* the instance public timeline (public timelines for all users) */
1040 1099
1041 /* this is an ugly kludge: first users in the list get all the fame */ 1100 /* NOTE: this api call needs no authorization; but,
1101 I need a logged-in user in mastoapi_status() for
1102 is_msg_public() and the liked/boosted flags,
1103 so it will silently fail for pure public access */
1042 1104
1043 const char *limit_s = xs_dict_get(args, "limit"); 1105 const char *limit_s = xs_dict_get(args, "limit");
1044 int limit = 0; 1106 int limit = 0;
1045 int cnt = 0; 1107 int cnt = 0;
1046 1108
@@ -1050,44 +1112,28 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1050 if (limit == 0) 1112 if (limit == 0)
1051 limit = 20; 1113 limit = 20;
1052 1114
1053 xs *out = xs_list_new(); 1115 xs *timeline = timeline_instance_list(0, limit);
1054 xs *users = user_list(); 1116 xs *out = xs_list_new();
1055 xs_list *p = users; 1117 xs_list *p = timeline;
1056 xs_str *uid; 1118 xs_str *md5;
1057
1058 while (xs_list_iter(&p, &uid) && cnt < limit) {
1059 snac user;
1060
1061 if (user_open(&user, uid)) {
1062 xs *timeline = timeline_simple_list(&user, "public", 0, 4);
1063 xs_list *p2 = timeline;
1064 xs_str *v;
1065
1066 while (xs_list_iter(&p2, &v) && cnt < limit) {
1067 xs *msg = NULL;
1068 1119
1069 /* get the entry */ 1120 while (logged_in && xs_list_iter(&p, &md5) && cnt < limit) {
1070 if (!valid_status(timeline_get_by_md5(&user, v, &msg))) 1121 xs *msg = NULL;
1071 continue;
1072
1073 /* discard non-Notes */
1074 if (strcmp(xs_dict_get(msg, "type"), "Note") != 0)
1075 continue;
1076 1122
1077 /* discard entries not by this user */ 1123 /* get the entry */
1078 if (!xs_startswith(xs_dict_get(msg, "id"), user.actor)) 1124 if (!valid_status(object_get_by_md5(md5, &msg)))
1079 continue; 1125 continue;
1080 1126
1081 /* convert the Note into a Mastodon status */ 1127 /* discard non-Notes */
1082 xs *st = mastoapi_status(&user, msg); 1128 if (strcmp(xs_dict_get(msg, "type"), "Note") != 0)
1129 continue;
1083 1130
1084 if (st != NULL) { 1131 /* convert the Note into a Mastodon status */
1085 out = xs_list_append(out, st); 1132 xs *st = mastoapi_status(&snac1, msg);
1086 cnt++;
1087 }
1088 }
1089 1133
1090 user_free(&user); 1134 if (st != NULL) {
1135 out = xs_list_append(out, st);
1136 cnt++;
1091 } 1137 }
1092 } 1138 }
1093 1139
@@ -1121,7 +1167,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1121 xs *actor = NULL; 1167 xs *actor = NULL;
1122 xs *entry = NULL; 1168 xs *entry = NULL;
1123 1169
1124 if (!valid_status(object_get(xs_dict_get(noti, "actor"), &actor))) 1170 if (!valid_status(actor_get(&snac1, xs_dict_get(noti, "actor"), &actor)))
1125 continue; 1171 continue;
1126 1172
1127 if (objid != NULL && !valid_status(object_get(objid, &entry))) 1173 if (objid != NULL && !valid_status(object_get(objid, &entry)))
@@ -1287,25 +1333,6 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1287 cfg = xs_dict_append(cfg, "statuses", d11); 1333 cfg = xs_dict_append(cfg, "statuses", d11);
1288 } 1334 }
1289 1335
1290 {
1291 xs *d11 = xs_dict_new();
1292 xs *mt = xs_list_new();
1293
1294 mt = xs_list_append(mt, "image/jpeg");
1295 mt = xs_list_append(mt, "image/png");
1296 mt = xs_list_append(mt, "image/gif");
1297
1298 d11 = xs_dict_append(d11, "supported_mime_types", mt);
1299
1300 d11 = xs_dict_append(d11, "image_size_limit", z);
1301 d11 = xs_dict_append(d11, "image_matrix_limit", z);
1302 d11 = xs_dict_append(d11, "video_size_limit", z);
1303 d11 = xs_dict_append(d11, "video_matrix_limit", z);
1304 d11 = xs_dict_append(d11, "video_frame_rate_limit", z);
1305
1306 cfg = xs_dict_append(cfg, "media_attachments", d11);
1307 }
1308
1309 ins = xs_dict_append(ins, "configuration", cfg); 1336 ins = xs_dict_append(ins, "configuration", cfg);
1310 1337
1311 *body = xs_json_dumps_pp(ins, 4); 1338 *body = xs_json_dumps_pp(ins, 4);
@@ -1326,7 +1353,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1326 /* skip the 'fake' part of the id */ 1353 /* skip the 'fake' part of the id */
1327 id = MID_TO_MD5(id); 1354 id = MID_TO_MD5(id);
1328 1355
1329 if (valid_status(timeline_get_by_md5(&snac1, id, &msg))) { 1356 if (valid_status(object_get_by_md5(id, &msg))) {
1330 if (op == NULL) { 1357 if (op == NULL) {
1331 if (!is_muted(&snac1, xs_dict_get(msg, "attributedTo"))) { 1358 if (!is_muted(&snac1, xs_dict_get(msg, "attributedTo"))) {
1332 /* return the status itself */ 1359 /* return the status itself */
@@ -1487,6 +1514,9 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
1487 const char *payload, int p_size, 1514 const char *payload, int p_size,
1488 char **body, int *b_size, char **ctype) 1515 char **body, int *b_size, char **ctype)
1489{ 1516{
1517 (void)p_size;
1518 (void)b_size;
1519
1490 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 1520 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
1491 return 0; 1521 return 0;
1492 1522
@@ -1513,7 +1543,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
1513 printf("%s\n", j); 1543 printf("%s\n", j);
1514 }*/ 1544 }*/
1515 1545
1516 xs *cmd = xs_replace(q_path, "/api", ""); 1546 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
1517 1547
1518 snac snac = {0}; 1548 snac snac = {0};
1519 int logged_in = process_auth_token(&snac, req); 1549 int logged_in = process_auth_token(&snac, req);
@@ -1562,7 +1592,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
1562 1592
1563 app_add(cid, app); 1593 app_add(cid, app);
1564 1594
1565 srv_debug(0, xs_fmt("mastoapi apps: new app %s", cid)); 1595 srv_debug(1, xs_fmt("mastoapi apps: new app %s", cid));
1566 } 1596 }
1567 } 1597 }
1568 else 1598 else
@@ -1582,6 +1612,9 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
1582 if (xs_is_null(media_ids)) 1612 if (xs_is_null(media_ids))
1583 media_ids = xs_dict_get(args, "media_ids[]"); 1613 media_ids = xs_dict_get(args, "media_ids[]");
1584 1614
1615 if (xs_is_null(visibility))
1616 visibility = "public";
1617
1585 xs *attach_list = xs_list_new(); 1618 xs *attach_list = xs_list_new();
1586 xs *irt = NULL; 1619 xs *irt = NULL;
1587 1620
@@ -1683,7 +1716,11 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
1683 } 1716 }
1684 else 1717 else
1685 if (strcmp(op, "unfavourite") == 0) { 1718 if (strcmp(op, "unfavourite") == 0) {
1686 /* snac does not support Undo+Like */ 1719 /* partial support: as the original Like message
1720 is not stored anywhere here, it's not possible
1721 to send an Undo + Like; the only thing done here
1722 is to delete the actor from the list of likes */
1723 object_unadmire(id, snac.actor, 1);
1687 } 1724 }
1688 else 1725 else
1689 if (strcmp(op, "reblog") == 0) { 1726 if (strcmp(op, "reblog") == 0) {
@@ -1698,7 +1735,8 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
1698 } 1735 }
1699 else 1736 else
1700 if (strcmp(op, "unreblog") == 0) { 1737 if (strcmp(op, "unreblog") == 0) {
1701 /* snac does not support Undo+Announce */ 1738 /* partial support: see comment in 'unfavourite' */
1739 object_unadmire(id, snac.actor, 0);
1702 } 1740 }
1703 else 1741 else
1704 if (strcmp(op, "bookmark") == 0) { 1742 if (strcmp(op, "bookmark") == 0) {
@@ -1903,6 +1941,9 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
1903 const char *payload, int p_size, 1941 const char *payload, int p_size,
1904 char **body, int *b_size, char **ctype) 1942 char **body, int *b_size, char **ctype)
1905{ 1943{
1944 (void)p_size;
1945 (void)b_size;
1946
1906 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 1947 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
1907 return 0; 1948 return 0;
1908 1949
@@ -1924,7 +1965,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
1924 if (args == NULL) 1965 if (args == NULL)
1925 return 400; 1966 return 400;
1926 1967
1927 xs *cmd = xs_replace(q_path, "/api", ""); 1968 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
1928 1969
1929 snac snac = {0}; 1970 snac snac = {0};
1930 int logged_in = process_auth_token(&snac, req); 1971 int logged_in = process_auth_token(&snac, req);
diff --git a/snac.h b/snac.h
index fe7adfb..782ae51 100644
--- a/snac.h
+++ b/snac.h
@@ -1,7 +1,7 @@
1/* snac - A simple, minimalistic ActivityPub instance */ 1/* snac - A simple, minimalistic ActivityPub instance */
2/* copyright (c) 2022 - 2023 grunfink / MIT license */ 2/* copyright (c) 2022 - 2023 grunfink / MIT license */
3 3
4#define VERSION "2.29" 4#define VERSION "2.30"
5 5
6#define USER_AGENT "snac/" VERSION 6#define USER_AGENT "snac/" VERSION
7 7
@@ -83,6 +83,7 @@ int object_del_if_unref(const char *id);
83double object_ctime_by_md5(const char *md5); 83double object_ctime_by_md5(const char *md5);
84double object_ctime(const char *id); 84double object_ctime(const char *id);
85int object_admire(const char *id, const char *actor, int like); 85int object_admire(const char *id, const char *actor, int like);
86int object_unadmire(const char *id, const char *actor, int like);
86 87
87int object_likes_len(const char *id); 88int object_likes_len(const char *id);
88int object_announces_len(const char *id); 89int object_announces_len(const char *id);
@@ -105,14 +106,14 @@ int timeline_touch(snac *snac);
105int timeline_here(snac *snac, const char *md5); 106int timeline_here(snac *snac, const char *md5);
106int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg); 107int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg);
107int timeline_del(snac *snac, char *id); 108int timeline_del(snac *snac, char *id);
108d_char *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show); 109xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show);
109d_char *timeline_list(snac *snac, const char *idx_name, int skip, int show); 110xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show);
110int timeline_add(snac *snac, char *id, char *o_msg); 111int timeline_add(snac *snac, char *id, char *o_msg);
111void timeline_admire(snac *snac, char *id, char *admirer, int like); 112void timeline_admire(snac *snac, char *id, char *admirer, int like);
112 113
113xs_list *timeline_top_level(snac *snac, xs_list *list); 114xs_list *timeline_top_level(snac *snac, xs_list *list);
114 115xs_list *local_list(snac *snac, int max);
115d_char *local_list(snac *snac, int max); 116xs_list *timeline_instance_list(int skip, int show);
116 117
117int following_add(snac *snac, const char *actor, const xs_dict *msg); 118int following_add(snac *snac, const char *actor, const xs_dict *msg);
118int following_del(snac *snac, const char *actor); 119int following_del(snac *snac, const char *actor);
@@ -127,8 +128,8 @@ int is_muted(snac *snac, const char *actor);
127void hide(snac *snac, const char *id); 128void hide(snac *snac, const char *id);
128int is_hidden(snac *snac, const char *id); 129int is_hidden(snac *snac, const char *id);
129 130
130int actor_add(snac *snac, const char *actor, d_char *msg); 131int actor_add(const char *actor, xs_dict *msg);
131int actor_get(snac *snac, const char *actor, d_char **data); 132int actor_get(snac *snac, const char *actor, xs_dict **data);
132 133
133int static_get(snac *snac, const char *id, d_char **data, int *size); 134int static_get(snac *snac, const char *id, d_char **data, int *size);
134void static_put(snac *snac, const char *id, const char *data, int size); 135void static_put(snac *snac, const char *id, const char *data, int size);
@@ -154,7 +155,7 @@ void inbox_add(const char *inbox);
154void inbox_add_by_actor(const xs_dict *actor); 155void inbox_add_by_actor(const xs_dict *actor);
155xs_list *inbox_list(void); 156xs_list *inbox_list(void);
156 157
157void enqueue_input(snac *snac, xs_dict *msg, xs_dict *req, int retries); 158void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries);
158void enqueue_output_raw(const char *keyid, const char *seckey, 159void enqueue_output_raw(const char *keyid, const char *seckey,
159 xs_dict *msg, xs_str *inbox, int retries); 160 xs_dict *msg, xs_str *inbox, int retries);
160void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries); 161void enqueue_output(snac *snac, xs_dict *msg, xs_str *inbox, int retries);
@@ -186,7 +187,7 @@ int check_signature(snac *snac, xs_dict *req, xs_str **err);
186void httpd(void); 187void httpd(void);
187 188
188int webfinger_request(const char *qs, char **actor, char **user); 189int webfinger_request(const char *qs, char **actor, char **user);
189int webfinger_get_handler(d_char *req, char *q_path, 190int webfinger_get_handler(xs_dict *req, char *q_path,
190 char **body, int *b_size, char **ctype); 191 char **body, int *b_size, char **ctype);
191 192
192const char *default_avatar_base64(void); 193const char *default_avatar_base64(void);
@@ -202,6 +203,8 @@ d_char *msg_undo(snac *snac, char *object);
202d_char *msg_delete(snac *snac, char *id); 203d_char *msg_delete(snac *snac, char *id);
203d_char *msg_actor(snac *snac); 204d_char *msg_actor(snac *snac);
204xs_dict *msg_update(snac *snac, xs_dict *object); 205xs_dict *msg_update(snac *snac, xs_dict *object);
206xs_dict *msg_ping(snac *user, const char *rcpt);
207xs_dict *msg_pong(snac *user, const char *rcpt, const char *object);
205 208
206int activitypub_request(snac *snac, const char *url, xs_dict **data); 209int activitypub_request(snac *snac, const char *url, xs_dict **data);
207int actor_request(snac *snac, const char *actor, xs_dict **data); 210int actor_request(snac *snac, const char *actor, xs_dict **data);
@@ -219,17 +222,19 @@ int process_user_queue(snac *snac);
219void process_queue_item(xs_dict *q_item); 222void process_queue_item(xs_dict *q_item);
220int process_queue(void); 223int process_queue(void);
221 224
222int activitypub_get_handler(d_char *req, char *q_path, 225int activitypub_get_handler(const xs_dict *req, const char *q_path,
223 char **body, int *b_size, char **ctype); 226 char **body, int *b_size, char **ctype);
224int activitypub_post_handler(d_char *req, char *q_path, 227int activitypub_post_handler(const xs_dict *req, const char *q_path,
225 char *payload, int p_size, 228 char *payload, int p_size,
226 char **body, int *b_size, char **ctype); 229 char **body, int *b_size, char **ctype);
227 230
228d_char *not_really_markdown(const char *content); 231d_char *not_really_markdown(const char *content);
229d_char *sanitize(const char *str); 232d_char *sanitize(const char *str);
230 233
231int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char **ctype); 234int html_get_handler(const xs_dict *req, const char *q_path,
232int html_post_handler(d_char *req, char *q_path, d_char *payload, int p_size, 235 char **body, int *b_size, char **ctype);
236int html_post_handler(const xs_dict *req, const char *q_path,
237 char *payload, int p_size,
233 char **body, int *b_size, char **ctype); 238 char **body, int *b_size, char **ctype);
234 239
235int snac_init(const char *_basedir); 240int snac_init(const char *_basedir);
diff --git a/webfinger.c b/webfinger.c
index 2f11516..c7b73f7 100644
--- a/webfinger.c
+++ b/webfinger.c
@@ -21,7 +21,7 @@ int webfinger_request(const char *qs, char **actor, char **user)
21 21
22 if (xs_startswith(qs, "https:/" "/")) { 22 if (xs_startswith(qs, "https:/" "/")) {
23 /* actor query: pick the host */ 23 /* actor query: pick the host */
24 xs *s = xs_replace(qs, "https:/" "/", ""); 24 xs *s = xs_replace_n(qs, "https:/" "/", "", 1);
25 25
26 l = xs_split_n(s, "/", 1); 26 l = xs_split_n(s, "/", 1);
27 27
@@ -74,7 +74,7 @@ int webfinger_request(const char *qs, char **actor, char **user)
74 char *subject = xs_dict_get(obj, "subject"); 74 char *subject = xs_dict_get(obj, "subject");
75 75
76 if (subject) 76 if (subject)
77 *user = xs_replace(subject, "acct:", ""); 77 *user = xs_replace_n(subject, "acct:", "", 1);
78 } 78 }
79 79
80 if (actor != NULL) { 80 if (actor != NULL) {
@@ -105,6 +105,8 @@ int webfinger_get_handler(d_char *req, char *q_path,
105{ 105{
106 int status; 106 int status;
107 107
108 (void)b_size;
109
108 if (strcmp(q_path, "/.well-known/webfinger") != 0) 110 if (strcmp(q_path, "/.well-known/webfinger") != 0)
109 return 0; 111 return 0;
110 112
@@ -137,7 +139,7 @@ int webfinger_get_handler(d_char *req, char *q_path,
137 else 139 else
138 if (xs_startswith(resource, "acct:")) { 140 if (xs_startswith(resource, "acct:")) {
139 /* it's an account name */ 141 /* it's an account name */
140 xs *an = xs_replace(resource, "acct:", ""); 142 xs *an = xs_replace_n(resource, "acct:", "", 1);
141 xs *l = NULL; 143 xs *l = NULL;
142 144
143 /* strip a possible leading @ */ 145 /* strip a possible leading @ */
diff --git a/xs.h b/xs.h
index fef91b7..c4c961b 100644
--- a/xs.h
+++ b/xs.h
@@ -65,8 +65,10 @@ xs_str *xs_str_new(const char *str);
65xs_str *xs_str_wrap_i(const char *prefix, xs_str *str, const char *suffix); 65xs_str *xs_str_wrap_i(const char *prefix, xs_str *str, const char *suffix);
66#define xs_str_prepend_i(str, prefix) xs_str_wrap_i(prefix, str, NULL) 66#define xs_str_prepend_i(str, prefix) xs_str_wrap_i(prefix, str, NULL)
67#define xs_str_cat(str, suffix) xs_str_wrap_i(NULL, str, suffix) 67#define xs_str_cat(str, suffix) xs_str_wrap_i(NULL, str, suffix)
68xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto); 68xs_str *xs_replace_in(xs_str *str, const char *sfrom, const char *sto, int times);
69#define xs_replace(str, sfrom, sto) xs_replace_i(xs_dup(str), sfrom, sto) 69#define xs_replace_i(str, sfrom, sto) xs_replace_in(str, sfrom, sto, XS_ALL)
70#define xs_replace(str, sfrom, sto) xs_replace_in(xs_dup(str), sfrom, sto, XS_ALL)
71#define xs_replace_n(str, sfrom, sto, times) xs_replace_in(xs_dup(str), sfrom, sto, times)
70xs_str *xs_fmt(const char *fmt, ...); 72xs_str *xs_fmt(const char *fmt, ...);
71int xs_str_in(const char *haystack, const char *needle); 73int xs_str_in(const char *haystack, const char *needle);
72int _xs_startsorends(const char *str, const char *xfix, int ends); 74int _xs_startsorends(const char *str, const char *xfix, int ends);
@@ -416,7 +418,7 @@ xs_str *xs_str_wrap_i(const char *prefix, xs_str *str, const char *suffix)
416} 418}
417 419
418 420
419xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto) 421xs_str *xs_replace_in(xs_str *str, const char *sfrom, const char *sto, int times)
420/* replaces inline all sfrom with sto */ 422/* replaces inline all sfrom with sto */
421{ 423{
422 XS_ASSERT_TYPE(str, XSTYPE_STRING); 424 XS_ASSERT_TYPE(str, XSTYPE_STRING);
@@ -426,7 +428,7 @@ xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto)
426 char *ss; 428 char *ss;
427 int offset = 0; 429 int offset = 0;
428 430
429 while ((ss = strstr(str + offset, sfrom)) != NULL) { 431 while (times > 0 && (ss = strstr(str + offset, sfrom)) != NULL) {
430 int n_offset = ss - str; 432 int n_offset = ss - str;
431 433
432 str = xs_collapse(str, n_offset, sfsz); 434 str = xs_collapse(str, n_offset, sfsz);
@@ -434,6 +436,8 @@ xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto)
434 memcpy(str + n_offset, sto, stsz); 436 memcpy(str + n_offset, sto, stsz);
435 437
436 offset = n_offset + stsz; 438 offset = n_offset + stsz;
439
440 times--;
437 } 441 }
438 442
439 return str; 443 return str;
diff --git a/xs_encdec.h b/xs_encdec.h
index 12f40ef..2502520 100644
--- a/xs_encdec.h
+++ b/xs_encdec.h
@@ -7,13 +7,20 @@
7 xs_str *xs_hex_enc(const xs_val *data, int size); 7 xs_str *xs_hex_enc(const xs_val *data, int size);
8 xs_val *xs_hex_dec(const xs_str *hex, int *size); 8 xs_val *xs_hex_dec(const xs_str *hex, int *size);
9 int xs_is_hex(const char *str); 9 int xs_is_hex(const char *str);
10 xs_str *xs_base32_enc(const xs_val *data, int sz);
11 xs_str *xs_base32hex_enc(const xs_val *data, int sz);
12 xs_val *xs_base32_dec(const xs_str *data, int *size);
13 xs_val *xs_base32hex_dec(const xs_str *data, int *size);
10 xs_str *xs_base64_enc(const xs_val *data, int sz); 14 xs_str *xs_base64_enc(const xs_val *data, int sz);
11 xs_val *xs_base64_dec(const xs_str *data, int *size); 15 xs_val *xs_base64_dec(const xs_str *data, int *size);
16 int xs_is_base64(const char *str);
12 xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint); 17 xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint);
13 18
14 19
15#ifdef XS_IMPLEMENTATION 20#ifdef XS_IMPLEMENTATION
16 21
22/** hex **/
23
17xs_str *xs_hex_enc(const xs_val *data, int size) 24xs_str *xs_hex_enc(const xs_val *data, int size)
18/* returns an hexdump of data */ 25/* returns an hexdump of data */
19{ 26{
@@ -78,16 +85,178 @@ int xs_is_hex(const char *str)
78} 85}
79 86
80 87
81xs_str *xs_base64_enc(const xs_val *data, int sz) 88/** base32 */
82/* encodes data to base64 */ 89
90static char *xs_b32_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
91 "234567=";
92
93static char *xs_b32hex_tbl = "0123456789"
94 "ABCDEFGHIJKLMNOPQRSTUV=";
95
96/*
97 00000|00011|11111|12222|22223|33333|33444|44444
98*/
99
100xs_str *xs_base32_enc_tbl(const xs_val *data, int sz, const char *b32_tbl)
101/* encodes data to base32 using a table */
102{
103 xs_str *s = xs_str_new(NULL);
104 unsigned char *p;
105 int n;
106
107 p = (unsigned char *)data;
108
109 for (n = 0; n < sz; n += 5) {
110 int l = sz - n;
111 char enc[9] = "========";
112
113 enc[0] = b32_tbl[(p[n] >> 3) & 0x1f];
114
115 if (l > 1) {
116 enc[1] = b32_tbl[(p[n] << 2 | p[n + 1] >> 6) & 0x1f];
117 enc[2] = b32_tbl[(p[n + 1] >> 1) & 0x1f];
118
119 if (l > 2) {
120 enc[3] = b32_tbl[(p[n + 1] << 4 | p[n + 2] >> 4) & 0x1f];
121
122 if (l > 3) {
123 enc[4] = b32_tbl[(p[n + 2] << 1 | p[n + 3] >> 7) & 0x1f];
124 enc[5] = b32_tbl[(p[n + 3] >> 2) & 0x1f];
125
126 if (l > 4) {
127 enc[6] = b32_tbl[(p[n + 3] << 3 | p[n + 4] >> 5) & 0x1f];
128 enc[7] = b32_tbl[(p[n + 4]) & 0x1f];
129 }
130 else
131 enc[6] = b32_tbl[(p[n + 3] << 3) & 0x1f];
132 }
133 else
134 enc[4] = b32_tbl[(p[n + 2] << 1) & 0x1f];
135 }
136 else
137 enc[3] = b32_tbl[(p[n + 1] << 4) & 0x1f];
138 }
139 else
140 enc[1] = b32_tbl[(p[n] << 2) & 0x1f];
141
142 s = xs_str_cat(s, enc);
143 }
144
145 return s;
146}
147
148
149xs_str *xs_base32_enc(const xs_val *data, int sz)
150/* encodes data to base32 */
151{
152 return xs_base32_enc_tbl(data, sz, xs_b32_tbl);
153}
154
155
156xs_str *xs_base32hex_enc(const xs_val *data, int sz)
157/* encodes data to base32 with HEX alphabet (RFC4648) */
158{
159 return xs_base32_enc_tbl(data, sz, xs_b32hex_tbl);
160}
161
162
163xs_val *xs_base32_dec_tbl(const xs_str *data, int *size, const char *b32_tbl)
164/* decodes data from base32 using a table */
165{
166 xs_val *s = NULL;
167 int sz = 0;
168 char *p;
169
170 p = (char *)data;
171
172 /* size of data must be a multiple of 8 */
173 if (strlen(p) % 8)
174 return NULL;
175
176 for (p = (char *)data; *p; p += 8) {
177 int cs[8];
178 int n;
179 unsigned char tmp[5];
180
181 for (n = 0; n < 8; n++) {
182 char *ss = strchr(b32_tbl, p[n]);
183
184 if (ss == NULL) {
185 /* not a base32 char */
186 return xs_free(s);
187 }
188
189 cs[n] = ss - b32_tbl;
190 }
191
192 n = 0;
193
194 /* #0 byte */
195 tmp[n++] = cs[0] << 3 | cs[1] >> 2;
196
197 if (cs[2] != 32) {
198 /* #1 byte */
199 tmp[n++] = (cs[1] & 0x3) << 6 | cs[2] << 1 | (cs[3] & 0x10) >> 4;
200
201 if (cs[4] != 32) {
202 /* #2 byte */
203 tmp[n++] = (cs[3] & 0xf) << 4 | cs[4] >> 1;
204
205 if (cs[5] != 32) {
206 /* #3 byte */
207 tmp[n++] = (cs[4] & 0x1) << 7 | cs[5] << 2 | cs[6] >> 3;
208
209 if (cs[7] != 32) {
210 /* #4 byte */
211 tmp[n++] = (cs[6] & 0x7) << 5 | cs[7];
212 }
213 }
214 }
215 }
216
217 /* must be done manually because data can be pure binary */
218 s = xs_realloc(s, _xs_blk_size(sz + n));
219 memcpy(s + sz, tmp, n);
220 sz += n;
221 }
222
223 /* asciiz it to use it as a string */
224 s = xs_realloc(s, _xs_blk_size(sz + 1));
225 s[sz] = '\0';
226
227 *size = sz;
228
229 return s;
230}
231
232
233xs_val *xs_base32_dec(const xs_str *data, int *size)
234/* decodes data from base32 */
235{
236 return xs_base32_dec_tbl(data, size, xs_b32_tbl);
237}
238
239
240xs_val *xs_base32hex_dec(const xs_str *data, int *size)
241/* decodes data from base32 with HEX alphabet (RFC4648) */
242{
243 return xs_base32_dec_tbl(data, size, xs_b32hex_tbl);
244}
245
246
247/** base64 */
248
249static char *xs_b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
250 "abcdefghijklmnopqrstuvwxyz"
251 "0123456789+/=";
252
253xs_str *xs_base64_enc_tbl(const xs_val *data, int sz, const char *b64_tbl)
254/* encodes data to base64 using a table */
83{ 255{
84 xs_str *s; 256 xs_str *s;
85 unsigned char *p; 257 unsigned char *p;
86 char *i; 258 char *i;
87 int bsz, n; 259 int bsz, n;
88 static char *b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
89 "abcdefghijklmnopqrstuvwxyz"
90 "0123456789+/";
91 260
92 bsz = ((sz + 3 - 1) / 3) * 4; 261 bsz = ((sz + 3 - 1) / 3) * 4;
93 i = s = xs_realloc(NULL, _xs_blk_size(bsz + 1)); 262 i = s = xs_realloc(NULL, _xs_blk_size(bsz + 1));
@@ -123,15 +292,19 @@ xs_str *xs_base64_enc(const xs_val *data, int sz)
123} 292}
124 293
125 294
126xs_val *xs_base64_dec(const xs_str *data, int *size) 295xs_str *xs_base64_enc(const xs_val *data, int sz)
127/* decodes data from base64 */ 296/* encodes data to base64 */
297{
298 return xs_base64_enc_tbl(data, sz, xs_b64_tbl);
299}
300
301
302xs_val *xs_base64_dec_tbl(const xs_str *data, int *size, const char *b64_tbl)
303/* decodes data from base64 using a table */
128{ 304{
129 xs_val *s = NULL; 305 xs_val *s = NULL;
130 int sz = 0; 306 int sz = 0;
131 char *p; 307 char *p;
132 static char *b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
133 "abcdefghijklmnopqrstuvwxyz"
134 "0123456789+/=";
135 308
136 p = (char *)data; 309 p = (char *)data;
137 310
@@ -184,6 +357,34 @@ xs_val *xs_base64_dec(const xs_str *data, int *size)
184} 357}
185 358
186 359
360xs_val *xs_base64_dec(const xs_str *data, int *size)
361/* decodes data from base64 */
362{
363 return xs_base64_dec_tbl(data, size, xs_b64_tbl);
364}
365
366
367int xs_is_base64_tbl(const char *str, const char *b64_tbl)
368/* returns 1 if str is a base64 string, with table */
369{
370 while (*str) {
371 if (strchr(b64_tbl, *str++) == NULL)
372 return 0;
373 }
374
375 return 1;
376}
377
378
379int xs_is_base64(const char *str)
380/* returns 1 if str is a base64 string */
381{
382 return xs_is_base64_tbl(str, xs_b64_tbl);
383}
384
385
386/** utf-8 **/
387
187xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint) 388xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint)
188/* encodes an Unicode codepoint to utf8 */ 389/* encodes an Unicode codepoint to utf8 */
189{ 390{
diff --git a/xs_version.h b/xs_version.h
index 1d59f5e..6117ce9 100644
--- a/xs_version.h
+++ b/xs_version.h
@@ -1 +1 @@
/* a885c7cc4c8e6384ae23125ed05f434471ccc6fb */ /* dfdd729248d7169b80cb6a7462fe6c0ba6efeb16 */