commit af8f1ef

Louis Brauer  ·  2024-05-29 09:53:34 +0000 UTC
parent 45a2aab
Implement image uploads for Tokodon
2 files changed,  +64, -32
+21, -11
 1@@ -3116,15 +3116,16 @@ void persist_image(const char *key, const xs_val *data, const char *payload, sna
 2 
 3             if (fn && *fn) {
 4                 const char *ext = strrchr(fn, '.');
 5-                /* Mona iOS sends JPG file as application/octet-stream with filename "header"
 6-                 * Make sure we have a unique file name, otherwise updated images will not be
 7-                 * loaded by clients.
 8-                 */
 9+
10+                /* Mona iOS sends always jpg as application/octet-stream with no filename */
11                 if (ext == NULL || strcmp(fn, key) == 0) {
12-                    fn = random_str();
13                     ext = ".jpg";
14                 }
15-                xs *hash        = xs_md5_hex(fn, strlen(fn));
16+
17+                /* Make sure we have a unique file name, otherwise updated images will not be
18+                 * re-loaded by clients. */
19+                xs *rnd         = random_str();
20+                xs *hash        = xs_md5_hex(rnd, strlen(rnd));
21                 xs *id          = xs_fmt("%s%s", hash, ext);
22                 xs *url         = xs_fmt("%s/s/%s", snac->actor, id);
23                 int fo          = xs_number_get(xs_list_get(data, 1));
24@@ -3158,6 +3159,14 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
25         if (!xs_is_null(payload))
26             args = xs_json_loads(payload);
27     }
28+    else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded"))
29+    {
30+        // Some apps send form data instead of json so we should cater for those
31+        if (!xs_is_null(payload)) {
32+            xs *upl = xs_url_dec(payload);
33+            args    = xs_url_vars(upl);
34+        }
35+    }
36     else
37         args = xs_dup(xs_dict_get(req, "p_vars"));
38 
39@@ -3172,10 +3181,6 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
40     if (xs_startswith(cmd, "/v1/accounts/update_credentials")) {
41         /* Update user profile fields */
42         if (logged_in) {
43-            /*
44-            xs_str *dump = xs_json_dumps(args, 4);
45-            printf("%s\n\n", dump);
46-            */
47             int c = 0;
48             const xs_str *k;
49             const xs_val *v;
50@@ -3195,7 +3200,8 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
51                 if (strcmp(k, "bot") == 0) {
52                     if (v != NULL)
53                         snac.config = xs_dict_set(snac.config, "bot",
54-                            strcmp(v, "true") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
55+                            (strcmp(v, "true") == 0 ||
56+                                strcmp(v, "1") == 0) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
57                 }
58                 else
59                 if (strcmp(k, "source[sensitive]") == 0) {
60@@ -3228,6 +3234,10 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
61                         snac.config = xs_dict_set(snac.config, "metadata", new_fields);
62                     }
63                 }
64+                /* we don't have support for the following options, yet
65+                   - discoverable (0/1)
66+                   - locked (0/1)
67+                 */
68             }
69 
70             /* Persist profile */
+43, -21
 1@@ -8,7 +8,6 @@ xs_str *xs_url_dec(const char *str);
 2 xs_dict *xs_url_vars(const char *str);
 3 xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header);
 4 
 5-
 6 #ifdef XS_IMPLEMENTATION
 7 
 8 xs_str *xs_url_dec(const char *str)
 9@@ -126,6 +125,7 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea
10         xs *l1 = NULL;
11         const char *vn = NULL;
12         const char *fn = NULL;
13+        const char *ct = NULL;
14         char *q;
15         int po, ps;
16 
17@@ -138,32 +138,47 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea
18         /* skip the \r\n */
19         p += 2;
20 
21-        /* now on a Content-Disposition... line; get it */
22-        q = strchr(p, '\r');
23-        s1 = xs_realloc(NULL, q - p + 1);
24-        memcpy(s1, p, q - p);
25-        s1[q - p] = '\0';
26-
27-        /* move on (over a \r\n) */
28-        p = q;
29-
30-        /* split by " like a primitive man */
31-        l1 = xs_split(s1, "\"");
32+        /* Tokodon sends also a Content-Type headers,
33+           let's use it to determine the file type */
34+        do {
35+            if (p[0] == 13 && p[1] == 10)
36+                break;
37+            q = strchr(p, '\r');
38+            s1 = xs_realloc(NULL, q - p + 1);
39+            memcpy(s1, p, q - p);
40+            s1[q - p] = '\0';
41+
42+            if (xs_startswith(s1, "Content-Disposition")) {
43+                /* split by " like a primitive man */
44+                l1 = xs_split(s1, "\"");
45+
46+                /* get the variable name */
47+                vn = xs_list_get(l1, 1);
48+
49+                /* is it an attached file? */
50+                if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) {
51+                    /* get the file name */
52+                    fn = xs_list_get(l1, 3);
53+                }
54+            }
55+            else
56+            if (xs_startswith(s1, "Content-Type")) {
57+                l1 = xs_split(s1, ":");
58 
59-        /* get the variable name */
60-        vn = xs_list_get(l1, 1);
61+                if (xs_list_len(l1) >= 2) {
62+                    ct = xs_lstrip_chars_i(xs_dup(xs_list_get(l1, 1)), " ");
63+                }
64+            }
65 
66-        /* is it an attached file? */
67-        if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) {
68-            /* get the file name */
69-            fn = xs_list_get(l1, 3);
70-        }
71+            p += (q - p);
72+            p += 2; // Skip /r/n
73+        } while (1);
74 
75         /* find the start of the part content */
76-        if ((p = xs_memmem(p, p_size - (p - payload), "\r\n\r\n", 4)) == NULL)
77+        if ((p = xs_memmem(p, p_size - (p - payload), "\r\n", 2)) == NULL)
78             break;
79 
80-        p += 4;
81+        p += 2; // Skip empty line
82 
83         /* find the next boundary */
84         if ((q = xs_memmem(p, p_size - (p - payload), boundary, bsz)) == NULL)
85@@ -175,6 +190,13 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea
86         /* is it a filename? */
87         if (fn != NULL) {
88             /* p_var value is a list */
89+            /* if filename has no extension and content-type is image, attach extension to the filename */
90+            if (strchr(fn, '.') == NULL && xs_startswith(ct, "image/")) {
91+                char *ext = strchr(ct, '/');
92+                ext++;
93+                fn = xs_str_cat(xs_str_new(""), fn, ".", ext);
94+            }
95+
96             xs *l1 = xs_list_new();
97             xs *vpo = xs_number_new(po);
98             xs *vps = xs_number_new(ps);