mirror of
https://codeberg.org/grunfink/snac2.git
synced 2024-12-25 16:53:37 +00:00
Merge pull request 'master' (#1) from grunfink/snac2:master into master
Reviewed-on: https://codeberg.org/poesty/snac2/pulls/1
This commit is contained in:
commit
e65760a349
16 changed files with 610 additions and 147 deletions
4
Makefile
4
Makefile
|
@ -1,6 +1,6 @@
|
|||
PREFIX=/usr/local
|
||||
PREFIX_MAN=$(PREFIX)/man
|
||||
CFLAGS?=-g -Wall
|
||||
CFLAGS?=-g -Wall -Wextra
|
||||
|
||||
all: snac
|
||||
|
||||
|
@ -40,7 +40,7 @@ httpd.o: httpd.c xs.h xs_io.h xs_encdec.h xs_json.h xs_socket.h \
|
|||
xs_httpd.h xs_mime.h snac.h
|
||||
main.o: main.c xs.h xs_io.h xs_encdec.h xs_json.h snac.h
|
||||
mastoapi.o: mastoapi.c xs.h xs_encdec.h xs_openssl.h xs_json.h xs_io.h \
|
||||
xs_time.h snac.h
|
||||
xs_time.h xs_glob.h snac.h
|
||||
snac.o: snac.c xs.h xs_io.h xs_encdec.h xs_json.h xs_curl.h xs_openssl.h \
|
||||
xs_socket.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h xs_glob.h \
|
||||
snac.h
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
# Release Notes
|
||||
|
||||
## 2.30
|
||||
|
||||
Fixed a bug that made some notifications to be missed.
|
||||
|
||||
New 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.
|
||||
|
||||
The 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).
|
||||
|
||||
The build process now includes the `-Wextra` flag.
|
||||
|
||||
## 2.29
|
||||
|
||||
New 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).
|
||||
|
|
|
@ -109,7 +109,7 @@ int actor_request(snac *snac, const char *actor, xs_dict **data)
|
|||
|
||||
if (valid_status(status2)) {
|
||||
/* renew data */
|
||||
status = actor_add(snac, actor, payload);
|
||||
status = actor_add(actor, payload);
|
||||
|
||||
if (data != NULL) {
|
||||
*data = payload;
|
||||
|
@ -437,7 +437,8 @@ void process_tags(snac *snac, const char *content, d_char **n_content, d_char **
|
|||
|
||||
/** messages **/
|
||||
|
||||
d_char *msg_base(snac *snac, char *type, char *id, char *actor, char *date, char *object)
|
||||
xs_dict *msg_base(snac *snac, const char *type, const char *id,
|
||||
const char *actor, const char *date, const char *object)
|
||||
/* creates a base ActivityPub message */
|
||||
{
|
||||
xs *did = NULL;
|
||||
|
@ -467,7 +468,7 @@ d_char *msg_base(snac *snac, char *type, char *id, char *actor, char *date, char
|
|||
}
|
||||
}
|
||||
|
||||
d_char *msg = xs_dict_new();
|
||||
xs_dict *msg = xs_dict_new();
|
||||
|
||||
msg = xs_dict_append(msg, "@context", "https:/" "/www.w3.org/ns/activitystreams");
|
||||
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,
|
|||
}
|
||||
|
||||
|
||||
xs_dict *msg_ping(snac *user, const char *rcpt)
|
||||
/* creates a Ping message (https://humungus.tedunangst.com/r/honk/v/tip/f/docs/ping.txt) */
|
||||
{
|
||||
xs_dict *msg = msg_base(user, "Ping", "@dummy", user->actor, NULL, NULL);
|
||||
|
||||
msg = xs_dict_append(msg, "to", rcpt);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
xs_dict *msg_pong(snac *user, const char *rcpt, const char *object)
|
||||
/* creates a Pong message (https://humungus.tedunangst.com/r/honk/v/tip/f/docs/ping.txt) */
|
||||
{
|
||||
xs_dict *msg = msg_base(user, "Pong", "@dummy", user->actor, NULL, object);
|
||||
|
||||
msg = xs_dict_append(msg, "to", rcpt);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
void notify(snac *snac, xs_str *type, xs_str *utype, xs_str *actor, xs_dict *msg)
|
||||
/* notifies the user of relevant events */
|
||||
{
|
||||
|
@ -1121,7 +1144,7 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
|
|||
else
|
||||
if (strcmp(type, "Update") == 0) {
|
||||
if (strcmp(utype, "Person") == 0) {
|
||||
actor_add(snac, actor, xs_dict_get(msg, "object"));
|
||||
actor_add(actor, xs_dict_get(msg, "object"));
|
||||
|
||||
snac_log(snac, xs_fmt("updated actor %s", actor));
|
||||
}
|
||||
|
@ -1147,7 +1170,19 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
|
|||
snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object));
|
||||
}
|
||||
else
|
||||
snac_debug(snac, 1, xs_fmt("process_message type '%s' ignored", type));
|
||||
if (strcmp(type, "Pong") == 0) {
|
||||
snac_log(snac, xs_fmt("'Pong' received from %s", actor));
|
||||
}
|
||||
else
|
||||
if (strcmp(type, "Ping") == 0) {
|
||||
snac_log(snac, xs_fmt("'Ping' requested from %s", actor));
|
||||
|
||||
xs *rsp = msg_pong(snac, actor, xs_dict_get(msg, "id"));
|
||||
|
||||
enqueue_output_by_actor(snac, rsp, actor, 0);
|
||||
}
|
||||
else
|
||||
snac_debug(snac, 1, xs_fmt("process_input_message type '%s' ignored", type));
|
||||
|
||||
if (do_notify) {
|
||||
notify(snac, type, utype, actor, msg);
|
||||
|
@ -1384,8 +1419,9 @@ void process_queue_item(xs_dict *q_item)
|
|||
xs *headers = xs_dict_new();
|
||||
headers = xs_dict_append(headers, "content-type", "application/json");
|
||||
|
||||
xs *rsp = xs_http_request("POST", url, headers,
|
||||
xs *rsp = xs_http_request("POST", url, headers,
|
||||
body, strlen(body), &status, NULL, NULL, 0);
|
||||
rsp = xs_free(rsp);
|
||||
|
||||
srv_debug(0, xs_fmt("telegram post %d", status));
|
||||
}
|
||||
|
@ -1426,7 +1462,7 @@ int process_queue(void)
|
|||
|
||||
/** HTTP handlers */
|
||||
|
||||
int activitypub_get_handler(d_char *req, char *q_path,
|
||||
int activitypub_get_handler(const xs_dict *req, const char *q_path,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
int status = 200;
|
||||
|
@ -1519,11 +1555,13 @@ int activitypub_get_handler(d_char *req, char *q_path,
|
|||
}
|
||||
|
||||
|
||||
int activitypub_post_handler(d_char *req, char *q_path,
|
||||
d_char *payload, int p_size,
|
||||
int activitypub_post_handler(const xs_dict *req, const char *q_path,
|
||||
char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype)
|
||||
/* processes an input message */
|
||||
{
|
||||
(void)b_size;
|
||||
|
||||
int status = 202; /* accepted */
|
||||
char *i_ctype = xs_dict_get(req, "content-type");
|
||||
snac snac;
|
||||
|
|
121
data.c
121
data.c
|
@ -328,6 +328,51 @@ int index_add(const char *fn, const char *id)
|
|||
}
|
||||
|
||||
|
||||
int index_del_md5(const char *fn, const char *md5)
|
||||
/* deletes an md5 from an index */
|
||||
{
|
||||
int status = 404;
|
||||
FILE *f;
|
||||
|
||||
pthread_mutex_lock(&data_mutex);
|
||||
|
||||
if ((f = fopen(fn, "r+")) != NULL) {
|
||||
char line[256];
|
||||
|
||||
while (fgets(line, sizeof(line), f) != NULL) {
|
||||
line[32] = '\0';
|
||||
|
||||
if (strcmp(line, md5) == 0) {
|
||||
/* found! just rewind, overwrite it with garbage
|
||||
and an eventual call to index_gc() will clean it
|
||||
[yes: this breaks index_len()] */
|
||||
fseek(f, -33, SEEK_CUR);
|
||||
fwrite("-", 1, 1, f);
|
||||
status = 200;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
else
|
||||
status = 500;
|
||||
|
||||
pthread_mutex_unlock(&data_mutex);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int index_del(const char *fn, const char *id)
|
||||
/* deletes an id from an index */
|
||||
{
|
||||
xs *md5 = xs_md5_hex(id, strlen(id));
|
||||
return index_del_md5(fn, md5);
|
||||
}
|
||||
|
||||
|
||||
int index_gc(const char *fn)
|
||||
/* garbage-collects an index, deleting objects that are not here */
|
||||
{
|
||||
|
@ -772,6 +817,23 @@ int object_admire(const char *id, const char *actor, int like)
|
|||
}
|
||||
|
||||
|
||||
int object_unadmire(const char *id, const char *actor, int like)
|
||||
/* actor no longer likes or announces this object */
|
||||
{
|
||||
int status;
|
||||
xs *fn = _object_fn(id);
|
||||
|
||||
fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx");
|
||||
|
||||
status = index_del(fn, actor);
|
||||
|
||||
srv_debug(0,
|
||||
xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
int _object_user_cache(snac *snac, const char *id, const char *cachedir, int del)
|
||||
/* adds or deletes from a user cache */
|
||||
{
|
||||
|
@ -969,8 +1031,13 @@ void timeline_update_indexes(snac *snac, const char *id)
|
|||
|
||||
if (valid_status(object_get(id, &msg))) {
|
||||
/* if its ours and is public, also store in public */
|
||||
if (is_msg_public(snac, msg))
|
||||
if (is_msg_public(snac, msg)) {
|
||||
object_user_cache_add(snac, id, "public");
|
||||
|
||||
/* also add it to the instance public timeline */
|
||||
xs *ipt = xs_fmt("%s/public.idx", srv_basedir);
|
||||
index_add(ipt, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1041,7 +1108,7 @@ xs_list *timeline_top_level(snac *snac, xs_list *list)
|
|||
}
|
||||
|
||||
|
||||
d_char *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show)
|
||||
xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show)
|
||||
/* returns a timeline (with all entries) */
|
||||
{
|
||||
int c_max;
|
||||
|
@ -1059,7 +1126,7 @@ d_char *timeline_simple_list(snac *snac, const char *idx_name, int skip, int sho
|
|||
}
|
||||
|
||||
|
||||
d_char *timeline_list(snac *snac, const char *idx_name, int skip, int show)
|
||||
xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show)
|
||||
/* returns a timeline (only top level entries) */
|
||||
{
|
||||
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)
|
|||
}
|
||||
|
||||
|
||||
xs_list *timeline_instance_list(int skip, int show)
|
||||
/* returns the timeline for the full instance */
|
||||
{
|
||||
xs *idx = xs_fmt("%s/public.idx", srv_basedir);
|
||||
|
||||
return index_list_desc(idx, skip, show);
|
||||
}
|
||||
|
||||
|
||||
/** following **/
|
||||
|
||||
/* this needs special treatment and cannot use the object db as is,
|
||||
|
@ -1273,30 +1349,49 @@ int is_hidden(snac *snac, const char *id)
|
|||
}
|
||||
|
||||
|
||||
int actor_add(snac *snac, const char *actor, d_char *msg)
|
||||
int actor_add(const char *actor, xs_dict *msg)
|
||||
/* adds an actor */
|
||||
{
|
||||
return object_add_ow(actor, msg);
|
||||
}
|
||||
|
||||
|
||||
int actor_get(snac *snac, const char *actor, d_char **data)
|
||||
int actor_get(snac *snac1, const char *actor, xs_dict **data)
|
||||
/* returns an already downloaded actor */
|
||||
{
|
||||
int status = 200;
|
||||
d_char *d;
|
||||
xs_dict *d = NULL;
|
||||
|
||||
if (strcmp(actor, snac->actor) == 0) {
|
||||
if (strcmp(actor, snac1->actor) == 0) {
|
||||
/* this actor */
|
||||
if (data)
|
||||
*data = msg_actor(snac);
|
||||
*data = msg_actor(snac1);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
if (xs_startswith(actor, srv_baseurl)) {
|
||||
/* it's a (possible) local user */
|
||||
xs *l = xs_split(actor, "/");
|
||||
const char *uid = xs_list_get(l, -1);
|
||||
snac user;
|
||||
|
||||
if (!xs_is_null(uid) && user_open(&user, uid)) {
|
||||
if (data)
|
||||
*data = msg_actor(&user);
|
||||
|
||||
user_free(&user);
|
||||
return 200;
|
||||
}
|
||||
else
|
||||
return 404;
|
||||
}
|
||||
|
||||
/* read the object */
|
||||
if (!valid_status(status = object_get(actor, &d)))
|
||||
if (!valid_status(status = object_get(actor, &d))) {
|
||||
d = xs_free(d);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (data)
|
||||
*data = d;
|
||||
|
@ -1719,7 +1814,7 @@ static xs_dict *_new_qmsg(const char *type, const xs_val *msg, int retries)
|
|||
}
|
||||
|
||||
|
||||
void enqueue_input(snac *snac, xs_dict *msg, xs_dict *req, int retries)
|
||||
void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries)
|
||||
/* enqueues an input message */
|
||||
{
|
||||
xs *qmsg = _new_qmsg("input", msg, retries);
|
||||
|
@ -2020,7 +2115,11 @@ void purge_server(void)
|
|||
xs *ib_dir = xs_fmt("%s/inbox", srv_basedir);
|
||||
_purge_dir(ib_dir, 7);
|
||||
|
||||
srv_debug(1, xs_fmt("purge: global (obj: %d, idx: %d)", cnt, icnt));
|
||||
/* purge the instance timeline */
|
||||
xs *itl_fn = xs_fmt("%s/public.idx", srv_basedir);
|
||||
int itl_gc = index_gc(itl_fn);
|
||||
|
||||
srv_debug(1, xs_fmt("purge: global (obj: %d, idx: %d, itl: %d)", cnt, icnt, itl_gc));
|
||||
}
|
||||
|
||||
|
||||
|
|
30
doc/snac.1
30
doc/snac.1
|
@ -220,6 +220,36 @@ Please take note that they will show your timeline in a 'Mastodon fashion'
|
|||
post display with the most active threads at the top that the web interface of
|
||||
.Nm
|
||||
provides.
|
||||
.Ss Implementing post bots
|
||||
.Nm
|
||||
makes very easy to post messages in a non-interactive manner. This example
|
||||
posts a string:
|
||||
.Bd -literal -offset indent
|
||||
uptime | snac note $SNAC_BASEDIR $SNAC_USER -
|
||||
.Ed
|
||||
.Pp
|
||||
You can setup a line like this from a
|
||||
.Xr crontab 5
|
||||
or similar. Take note that you need a) command-line access to the same machine
|
||||
that hosts the
|
||||
.Nm
|
||||
instance, and b) write permissions to the storage directories and files.
|
||||
.Pp
|
||||
You can also post non-interactively using the Mastodon API and a command-line
|
||||
http tool like
|
||||
.Xr curl 1
|
||||
or similar. This has the advantage that you can do it remotely from any host,
|
||||
anywhere; the only thing you need is an API Token. This is an example:
|
||||
.Bd -literal -offset indent
|
||||
curl -X POST https://$SNAC_HOST/api/v1/statuses \\
|
||||
--header "Authorization: Bearer ${TOKEN}" -d "status=$(uptime)"
|
||||
.Ed
|
||||
.Pp
|
||||
You can obtain an API Token by connecting to the following URL:
|
||||
.Bd -literal -offset indent
|
||||
https://$SNAC_HOST/oauth/x-snac-get-token
|
||||
.Ed
|
||||
.Pp
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width Ds
|
||||
.It Ev DEBUG
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#! /bin/sh
|
||||
if [ ! -e /data/data/server.json ]
|
||||
then
|
||||
echo -ne "0.0.0.0\r\n8001\r\nlocalhost\r\n\r\n" | snac init /data/data
|
||||
echo -ne "0.0.0.0\r\n8001\r\nlocalhost\r\n\r\n\r\n" | snac init /data/data
|
||||
snac adduser /data/data testuser
|
||||
fi
|
||||
SSLKEYLOGFILE=/data/key snac httpd /data/data
|
||||
|
|
27
html.c
27
html.c
|
@ -79,7 +79,7 @@ xs_str *actor_name(xs_dict *actor)
|
|||
}
|
||||
|
||||
|
||||
d_char *html_actor_icon(snac *snac, d_char *os, char *actor,
|
||||
xs_str *html_actor_icon(xs_str *os, char *actor,
|
||||
const char *date, const char *udate, const char *url, int priv)
|
||||
{
|
||||
xs *s = xs_str_new(NULL);
|
||||
|
@ -168,7 +168,7 @@ d_char *html_msg_icon(snac *snac, d_char *os, char *msg)
|
|||
date = xs_dict_get(msg, "published");
|
||||
udate = xs_dict_get(msg, "updated");
|
||||
|
||||
os = html_actor_icon(snac, os, actor, date, udate, url, priv);
|
||||
os = html_actor_icon(os, actor, date, udate, url, priv);
|
||||
}
|
||||
|
||||
return os;
|
||||
|
@ -983,7 +983,7 @@ d_char *html_entry(snac *snac, d_char *os, char *msg, int local,
|
|||
}
|
||||
|
||||
|
||||
d_char *html_user_footer(snac *snac, d_char *s)
|
||||
xs_str *html_user_footer(xs_str *s)
|
||||
{
|
||||
xs *s1 = xs_fmt(
|
||||
"<div class=\"snac-footer\">\n"
|
||||
|
@ -1064,7 +1064,7 @@ d_char *html_timeline(snac *snac, char *list, int local, int skip, int show, int
|
|||
s = xs_str_cat(s, s1);
|
||||
}
|
||||
|
||||
s = html_user_footer(snac, s);
|
||||
s = html_user_footer(s);
|
||||
|
||||
s = xs_str_cat(s, "</body>\n</html>\n");
|
||||
|
||||
|
@ -1088,8 +1088,7 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade
|
|||
if (valid_status(actor_get(snac, actor_id, &actor))) {
|
||||
s = xs_str_cat(s, "<div class=\"snac-post\">\n");
|
||||
|
||||
s = html_actor_icon(snac, s, actor, xs_dict_get(actor, "published"), NULL, NULL, 0);
|
||||
|
||||
s = html_actor_icon(s, actor, xs_dict_get(actor, "published"), NULL, NULL, 0);
|
||||
|
||||
/* content (user bio) */
|
||||
char *c = xs_dict_get(actor, "summary");
|
||||
|
@ -1182,7 +1181,7 @@ d_char *html_people(snac *snac)
|
|||
|
||||
s = html_people_list(snac, s, wers, L("People that follows you"), "e");
|
||||
|
||||
s = html_user_footer(snac, s);
|
||||
s = html_user_footer(s);
|
||||
|
||||
s = xs_str_cat(s, "</body>\n</html>\n");
|
||||
|
||||
|
@ -1226,7 +1225,7 @@ xs_str *html_notifications(snac *snac)
|
|||
const char *actor_id = xs_dict_get(noti, "actor");
|
||||
xs *actor = NULL;
|
||||
|
||||
if (!valid_status(object_get(actor_id, &actor)))
|
||||
if (!valid_status(actor_get(snac, actor_id, &actor)))
|
||||
continue;
|
||||
|
||||
xs *a_name = actor_name(actor);
|
||||
|
@ -1277,12 +1276,13 @@ xs_str *html_notifications(snac *snac)
|
|||
s = xs_str_cat(s, s1);
|
||||
}
|
||||
|
||||
s = html_user_footer(snac, s);
|
||||
s = html_user_footer(s);
|
||||
|
||||
s = xs_str_cat(s, "</body>\n</html>\n");
|
||||
|
||||
/* set the check time to now */
|
||||
xs *dummy = notify_check_time(snac, 1);
|
||||
dummy = xs_free(dummy);
|
||||
|
||||
timeline_touch(snac);
|
||||
|
||||
|
@ -1290,7 +1290,8 @@ xs_str *html_notifications(snac *snac)
|
|||
}
|
||||
|
||||
|
||||
int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char **ctype)
|
||||
int html_get_handler(const xs_dict *req, const char *q_path,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
char *accept = xs_dict_get(req, "accept");
|
||||
int status = 404;
|
||||
|
@ -1547,9 +1548,13 @@ int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char *
|
|||
}
|
||||
|
||||
|
||||
int html_post_handler(d_char *req, char *q_path, d_char *payload, int p_size,
|
||||
int html_post_handler(const xs_dict *req, const char *q_path,
|
||||
char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
(void)p_size;
|
||||
(void)ctype;
|
||||
|
||||
int status = 0;
|
||||
snac snac;
|
||||
char *uid, *p_path;
|
||||
|
|
2
http.c
2
http.c
|
@ -33,7 +33,7 @@ xs_dict *http_signed_request_raw(const char *keyid, const char *seckey,
|
|||
date = xs_str_utctime(0, "%a, %d %b %Y %H:%M:%S GMT");
|
||||
|
||||
{
|
||||
xs *s = xs_replace(url, "https:/" "/", "");
|
||||
xs *s = xs_replace_n(url, "https:/" "/", "", 1);
|
||||
l1 = xs_split_n(s, "/", 1);
|
||||
}
|
||||
|
||||
|
|
8
httpd.c
8
httpd.c
|
@ -43,12 +43,14 @@ d_char *nodeinfo_2_0(void)
|
|||
}
|
||||
|
||||
|
||||
int server_get_handler(d_char *req, char *q_path,
|
||||
int server_get_handler(xs_dict *req, char *q_path,
|
||||
char **body, int *b_size, char **ctype)
|
||||
/* basic server services */
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
(void)req;
|
||||
|
||||
/* is it the server root? */
|
||||
if (*q_path == '\0') {
|
||||
/* try to open greeting.html */
|
||||
|
@ -285,6 +287,8 @@ static jmp_buf on_break;
|
|||
|
||||
void term_handler(int s)
|
||||
{
|
||||
(void)s;
|
||||
|
||||
longjmp(on_break, 1);
|
||||
}
|
||||
|
||||
|
@ -401,6 +405,8 @@ static void *background_thread(void *arg)
|
|||
{
|
||||
time_t purge_time;
|
||||
|
||||
(void)arg;
|
||||
|
||||
/* first purge time */
|
||||
purge_time = time(NULL) + 10 * 60;
|
||||
|
||||
|
|
22
main.c
22
main.c
|
@ -30,6 +30,7 @@ int usage(void)
|
|||
printf("actor {basedir} {uid} {url} Requests an actor\n");
|
||||
printf("note {basedir} {uid} {'text'} Sends a note to followers\n");
|
||||
printf("resetpwd {basedir} {uid} Resets the password of a user\n");
|
||||
printf("ping {basedir} {uid} {actor} Pings an actor\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -228,6 +229,27 @@ int main(int argc, char *argv[])
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "ping") == 0) {
|
||||
xs *actor_o = NULL;
|
||||
|
||||
if (valid_status(actor_request(&snac, url, &actor_o))) {
|
||||
xs *msg = msg_ping(&snac, url);
|
||||
|
||||
enqueue_output_by_actor(&snac, msg, url, 0);
|
||||
|
||||
if (dbglevel) {
|
||||
xs *j = xs_json_dumps_pp(msg, 4);
|
||||
printf("%s\n", j);
|
||||
}
|
||||
}
|
||||
else {
|
||||
srv_log(xs_fmt("Error getting actor %s", url));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(cmd, "request") == 0) {
|
||||
int status;
|
||||
xs *data = NULL;
|
||||
|
|
201
mastoapi.c
201
mastoapi.c
|
@ -162,7 +162,7 @@ const char *login_page = ""
|
|||
"<!DOCTYPE html>\n"
|
||||
"<body><h1>%s OAuth identify</h1>\n"
|
||||
"<div style=\"background-color: red; color: white\">%s</div>\n"
|
||||
"<form method=\"post\" action=\"https:/" "/%s/oauth/x-snac-login\">\n"
|
||||
"<form method=\"post\" action=\"https:/" "/%s/%s\">\n"
|
||||
"<p>Login: <input type=\"text\" name=\"login\"></p>\n"
|
||||
"<p>Password: <input type=\"password\" name=\"passwd\"></p>\n"
|
||||
"<input type=\"hidden\" name=\"redir\" value=\"%s\">\n"
|
||||
|
@ -175,6 +175,8 @@ const char *login_page = ""
|
|||
int oauth_get_handler(const xs_dict *req, const char *q_path,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
(void)b_size;
|
||||
|
||||
if (!xs_startswith(q_path, "/oauth/"))
|
||||
return 0;
|
||||
|
||||
|
@ -185,7 +187,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
|
|||
|
||||
int status = 404;
|
||||
xs_dict *msg = xs_dict_get(req, "q_vars");
|
||||
xs *cmd = xs_replace(q_path, "/oauth", "");
|
||||
xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
|
||||
|
||||
srv_debug(1, xs_fmt("oauth_get_handler %s", q_path));
|
||||
|
||||
|
@ -206,17 +208,28 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
|
|||
if (xs_is_null(state))
|
||||
state = "";
|
||||
|
||||
*body = xs_fmt(login_page, host, "", host, ruri, cid, state, USER_AGENT);
|
||||
*body = xs_fmt(login_page, host, "", host, "oauth/x-snac-login",
|
||||
ruri, cid, state, USER_AGENT);
|
||||
*ctype = "text/html";
|
||||
status = 200;
|
||||
|
||||
srv_debug(0, xs_fmt("oauth authorize: generating login page"));
|
||||
srv_debug(1, xs_fmt("oauth authorize: generating login page"));
|
||||
}
|
||||
else
|
||||
srv_debug(0, xs_fmt("oauth authorize: bad client_id %s", cid));
|
||||
srv_debug(1, xs_fmt("oauth authorize: bad client_id %s", cid));
|
||||
}
|
||||
else
|
||||
srv_debug(0, xs_fmt("oauth authorize: invalid or unset arguments"));
|
||||
srv_debug(1, xs_fmt("oauth authorize: invalid or unset arguments"));
|
||||
}
|
||||
else
|
||||
if (strcmp(cmd, "/x-snac-get-token") == 0) {
|
||||
const char *host = xs_dict_get(srv_config, "host");
|
||||
|
||||
*body = xs_fmt(login_page, host, "", host, "oauth/x-snac-get-token",
|
||||
"", "", "", USER_AGENT);
|
||||
*ctype = "text/html";
|
||||
status = 200;
|
||||
|
||||
}
|
||||
|
||||
return status;
|
||||
|
@ -227,6 +240,9 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
const char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
(void)p_size;
|
||||
(void)b_size;
|
||||
|
||||
if (!xs_startswith(q_path, "/oauth/"))
|
||||
return 0;
|
||||
|
||||
|
@ -245,7 +261,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
else
|
||||
args = xs_dup(xs_dict_get(req, "p_vars"));
|
||||
|
||||
xs *cmd = xs_replace(q_path, "/oauth", "");
|
||||
xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
|
||||
|
||||
srv_debug(1, xs_fmt("oauth_post_handler %s", q_path));
|
||||
|
||||
|
@ -259,7 +275,8 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
const char *host = xs_dict_get(srv_config, "host");
|
||||
|
||||
/* by default, generate another login form with an error */
|
||||
*body = xs_fmt(login_page, host, "LOGIN INCORRECT", host, redir, cid, state, USER_AGENT);
|
||||
*body = xs_fmt(login_page, host, "LOGIN INCORRECT", host, "oauth/x-snac-login",
|
||||
redir, cid, state, USER_AGENT);
|
||||
*ctype = "text/html";
|
||||
status = 200;
|
||||
|
||||
|
@ -268,8 +285,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
|
||||
if (user_open(&snac, login)) {
|
||||
/* check the login + password */
|
||||
if (check_password(login, passwd,
|
||||
xs_dict_get(snac.config, "passwd"))) {
|
||||
if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) {
|
||||
/* success! redirect to the desired uri */
|
||||
xs *code = random_str();
|
||||
|
||||
|
@ -328,7 +344,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
const char *auhdr = xs_dict_get(req, "authorization");
|
||||
|
||||
if (!xs_is_null(auhdr) && xs_startswith(auhdr, "Basic ")) {
|
||||
xs *s1 = xs_replace(auhdr, "Basic ", "");
|
||||
xs *s1 = xs_replace_n(auhdr, "Basic ", "", 1);
|
||||
int size;
|
||||
xs *s2 = xs_base64_dec(s1, &size);
|
||||
|
||||
|
@ -373,7 +389,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
|
||||
const char *uid = xs_dict_get(app, "uid");
|
||||
|
||||
srv_debug(0, xs_fmt("oauth token: "
|
||||
srv_debug(1, xs_fmt("oauth token: "
|
||||
"successful login for %s, new token %s", uid, tokid));
|
||||
|
||||
xs *token = xs_dict_new();
|
||||
|
@ -387,7 +403,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
}
|
||||
}
|
||||
else {
|
||||
srv_debug(0, xs_fmt("oauth token: invalid or unset arguments"));
|
||||
srv_debug(1, xs_fmt("oauth token: invalid or unset arguments"));
|
||||
status = 400;
|
||||
}
|
||||
}
|
||||
|
@ -409,7 +425,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
|
|||
}
|
||||
else {
|
||||
token_del(tokid);
|
||||
srv_debug(0, xs_fmt("oauth revoke: revoked token %s", tokid));
|
||||
srv_debug(1, xs_fmt("oauth revoke: revoked token %s", tokid));
|
||||
status = 200;
|
||||
|
||||
/* 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,
|
|||
}
|
||||
}
|
||||
else {
|
||||
srv_debug(0, xs_fmt("oauth revoke: invalid or unset arguments"));
|
||||
srv_debug(1, xs_fmt("oauth revoke: invalid or unset arguments"));
|
||||
status = 403;
|
||||
}
|
||||
}
|
||||
if (strcmp(cmd, "/x-snac-get-token") == 0) {
|
||||
const char *login = xs_dict_get(args, "login");
|
||||
const char *passwd = xs_dict_get(args, "passwd");
|
||||
|
||||
const char *host = xs_dict_get(srv_config, "host");
|
||||
|
||||
/* by default, generate another login form with an error */
|
||||
*body = xs_fmt(login_page, host, "LOGIN INCORRECT", host, "oauth/x-snac-get-token",
|
||||
"", "", "", USER_AGENT);
|
||||
*ctype = "text/html";
|
||||
status = 200;
|
||||
|
||||
if (login && passwd) {
|
||||
snac user;
|
||||
|
||||
if (user_open(&user, login)) {
|
||||
/* check the login + password */
|
||||
if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) {
|
||||
/* success! create a new token */
|
||||
xs *tokid = random_str();
|
||||
|
||||
srv_debug(1, xs_fmt("x-snac-new-token: "
|
||||
"successful login for %s, new token %s", login, tokid));
|
||||
|
||||
xs *token = xs_dict_new();
|
||||
token = xs_dict_append(token, "token", tokid);
|
||||
token = xs_dict_append(token, "client_id", "snac-client");
|
||||
token = xs_dict_append(token, "client_secret", "");
|
||||
token = xs_dict_append(token, "uid", login);
|
||||
token = xs_dict_append(token, "code", "");
|
||||
|
||||
token_add(tokid, token);
|
||||
|
||||
*ctype = "text/plain";
|
||||
xs_free(*body);
|
||||
*body = xs_dup(tokid);
|
||||
}
|
||||
|
||||
user_free(&user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
@ -537,7 +595,6 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
|
|||
xs *f = xs_val_new(XSTYPE_FALSE);
|
||||
xs *t = xs_val_new(XSTYPE_TRUE);
|
||||
xs *n = xs_val_new(XSTYPE_NULL);
|
||||
xs *el = xs_list_new();
|
||||
xs *idx = NULL;
|
||||
xs *ixc = NULL;
|
||||
|
||||
|
@ -787,7 +844,7 @@ int process_auth_token(snac *snac, const xs_dict *req)
|
|||
|
||||
/* if there is an authorization field, try to validate it */
|
||||
if (!xs_is_null(v = xs_dict_get(req, "authorization")) && xs_startswith(v, "Bearer ")) {
|
||||
xs *tokid = xs_replace(v, "Bearer ", "");
|
||||
xs *tokid = xs_replace_n(v, "Bearer ", "", 1);
|
||||
xs *token = token_get(tokid);
|
||||
|
||||
if (token != NULL) {
|
||||
|
@ -815,6 +872,8 @@ int process_auth_token(snac *snac, const xs_dict *req)
|
|||
int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
(void)b_size;
|
||||
|
||||
if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
|
||||
return 0;
|
||||
|
||||
|
@ -826,7 +885,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
|||
|
||||
int status = 404;
|
||||
xs_dict *args = xs_dict_get(req, "q_vars");
|
||||
xs *cmd = xs_replace(q_path, "/api", "");
|
||||
xs *cmd = xs_replace_n(q_path, "/api", "", 1);
|
||||
|
||||
snac snac1 = {0};
|
||||
int logged_in = process_auth_token(&snac1, req);
|
||||
|
@ -1036,11 +1095,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
|||
}
|
||||
else
|
||||
if (strcmp(cmd, "/v1/timelines/public") == 0) {
|
||||
/* the public timeline (public timelines for all users) */
|
||||
/* the instance public timeline (public timelines for all users) */
|
||||
|
||||
/* this is an ugly kludge: first users in the list get all the fame */
|
||||
/* NOTE: this api call needs no authorization; but,
|
||||
I need a logged-in user in mastoapi_status() for
|
||||
is_msg_public() and the liked/boosted flags,
|
||||
so it will silently fail for pure public access */
|
||||
|
||||
const char *limit_s = xs_dict_get(args, "limit");
|
||||
const char *limit_s = xs_dict_get(args, "limit");
|
||||
int limit = 0;
|
||||
int cnt = 0;
|
||||
|
||||
|
@ -1050,44 +1112,28 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
|||
if (limit == 0)
|
||||
limit = 20;
|
||||
|
||||
xs *out = xs_list_new();
|
||||
xs *users = user_list();
|
||||
xs_list *p = users;
|
||||
xs_str *uid;
|
||||
xs *timeline = timeline_instance_list(0, limit);
|
||||
xs *out = xs_list_new();
|
||||
xs_list *p = timeline;
|
||||
xs_str *md5;
|
||||
|
||||
while (xs_list_iter(&p, &uid) && cnt < limit) {
|
||||
snac user;
|
||||
while (logged_in && xs_list_iter(&p, &md5) && cnt < limit) {
|
||||
xs *msg = NULL;
|
||||
|
||||
if (user_open(&user, uid)) {
|
||||
xs *timeline = timeline_simple_list(&user, "public", 0, 4);
|
||||
xs_list *p2 = timeline;
|
||||
xs_str *v;
|
||||
/* get the entry */
|
||||
if (!valid_status(object_get_by_md5(md5, &msg)))
|
||||
continue;
|
||||
|
||||
while (xs_list_iter(&p2, &v) && cnt < limit) {
|
||||
xs *msg = NULL;
|
||||
/* discard non-Notes */
|
||||
if (strcmp(xs_dict_get(msg, "type"), "Note") != 0)
|
||||
continue;
|
||||
|
||||
/* get the entry */
|
||||
if (!valid_status(timeline_get_by_md5(&user, v, &msg)))
|
||||
continue;
|
||||
/* convert the Note into a Mastodon status */
|
||||
xs *st = mastoapi_status(&snac1, msg);
|
||||
|
||||
/* discard non-Notes */
|
||||
if (strcmp(xs_dict_get(msg, "type"), "Note") != 0)
|
||||
continue;
|
||||
|
||||
/* discard entries not by this user */
|
||||
if (!xs_startswith(xs_dict_get(msg, "id"), user.actor))
|
||||
continue;
|
||||
|
||||
/* convert the Note into a Mastodon status */
|
||||
xs *st = mastoapi_status(&user, msg);
|
||||
|
||||
if (st != NULL) {
|
||||
out = xs_list_append(out, st);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
user_free(&user);
|
||||
if (st != NULL) {
|
||||
out = xs_list_append(out, st);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1121,7 +1167,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
|||
xs *actor = NULL;
|
||||
xs *entry = NULL;
|
||||
|
||||
if (!valid_status(object_get(xs_dict_get(noti, "actor"), &actor)))
|
||||
if (!valid_status(actor_get(&snac1, xs_dict_get(noti, "actor"), &actor)))
|
||||
continue;
|
||||
|
||||
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,
|
|||
cfg = xs_dict_append(cfg, "statuses", d11);
|
||||
}
|
||||
|
||||
{
|
||||
xs *d11 = xs_dict_new();
|
||||
xs *mt = xs_list_new();
|
||||
|
||||
mt = xs_list_append(mt, "image/jpeg");
|
||||
mt = xs_list_append(mt, "image/png");
|
||||
mt = xs_list_append(mt, "image/gif");
|
||||
|
||||
d11 = xs_dict_append(d11, "supported_mime_types", mt);
|
||||
|
||||
d11 = xs_dict_append(d11, "image_size_limit", z);
|
||||
d11 = xs_dict_append(d11, "image_matrix_limit", z);
|
||||
d11 = xs_dict_append(d11, "video_size_limit", z);
|
||||
d11 = xs_dict_append(d11, "video_matrix_limit", z);
|
||||
d11 = xs_dict_append(d11, "video_frame_rate_limit", z);
|
||||
|
||||
cfg = xs_dict_append(cfg, "media_attachments", d11);
|
||||
}
|
||||
|
||||
ins = xs_dict_append(ins, "configuration", cfg);
|
||||
|
||||
*body = xs_json_dumps_pp(ins, 4);
|
||||
|
@ -1326,7 +1353,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
|||
/* skip the 'fake' part of the id */
|
||||
id = MID_TO_MD5(id);
|
||||
|
||||
if (valid_status(timeline_get_by_md5(&snac1, id, &msg))) {
|
||||
if (valid_status(object_get_by_md5(id, &msg))) {
|
||||
if (op == NULL) {
|
||||
if (!is_muted(&snac1, xs_dict_get(msg, "attributedTo"))) {
|
||||
/* return the status itself */
|
||||
|
@ -1487,6 +1514,9 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
|
|||
const char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
(void)p_size;
|
||||
(void)b_size;
|
||||
|
||||
if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
|
||||
return 0;
|
||||
|
||||
|
@ -1513,7 +1543,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
|
|||
printf("%s\n", j);
|
||||
}*/
|
||||
|
||||
xs *cmd = xs_replace(q_path, "/api", "");
|
||||
xs *cmd = xs_replace_n(q_path, "/api", "", 1);
|
||||
|
||||
snac snac = {0};
|
||||
int logged_in = process_auth_token(&snac, req);
|
||||
|
@ -1562,7 +1592,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
|
|||
|
||||
app_add(cid, app);
|
||||
|
||||
srv_debug(0, xs_fmt("mastoapi apps: new app %s", cid));
|
||||
srv_debug(1, xs_fmt("mastoapi apps: new app %s", cid));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1582,6 +1612,9 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
|
|||
if (xs_is_null(media_ids))
|
||||
media_ids = xs_dict_get(args, "media_ids[]");
|
||||
|
||||
if (xs_is_null(visibility))
|
||||
visibility = "public";
|
||||
|
||||
xs *attach_list = xs_list_new();
|
||||
xs *irt = NULL;
|
||||
|
||||
|
@ -1683,7 +1716,11 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
|
|||
}
|
||||
else
|
||||
if (strcmp(op, "unfavourite") == 0) {
|
||||
/* snac does not support Undo+Like */
|
||||
/* partial support: as the original Like message
|
||||
is not stored anywhere here, it's not possible
|
||||
to send an Undo + Like; the only thing done here
|
||||
is to delete the actor from the list of likes */
|
||||
object_unadmire(id, snac.actor, 1);
|
||||
}
|
||||
else
|
||||
if (strcmp(op, "reblog") == 0) {
|
||||
|
@ -1698,7 +1735,8 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
|
|||
}
|
||||
else
|
||||
if (strcmp(op, "unreblog") == 0) {
|
||||
/* snac does not support Undo+Announce */
|
||||
/* partial support: see comment in 'unfavourite' */
|
||||
object_unadmire(id, snac.actor, 0);
|
||||
}
|
||||
else
|
||||
if (strcmp(op, "bookmark") == 0) {
|
||||
|
@ -1903,6 +1941,9 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
|
|||
const char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype)
|
||||
{
|
||||
(void)p_size;
|
||||
(void)b_size;
|
||||
|
||||
if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
|
||||
return 0;
|
||||
|
||||
|
@ -1924,7 +1965,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
|
|||
if (args == NULL)
|
||||
return 400;
|
||||
|
||||
xs *cmd = xs_replace(q_path, "/api", "");
|
||||
xs *cmd = xs_replace_n(q_path, "/api", "", 1);
|
||||
|
||||
snac snac = {0};
|
||||
int logged_in = process_auth_token(&snac, req);
|
||||
|
|
31
snac.h
31
snac.h
|
@ -1,7 +1,7 @@
|
|||
/* snac - A simple, minimalistic ActivityPub instance */
|
||||
/* copyright (c) 2022 - 2023 grunfink / MIT license */
|
||||
|
||||
#define VERSION "2.29"
|
||||
#define VERSION "2.30"
|
||||
|
||||
#define USER_AGENT "snac/" VERSION
|
||||
|
||||
|
@ -83,6 +83,7 @@ int object_del_if_unref(const char *id);
|
|||
double object_ctime_by_md5(const char *md5);
|
||||
double object_ctime(const char *id);
|
||||
int object_admire(const char *id, const char *actor, int like);
|
||||
int object_unadmire(const char *id, const char *actor, int like);
|
||||
|
||||
int object_likes_len(const char *id);
|
||||
int object_announces_len(const char *id);
|
||||
|
@ -105,14 +106,14 @@ int timeline_touch(snac *snac);
|
|||
int timeline_here(snac *snac, const char *md5);
|
||||
int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg);
|
||||
int timeline_del(snac *snac, char *id);
|
||||
d_char *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show);
|
||||
d_char *timeline_list(snac *snac, const char *idx_name, int skip, int show);
|
||||
xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show);
|
||||
xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show);
|
||||
int timeline_add(snac *snac, char *id, char *o_msg);
|
||||
void timeline_admire(snac *snac, char *id, char *admirer, int like);
|
||||
|
||||
xs_list *timeline_top_level(snac *snac, xs_list *list);
|
||||
|
||||
d_char *local_list(snac *snac, int max);
|
||||
xs_list *local_list(snac *snac, int max);
|
||||
xs_list *timeline_instance_list(int skip, int show);
|
||||
|
||||
int following_add(snac *snac, const char *actor, const xs_dict *msg);
|
||||
int following_del(snac *snac, const char *actor);
|
||||
|
@ -127,8 +128,8 @@ int is_muted(snac *snac, const char *actor);
|
|||
void hide(snac *snac, const char *id);
|
||||
int is_hidden(snac *snac, const char *id);
|
||||
|
||||
int actor_add(snac *snac, const char *actor, d_char *msg);
|
||||
int actor_get(snac *snac, const char *actor, d_char **data);
|
||||
int actor_add(const char *actor, xs_dict *msg);
|
||||
int actor_get(snac *snac, const char *actor, xs_dict **data);
|
||||
|
||||
int static_get(snac *snac, const char *id, d_char **data, int *size);
|
||||
void static_put(snac *snac, const char *id, const char *data, int size);
|
||||
|
@ -154,7 +155,7 @@ void inbox_add(const char *inbox);
|
|||
void inbox_add_by_actor(const xs_dict *actor);
|
||||
xs_list *inbox_list(void);
|
||||
|
||||
void enqueue_input(snac *snac, xs_dict *msg, xs_dict *req, int retries);
|
||||
void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries);
|
||||
void enqueue_output_raw(const char *keyid, const char *seckey,
|
||||
xs_dict *msg, xs_str *inbox, int retries);
|
||||
void 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);
|
|||
void httpd(void);
|
||||
|
||||
int webfinger_request(const char *qs, char **actor, char **user);
|
||||
int webfinger_get_handler(d_char *req, char *q_path,
|
||||
int webfinger_get_handler(xs_dict *req, char *q_path,
|
||||
char **body, int *b_size, char **ctype);
|
||||
|
||||
const char *default_avatar_base64(void);
|
||||
|
@ -202,6 +203,8 @@ d_char *msg_undo(snac *snac, char *object);
|
|||
d_char *msg_delete(snac *snac, char *id);
|
||||
d_char *msg_actor(snac *snac);
|
||||
xs_dict *msg_update(snac *snac, xs_dict *object);
|
||||
xs_dict *msg_ping(snac *user, const char *rcpt);
|
||||
xs_dict *msg_pong(snac *user, const char *rcpt, const char *object);
|
||||
|
||||
int activitypub_request(snac *snac, const char *url, xs_dict **data);
|
||||
int actor_request(snac *snac, const char *actor, xs_dict **data);
|
||||
|
@ -219,17 +222,19 @@ int process_user_queue(snac *snac);
|
|||
void process_queue_item(xs_dict *q_item);
|
||||
int process_queue(void);
|
||||
|
||||
int activitypub_get_handler(d_char *req, char *q_path,
|
||||
int activitypub_get_handler(const xs_dict *req, const char *q_path,
|
||||
char **body, int *b_size, char **ctype);
|
||||
int activitypub_post_handler(d_char *req, char *q_path,
|
||||
int activitypub_post_handler(const xs_dict *req, const char *q_path,
|
||||
char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype);
|
||||
|
||||
d_char *not_really_markdown(const char *content);
|
||||
d_char *sanitize(const char *str);
|
||||
|
||||
int html_get_handler(d_char *req, char *q_path, char **body, int *b_size, char **ctype);
|
||||
int html_post_handler(d_char *req, char *q_path, d_char *payload, int p_size,
|
||||
int html_get_handler(const xs_dict *req, const char *q_path,
|
||||
char **body, int *b_size, char **ctype);
|
||||
int html_post_handler(const xs_dict *req, const char *q_path,
|
||||
char *payload, int p_size,
|
||||
char **body, int *b_size, char **ctype);
|
||||
|
||||
int snac_init(const char *_basedir);
|
||||
|
|
|
@ -21,7 +21,7 @@ int webfinger_request(const char *qs, char **actor, char **user)
|
|||
|
||||
if (xs_startswith(qs, "https:/" "/")) {
|
||||
/* actor query: pick the host */
|
||||
xs *s = xs_replace(qs, "https:/" "/", "");
|
||||
xs *s = xs_replace_n(qs, "https:/" "/", "", 1);
|
||||
|
||||
l = xs_split_n(s, "/", 1);
|
||||
|
||||
|
@ -74,7 +74,7 @@ int webfinger_request(const char *qs, char **actor, char **user)
|
|||
char *subject = xs_dict_get(obj, "subject");
|
||||
|
||||
if (subject)
|
||||
*user = xs_replace(subject, "acct:", "");
|
||||
*user = xs_replace_n(subject, "acct:", "", 1);
|
||||
}
|
||||
|
||||
if (actor != NULL) {
|
||||
|
@ -105,6 +105,8 @@ int webfinger_get_handler(d_char *req, char *q_path,
|
|||
{
|
||||
int status;
|
||||
|
||||
(void)b_size;
|
||||
|
||||
if (strcmp(q_path, "/.well-known/webfinger") != 0)
|
||||
return 0;
|
||||
|
||||
|
@ -137,7 +139,7 @@ int webfinger_get_handler(d_char *req, char *q_path,
|
|||
else
|
||||
if (xs_startswith(resource, "acct:")) {
|
||||
/* it's an account name */
|
||||
xs *an = xs_replace(resource, "acct:", "");
|
||||
xs *an = xs_replace_n(resource, "acct:", "", 1);
|
||||
xs *l = NULL;
|
||||
|
||||
/* strip a possible leading @ */
|
||||
|
|
12
xs.h
12
xs.h
|
@ -65,8 +65,10 @@ xs_str *xs_str_new(const char *str);
|
|||
xs_str *xs_str_wrap_i(const char *prefix, xs_str *str, const char *suffix);
|
||||
#define xs_str_prepend_i(str, prefix) xs_str_wrap_i(prefix, str, NULL)
|
||||
#define xs_str_cat(str, suffix) xs_str_wrap_i(NULL, str, suffix)
|
||||
xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto);
|
||||
#define xs_replace(str, sfrom, sto) xs_replace_i(xs_dup(str), sfrom, sto)
|
||||
xs_str *xs_replace_in(xs_str *str, const char *sfrom, const char *sto, int times);
|
||||
#define xs_replace_i(str, sfrom, sto) xs_replace_in(str, sfrom, sto, XS_ALL)
|
||||
#define xs_replace(str, sfrom, sto) xs_replace_in(xs_dup(str), sfrom, sto, XS_ALL)
|
||||
#define xs_replace_n(str, sfrom, sto, times) xs_replace_in(xs_dup(str), sfrom, sto, times)
|
||||
xs_str *xs_fmt(const char *fmt, ...);
|
||||
int xs_str_in(const char *haystack, const char *needle);
|
||||
int _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)
|
|||
}
|
||||
|
||||
|
||||
xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto)
|
||||
xs_str *xs_replace_in(xs_str *str, const char *sfrom, const char *sto, int times)
|
||||
/* replaces inline all sfrom with sto */
|
||||
{
|
||||
XS_ASSERT_TYPE(str, XSTYPE_STRING);
|
||||
|
@ -426,7 +428,7 @@ xs_str *xs_replace_i(xs_str *str, const char *sfrom, const char *sto)
|
|||
char *ss;
|
||||
int offset = 0;
|
||||
|
||||
while ((ss = strstr(str + offset, sfrom)) != NULL) {
|
||||
while (times > 0 && (ss = strstr(str + offset, sfrom)) != NULL) {
|
||||
int n_offset = ss - str;
|
||||
|
||||
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)
|
|||
memcpy(str + n_offset, sto, stsz);
|
||||
|
||||
offset = n_offset + stsz;
|
||||
|
||||
times--;
|
||||
}
|
||||
|
||||
return str;
|
||||
|
|
221
xs_encdec.h
221
xs_encdec.h
|
@ -7,13 +7,20 @@
|
|||
xs_str *xs_hex_enc(const xs_val *data, int size);
|
||||
xs_val *xs_hex_dec(const xs_str *hex, int *size);
|
||||
int xs_is_hex(const char *str);
|
||||
xs_str *xs_base32_enc(const xs_val *data, int sz);
|
||||
xs_str *xs_base32hex_enc(const xs_val *data, int sz);
|
||||
xs_val *xs_base32_dec(const xs_str *data, int *size);
|
||||
xs_val *xs_base32hex_dec(const xs_str *data, int *size);
|
||||
xs_str *xs_base64_enc(const xs_val *data, int sz);
|
||||
xs_val *xs_base64_dec(const xs_str *data, int *size);
|
||||
int xs_is_base64(const char *str);
|
||||
xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint);
|
||||
|
||||
|
||||
#ifdef XS_IMPLEMENTATION
|
||||
|
||||
/** hex **/
|
||||
|
||||
xs_str *xs_hex_enc(const xs_val *data, int size)
|
||||
/* returns an hexdump of data */
|
||||
{
|
||||
|
@ -78,16 +85,178 @@ int xs_is_hex(const char *str)
|
|||
}
|
||||
|
||||
|
||||
xs_str *xs_base64_enc(const xs_val *data, int sz)
|
||||
/* encodes data to base64 */
|
||||
/** base32 */
|
||||
|
||||
static char *xs_b32_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"234567=";
|
||||
|
||||
static char *xs_b32hex_tbl = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUV=";
|
||||
|
||||
/*
|
||||
00000|00011|11111|12222|22223|33333|33444|44444
|
||||
*/
|
||||
|
||||
xs_str *xs_base32_enc_tbl(const xs_val *data, int sz, const char *b32_tbl)
|
||||
/* encodes data to base32 using a table */
|
||||
{
|
||||
xs_str *s = xs_str_new(NULL);
|
||||
unsigned char *p;
|
||||
int n;
|
||||
|
||||
p = (unsigned char *)data;
|
||||
|
||||
for (n = 0; n < sz; n += 5) {
|
||||
int l = sz - n;
|
||||
char enc[9] = "========";
|
||||
|
||||
enc[0] = b32_tbl[(p[n] >> 3) & 0x1f];
|
||||
|
||||
if (l > 1) {
|
||||
enc[1] = b32_tbl[(p[n] << 2 | p[n + 1] >> 6) & 0x1f];
|
||||
enc[2] = b32_tbl[(p[n + 1] >> 1) & 0x1f];
|
||||
|
||||
if (l > 2) {
|
||||
enc[3] = b32_tbl[(p[n + 1] << 4 | p[n + 2] >> 4) & 0x1f];
|
||||
|
||||
if (l > 3) {
|
||||
enc[4] = b32_tbl[(p[n + 2] << 1 | p[n + 3] >> 7) & 0x1f];
|
||||
enc[5] = b32_tbl[(p[n + 3] >> 2) & 0x1f];
|
||||
|
||||
if (l > 4) {
|
||||
enc[6] = b32_tbl[(p[n + 3] << 3 | p[n + 4] >> 5) & 0x1f];
|
||||
enc[7] = b32_tbl[(p[n + 4]) & 0x1f];
|
||||
}
|
||||
else
|
||||
enc[6] = b32_tbl[(p[n + 3] << 3) & 0x1f];
|
||||
}
|
||||
else
|
||||
enc[4] = b32_tbl[(p[n + 2] << 1) & 0x1f];
|
||||
}
|
||||
else
|
||||
enc[3] = b32_tbl[(p[n + 1] << 4) & 0x1f];
|
||||
}
|
||||
else
|
||||
enc[1] = b32_tbl[(p[n] << 2) & 0x1f];
|
||||
|
||||
s = xs_str_cat(s, enc);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
xs_str *xs_base32_enc(const xs_val *data, int sz)
|
||||
/* encodes data to base32 */
|
||||
{
|
||||
return xs_base32_enc_tbl(data, sz, xs_b32_tbl);
|
||||
}
|
||||
|
||||
|
||||
xs_str *xs_base32hex_enc(const xs_val *data, int sz)
|
||||
/* encodes data to base32 with HEX alphabet (RFC4648) */
|
||||
{
|
||||
return xs_base32_enc_tbl(data, sz, xs_b32hex_tbl);
|
||||
}
|
||||
|
||||
|
||||
xs_val *xs_base32_dec_tbl(const xs_str *data, int *size, const char *b32_tbl)
|
||||
/* decodes data from base32 using a table */
|
||||
{
|
||||
xs_val *s = NULL;
|
||||
int sz = 0;
|
||||
char *p;
|
||||
|
||||
p = (char *)data;
|
||||
|
||||
/* size of data must be a multiple of 8 */
|
||||
if (strlen(p) % 8)
|
||||
return NULL;
|
||||
|
||||
for (p = (char *)data; *p; p += 8) {
|
||||
int cs[8];
|
||||
int n;
|
||||
unsigned char tmp[5];
|
||||
|
||||
for (n = 0; n < 8; n++) {
|
||||
char *ss = strchr(b32_tbl, p[n]);
|
||||
|
||||
if (ss == NULL) {
|
||||
/* not a base32 char */
|
||||
return xs_free(s);
|
||||
}
|
||||
|
||||
cs[n] = ss - b32_tbl;
|
||||
}
|
||||
|
||||
n = 0;
|
||||
|
||||
/* #0 byte */
|
||||
tmp[n++] = cs[0] << 3 | cs[1] >> 2;
|
||||
|
||||
if (cs[2] != 32) {
|
||||
/* #1 byte */
|
||||
tmp[n++] = (cs[1] & 0x3) << 6 | cs[2] << 1 | (cs[3] & 0x10) >> 4;
|
||||
|
||||
if (cs[4] != 32) {
|
||||
/* #2 byte */
|
||||
tmp[n++] = (cs[3] & 0xf) << 4 | cs[4] >> 1;
|
||||
|
||||
if (cs[5] != 32) {
|
||||
/* #3 byte */
|
||||
tmp[n++] = (cs[4] & 0x1) << 7 | cs[5] << 2 | cs[6] >> 3;
|
||||
|
||||
if (cs[7] != 32) {
|
||||
/* #4 byte */
|
||||
tmp[n++] = (cs[6] & 0x7) << 5 | cs[7];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* must be done manually because data can be pure binary */
|
||||
s = xs_realloc(s, _xs_blk_size(sz + n));
|
||||
memcpy(s + sz, tmp, n);
|
||||
sz += n;
|
||||
}
|
||||
|
||||
/* asciiz it to use it as a string */
|
||||
s = xs_realloc(s, _xs_blk_size(sz + 1));
|
||||
s[sz] = '\0';
|
||||
|
||||
*size = sz;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
xs_val *xs_base32_dec(const xs_str *data, int *size)
|
||||
/* decodes data from base32 */
|
||||
{
|
||||
return xs_base32_dec_tbl(data, size, xs_b32_tbl);
|
||||
}
|
||||
|
||||
|
||||
xs_val *xs_base32hex_dec(const xs_str *data, int *size)
|
||||
/* decodes data from base32 with HEX alphabet (RFC4648) */
|
||||
{
|
||||
return xs_base32_dec_tbl(data, size, xs_b32hex_tbl);
|
||||
}
|
||||
|
||||
|
||||
/** base64 */
|
||||
|
||||
static char *xs_b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/=";
|
||||
|
||||
xs_str *xs_base64_enc_tbl(const xs_val *data, int sz, const char *b64_tbl)
|
||||
/* encodes data to base64 using a table */
|
||||
{
|
||||
xs_str *s;
|
||||
unsigned char *p;
|
||||
char *i;
|
||||
int bsz, n;
|
||||
static char *b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
bsz = ((sz + 3 - 1) / 3) * 4;
|
||||
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)
|
|||
}
|
||||
|
||||
|
||||
xs_val *xs_base64_dec(const xs_str *data, int *size)
|
||||
/* decodes data from base64 */
|
||||
xs_str *xs_base64_enc(const xs_val *data, int sz)
|
||||
/* encodes data to base64 */
|
||||
{
|
||||
return xs_base64_enc_tbl(data, sz, xs_b64_tbl);
|
||||
}
|
||||
|
||||
|
||||
xs_val *xs_base64_dec_tbl(const xs_str *data, int *size, const char *b64_tbl)
|
||||
/* decodes data from base64 using a table */
|
||||
{
|
||||
xs_val *s = NULL;
|
||||
int sz = 0;
|
||||
char *p;
|
||||
static char *b64_tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/=";
|
||||
|
||||
p = (char *)data;
|
||||
|
||||
|
@ -184,6 +357,34 @@ xs_val *xs_base64_dec(const xs_str *data, int *size)
|
|||
}
|
||||
|
||||
|
||||
xs_val *xs_base64_dec(const xs_str *data, int *size)
|
||||
/* decodes data from base64 */
|
||||
{
|
||||
return xs_base64_dec_tbl(data, size, xs_b64_tbl);
|
||||
}
|
||||
|
||||
|
||||
int xs_is_base64_tbl(const char *str, const char *b64_tbl)
|
||||
/* returns 1 if str is a base64 string, with table */
|
||||
{
|
||||
while (*str) {
|
||||
if (strchr(b64_tbl, *str++) == NULL)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int xs_is_base64(const char *str)
|
||||
/* returns 1 if str is a base64 string */
|
||||
{
|
||||
return xs_is_base64_tbl(str, xs_b64_tbl);
|
||||
}
|
||||
|
||||
|
||||
/** utf-8 **/
|
||||
|
||||
xs_str *xs_utf8_enc(xs_str *str, unsigned int cpoint)
|
||||
/* encodes an Unicode codepoint to utf8 */
|
||||
{
|
||||
|
|
|
@ -1 +1 @@
|
|||
/* a885c7cc4c8e6384ae23125ed05f434471ccc6fb */
|
||||
/* dfdd729248d7169b80cb6a7462fe6c0ba6efeb16 */
|
||||
|
|
Loading…
Reference in a new issue