summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Louis Brauer2024-05-29 11:53:34 +0200
committerGravatar Louis Brauer2024-05-29 11:53:34 +0200
commitaf8f1ef273e457318cb48f198e73c59e57373723 (patch)
tree5998501d0ea9a09f26db65e2d29fcb0927b5eee0
parentFix parsing of boundary for multipart/form-data (diff)
downloadpenes-snac2-af8f1ef273e457318cb48f198e73c59e57373723.tar.gz
penes-snac2-af8f1ef273e457318cb48f198e73c59e57373723.tar.xz
penes-snac2-af8f1ef273e457318cb48f198e73c59e57373723.zip
Implement image uploads for Tokodon
-rw-r--r--mastoapi.c32
-rw-r--r--xs_url.h64
2 files changed, 64 insertions, 32 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
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 */
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);
8xs_dict *xs_url_vars(const char *str); 8xs_dict *xs_url_vars(const char *str);
9xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header); 9xs_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
14xs_str *xs_url_dec(const char *str) 13xs_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);