diff options
| author | 2024-05-29 11:53:34 +0200 | |
|---|---|---|
| committer | 2024-05-29 11:53:34 +0200 | |
| commit | af8f1ef273e457318cb48f198e73c59e57373723 (patch) | |
| tree | 5998501d0ea9a09f26db65e2d29fcb0927b5eee0 | |
| parent | Fix parsing of boundary for multipart/form-data (diff) | |
| download | penes-snac2-af8f1ef273e457318cb48f198e73c59e57373723.tar.gz penes-snac2-af8f1ef273e457318cb48f198e73c59e57373723.tar.xz penes-snac2-af8f1ef273e457318cb48f198e73c59e57373723.zip | |
Implement image uploads for Tokodon
| -rw-r--r-- | mastoapi.c | 32 | ||||
| -rw-r--r-- | xs_url.h | 64 |
2 files changed, 64 insertions, 32 deletions
| @@ -3116,15 +3116,16 @@ void persist_image(const char *key, const xs_val *data, const char *payload, sna | |||
| 3116 | 3116 | ||
| 3117 | if (fn && *fn) { | 3117 | if (fn && *fn) { |
| 3118 | const char *ext = strrchr(fn, '.'); | 3118 | const char *ext = strrchr(fn, '.'); |
| 3119 | /* Mona iOS sends JPG file as application/octet-stream with filename "header" | 3119 | |
| 3120 | * Make sure we have a unique file name, otherwise updated images will not be | 3120 | /* Mona iOS sends always jpg as application/octet-stream with no filename */ |
| 3121 | * loaded by clients. | ||
| 3122 | */ | ||
| 3123 | if (ext == NULL || strcmp(fn, key) == 0) { | 3121 | if (ext == NULL || strcmp(fn, key) == 0) { |
| 3124 | fn = random_str(); | ||
| 3125 | ext = ".jpg"; | 3122 | ext = ".jpg"; |
| 3126 | } | 3123 | } |
| 3127 | xs *hash = xs_md5_hex(fn, strlen(fn)); | 3124 | |
| 3125 | /* Make sure we have a unique file name, otherwise updated images will not be | ||
| 3126 | * re-loaded by clients. */ | ||
| 3127 | xs *rnd = random_str(); | ||
| 3128 | xs *hash = xs_md5_hex(rnd, strlen(rnd)); | ||
| 3128 | xs *id = xs_fmt("%s%s", hash, ext); | 3129 | xs *id = xs_fmt("%s%s", hash, ext); |
| 3129 | xs *url = xs_fmt("%s/s/%s", snac->actor, id); | 3130 | xs *url = xs_fmt("%s/s/%s", snac->actor, id); |
| 3130 | int fo = xs_number_get(xs_list_get(data, 1)); | 3131 | 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, | |||
| 3158 | if (!xs_is_null(payload)) | 3159 | if (!xs_is_null(payload)) |
| 3159 | args = xs_json_loads(payload); | 3160 | args = xs_json_loads(payload); |
| 3160 | } | 3161 | } |
| 3162 | else if (i_ctype && xs_startswith(i_ctype, "application/x-www-form-urlencoded")) | ||
| 3163 | { | ||
| 3164 | // Some apps send form data instead of json so we should cater for those | ||
| 3165 | if (!xs_is_null(payload)) { | ||
| 3166 | xs *upl = xs_url_dec(payload); | ||
| 3167 | args = xs_url_vars(upl); | ||
| 3168 | } | ||
| 3169 | } | ||
| 3161 | else | 3170 | else |
| 3162 | args = xs_dup(xs_dict_get(req, "p_vars")); | 3171 | args = xs_dup(xs_dict_get(req, "p_vars")); |
| 3163 | 3172 | ||
| @@ -3172,10 +3181,6 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, | |||
| 3172 | if (xs_startswith(cmd, "/v1/accounts/update_credentials")) { | 3181 | if (xs_startswith(cmd, "/v1/accounts/update_credentials")) { |
| 3173 | /* Update user profile fields */ | 3182 | /* Update user profile fields */ |
| 3174 | if (logged_in) { | 3183 | if (logged_in) { |
| 3175 | /* | ||
| 3176 | xs_str *dump = xs_json_dumps(args, 4); | ||
| 3177 | printf("%s\n\n", dump); | ||
| 3178 | */ | ||
| 3179 | int c = 0; | 3184 | int c = 0; |
| 3180 | const xs_str *k; | 3185 | const xs_str *k; |
| 3181 | const xs_val *v; | 3186 | const xs_val *v; |
| @@ -3195,7 +3200,8 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, | |||
| 3195 | if (strcmp(k, "bot") == 0) { | 3200 | if (strcmp(k, "bot") == 0) { |
| 3196 | if (v != NULL) | 3201 | if (v != NULL) |
| 3197 | snac.config = xs_dict_set(snac.config, "bot", | 3202 | snac.config = xs_dict_set(snac.config, "bot", |
| 3198 | strcmp(v, "true") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); | 3203 | (strcmp(v, "true") == 0 || |
| 3204 | strcmp(v, "1") == 0) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); | ||
| 3199 | } | 3205 | } |
| 3200 | else | 3206 | else |
| 3201 | if (strcmp(k, "source[sensitive]") == 0) { | 3207 | if (strcmp(k, "source[sensitive]") == 0) { |
| @@ -3228,6 +3234,10 @@ int mastoapi_patch_handler(const xs_dict *req, const char *q_path, | |||
| 3228 | snac.config = xs_dict_set(snac.config, "metadata", new_fields); | 3234 | snac.config = xs_dict_set(snac.config, "metadata", new_fields); |
| 3229 | } | 3235 | } |
| 3230 | } | 3236 | } |
| 3237 | /* we don't have support for the following options, yet | ||
| 3238 | - discoverable (0/1) | ||
| 3239 | - locked (0/1) | ||
| 3240 | */ | ||
| 3231 | } | 3241 | } |
| 3232 | 3242 | ||
| 3233 | /* Persist profile */ | 3243 | /* Persist profile */ |
| @@ -8,7 +8,6 @@ xs_str *xs_url_dec(const char *str); | |||
| 8 | xs_dict *xs_url_vars(const char *str); | 8 | xs_dict *xs_url_vars(const char *str); |
| 9 | xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header); | 9 | xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header); |
| 10 | 10 | ||
| 11 | |||
| 12 | #ifdef XS_IMPLEMENTATION | 11 | #ifdef XS_IMPLEMENTATION |
| 13 | 12 | ||
| 14 | xs_str *xs_url_dec(const char *str) | 13 | 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 | |||
| 126 | xs *l1 = NULL; | 125 | xs *l1 = NULL; |
| 127 | const char *vn = NULL; | 126 | const char *vn = NULL; |
| 128 | const char *fn = NULL; | 127 | const char *fn = NULL; |
| 128 | const char *ct = NULL; | ||
| 129 | char *q; | 129 | char *q; |
| 130 | int po, ps; | 130 | int po, ps; |
| 131 | 131 | ||
| @@ -138,32 +138,47 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea | |||
| 138 | /* skip the \r\n */ | 138 | /* skip the \r\n */ |
| 139 | p += 2; | 139 | p += 2; |
| 140 | 140 | ||
| 141 | /* now on a Content-Disposition... line; get it */ | 141 | /* Tokodon sends also a Content-Type headers, |
| 142 | q = strchr(p, '\r'); | 142 | let's use it to determine the file type */ |
| 143 | s1 = xs_realloc(NULL, q - p + 1); | 143 | do { |
| 144 | memcpy(s1, p, q - p); | 144 | if (p[0] == 13 && p[1] == 10) |
| 145 | s1[q - p] = '\0'; | 145 | break; |
| 146 | 146 | q = strchr(p, '\r'); | |
| 147 | /* move on (over a \r\n) */ | 147 | s1 = xs_realloc(NULL, q - p + 1); |
| 148 | p = q; | 148 | memcpy(s1, p, q - p); |
| 149 | 149 | s1[q - p] = '\0'; | |
| 150 | /* split by " like a primitive man */ | 150 | |
| 151 | l1 = xs_split(s1, "\""); | 151 | if (xs_startswith(s1, "Content-Disposition")) { |
| 152 | /* split by " like a primitive man */ | ||
| 153 | l1 = xs_split(s1, "\""); | ||
| 154 | |||
| 155 | /* get the variable name */ | ||
| 156 | vn = xs_list_get(l1, 1); | ||
| 157 | |||
| 158 | /* is it an attached file? */ | ||
| 159 | if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) { | ||
| 160 | /* get the file name */ | ||
| 161 | fn = xs_list_get(l1, 3); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | else | ||
| 165 | if (xs_startswith(s1, "Content-Type")) { | ||
| 166 | l1 = xs_split(s1, ":"); | ||
| 152 | 167 | ||
| 153 | /* get the variable name */ | 168 | if (xs_list_len(l1) >= 2) { |
| 154 | vn = xs_list_get(l1, 1); | 169 | ct = xs_lstrip_chars_i(xs_dup(xs_list_get(l1, 1)), " "); |
| 170 | } | ||
| 171 | } | ||
| 155 | 172 | ||
| 156 | /* is it an attached file? */ | 173 | p += (q - p); |
| 157 | if (xs_list_len(l1) >= 4 && strcmp(xs_list_get(l1, 2), "; filename=") == 0) { | 174 | p += 2; // Skip /r/n |
| 158 | /* get the file name */ | 175 | } while (1); |
| 159 | fn = xs_list_get(l1, 3); | ||
| 160 | } | ||
| 161 | 176 | ||
| 162 | /* find the start of the part content */ | 177 | /* find the start of the part content */ |
| 163 | if ((p = xs_memmem(p, p_size - (p - payload), "\r\n\r\n", 4)) == NULL) | 178 | if ((p = xs_memmem(p, p_size - (p - payload), "\r\n", 2)) == NULL) |
| 164 | break; | 179 | break; |
| 165 | 180 | ||
| 166 | p += 4; | 181 | p += 2; // Skip empty line |
| 167 | 182 | ||
| 168 | /* find the next boundary */ | 183 | /* find the next boundary */ |
| 169 | if ((q = xs_memmem(p, p_size - (p - payload), boundary, bsz)) == NULL) | 184 | 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 | |||
| 175 | /* is it a filename? */ | 190 | /* is it a filename? */ |
| 176 | if (fn != NULL) { | 191 | if (fn != NULL) { |
| 177 | /* p_var value is a list */ | 192 | /* p_var value is a list */ |
| 193 | /* if filename has no extension and content-type is image, attach extension to the filename */ | ||
| 194 | if (strchr(fn, '.') == NULL && xs_startswith(ct, "image/")) { | ||
| 195 | char *ext = strchr(ct, '/'); | ||
| 196 | ext++; | ||
| 197 | fn = xs_str_cat(xs_str_new(""), fn, ".", ext); | ||
| 198 | } | ||
| 199 | |||
| 178 | xs *l1 = xs_list_new(); | 200 | xs *l1 = xs_list_new(); |
| 179 | xs *vpo = xs_number_new(po); | 201 | xs *vpo = xs_number_new(po); |
| 180 | xs *vps = xs_number_new(ps); | 202 | xs *vps = xs_number_new(ps); |