From af8f1ef273e457318cb48f198e73c59e57373723 Mon Sep 17 00:00:00 2001 From: Louis Brauer Date: Wed, 29 May 2024 11:53:34 +0200 Subject: [PATCH] Implement image uploads for Tokodon --- mastoapi.c | 32 +++++++++++++++++++---------- xs_url.h | 60 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/mastoapi.c b/mastoapi.c index 0adba08..c1f70b9 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -3116,15 +3116,16 @@ void persist_image(const char *key, const xs_val *data, const char *payload, sna if (fn && *fn) { const char *ext = strrchr(fn, '.'); - /* Mona iOS sends JPG file as application/octet-stream with filename "header" - * Make sure we have a unique file name, otherwise updated images will not be - * loaded by clients. - */ + + /* Mona iOS sends always jpg as application/octet-stream with no filename */ if (ext == NULL || strcmp(fn, key) == 0) { - fn = random_str(); ext = ".jpg"; } - xs *hash = xs_md5_hex(fn, strlen(fn)); + + /* Make sure we have a unique file name, otherwise updated images will not be + * re-loaded by clients. */ + xs *rnd = random_str(); + xs *hash = xs_md5_hex(rnd, strlen(rnd)); xs *id = xs_fmt("%s%s", hash, ext); xs *url = xs_fmt("%s/s/%s", snac->actor, id); int fo = xs_number_get(xs_list_get(data, 1)); @@ -3158,6 +3159,14 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, if (!xs_is_null(payload)) args = xs_json_loads(payload); } + else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded")) + { + // Some apps send form data instead of json so we should cater for those + if (!xs_is_null(payload)) { + xs *upl = xs_url_dec(payload); + args = xs_url_vars(upl); + } + } else args = xs_dup(xs_dict_get(req, "p_vars")); @@ -3172,10 +3181,6 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, if (xs_startswith(cmd, "/v1/accounts/update_credentials")) { /* Update user profile fields */ if (logged_in) { - /* - xs_str *dump = xs_json_dumps(args, 4); - printf("%s\n\n", dump); - */ int c = 0; const xs_str *k; const xs_val *v; @@ -3195,7 +3200,8 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, if (strcmp(k, "bot") == 0) { if (v != NULL) snac.config = xs_dict_set(snac.config, "bot", - strcmp(v, "true") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); + (strcmp(v, "true") == 0 || + strcmp(v, "1") == 0) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); } else if (strcmp(k, "source[sensitive]") == 0) { @@ -3228,6 +3234,10 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, snac.config = xs_dict_set(snac.config, "metadata", new_fields); } } + /* we don't have support for the following options, yet + - discoverable (0/1) + - locked (0/1) + */ } /* Persist profile */ diff --git a/xs_url.h b/xs_url.h index 3a063cd..9deda38 100644 --- a/xs_url.h +++ b/xs_url.h @@ -8,7 +8,6 @@ xs_str *xs_url_dec(const char *str); xs_dict *xs_url_vars(const char *str); xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header); - #ifdef XS_IMPLEMENTATION xs_str *xs_url_dec(const char *str) @@ -126,6 +125,7 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea xs *l1 = NULL; const char *vn = NULL; const char *fn = NULL; + const char *ct = NULL; char *q; int po, ps; @@ -138,32 +138,47 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea /* skip the \r\n */ p += 2; - /* now on a Content-Disposition... line; get it */ - q = strchr(p, '\r'); - s1 = xs_realloc(NULL, q - p + 1); - memcpy(s1, p, q - p); - s1[q - p] = '\0'; + /* Tokodon sends also a Content-Type headers, + let's use it to determine the file type */ + do { + if (p[0] == 13 && p[1] == 10) + break; + q = strchr(p, '\r'); + s1 = xs_realloc(NULL, q - p + 1); + memcpy(s1, p, q - p); + s1[q - p] = '\0'; - /* move on (over a \r\n) */ - p = q; + if (xs_startswith(s1, "Content-Disposition")) { + /* split by " like a primitive man */ + l1 = xs_split(s1, "\""); - /* split by " like a primitive man */ - l1 = xs_split(s1, "\""); + /* get the variable name */ + vn = xs_list_get(l1, 1); - /* get the variable name */ - vn = xs_list_get(l1, 1); + /* is it an attached file? */ + if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) { + /* get the file name */ + fn = xs_list_get(l1, 3); + } + } + else + if (xs_startswith(s1, "Content-Type")) { + l1 = xs_split(s1, ":"); - /* is it an attached file? */ - if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) { - /* get the file name */ - fn = xs_list_get(l1, 3); - } + if (xs_list_len(l1) >= 2) { + ct = xs_lstrip_chars_i(xs_dup(xs_list_get(l1, 1)), " "); + } + } + + p += (q - p); + p += 2; // Skip /r/n + } while (1); /* find the start of the part content */ - if ((p = xs_memmem(p, p_size - (p - payload), "\r\n\r\n", 4)) == NULL) + if ((p = xs_memmem(p, p_size - (p - payload), "\r\n", 2)) == NULL) break; - p += 4; + p += 2; // Skip empty line /* find the next boundary */ if ((q = xs_memmem(p, p_size - (p - payload), boundary, bsz)) == NULL) @@ -175,6 +190,13 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea /* is it a filename? */ if (fn != NULL) { /* p_var value is a list */ + /* if filename has no extension and content-type is image, attach extension to the filename */ + if (strchr(fn, '.') == NULL && xs_startswith(ct, "image/")) { + char *ext = strchr(ct, '/'); + ext++; + fn = xs_str_cat(xs_str_new(""), fn, ".", ext); + } + xs *l1 = xs_list_new(); xs *vpo = xs_number_new(po); xs *vps = xs_number_new(ps);