summaryrefslogtreecommitdiff
path: root/mastoapi.c
diff options
context:
space:
mode:
Diffstat (limited to 'mastoapi.c')
-rw-r--r--mastoapi.c526
1 files changed, 354 insertions, 172 deletions
diff --git a/mastoapi.c b/mastoapi.c
index 3936c2a..c1f70b9 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -32,9 +32,9 @@ int app_add(const char *id, const xs_dict *app)
32/* stores an app */ 32/* stores an app */
33{ 33{
34 if (!xs_is_hex(id)) 34 if (!xs_is_hex(id))
35 return 500; 35 return HTTP_STATUS_INTERNAL_SERVER_ERROR;
36 36
37 int status = 201; 37 int status = HTTP_STATUS_CREATED;
38 xs *fn = xs_fmt("%s/app/", srv_basedir); 38 xs *fn = xs_fmt("%s/app/", srv_basedir);
39 FILE *f; 39 FILE *f;
40 40
@@ -47,7 +47,7 @@ int app_add(const char *id, const xs_dict *app)
47 fclose(f); 47 fclose(f);
48 } 48 }
49 else 49 else
50 status = 500; 50 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
51 51
52 return status; 52 return status;
53} 53}
@@ -95,9 +95,9 @@ int token_add(const char *id, const xs_dict *token)
95/* stores a token */ 95/* stores a token */
96{ 96{
97 if (!xs_is_hex(id)) 97 if (!xs_is_hex(id))
98 return 500; 98 return HTTP_STATUS_INTERNAL_SERVER_ERROR;
99 99
100 int status = 201; 100 int status = HTTP_STATUS_CREATED;
101 xs *fn = xs_fmt("%s/token/", srv_basedir); 101 xs *fn = xs_fmt("%s/token/", srv_basedir);
102 FILE *f; 102 FILE *f;
103 103
@@ -110,7 +110,7 @@ int token_add(const char *id, const xs_dict *token)
110 fclose(f); 110 fclose(f);
111 } 111 }
112 else 112 else
113 status = 500; 113 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
114 114
115 return status; 115 return status;
116} 116}
@@ -174,7 +174,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
174 if (!xs_startswith(q_path, "/oauth/")) 174 if (!xs_startswith(q_path, "/oauth/"))
175 return 0; 175 return 0;
176 176
177 int status = 404; 177 int status = HTTP_STATUS_NOT_FOUND;
178 const xs_dict *msg = xs_dict_get(req, "q_vars"); 178 const xs_dict *msg = xs_dict_get(req, "q_vars");
179 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); 179 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
180 180
@@ -186,7 +186,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
186 const char *rtype = xs_dict_get(msg, "response_type"); 186 const char *rtype = xs_dict_get(msg, "response_type");
187 const char *state = xs_dict_get(msg, "state"); 187 const char *state = xs_dict_get(msg, "state");
188 188
189 status = 400; 189 status = HTTP_STATUS_BAD_REQUEST;
190 190
191 if (cid && ruri && rtype && strcmp(rtype, "code") == 0) { 191 if (cid && ruri && rtype && strcmp(rtype, "code") == 0) {
192 xs *app = app_get(cid); 192 xs *app = app_get(cid);
@@ -201,7 +201,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
201 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-login", 201 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-login",
202 ruri, cid, state, USER_AGENT); 202 ruri, cid, state, USER_AGENT);
203 *ctype = "text/html"; 203 *ctype = "text/html";
204 status = 200; 204 status = HTTP_STATUS_OK;
205 205
206 srv_debug(1, xs_fmt("oauth authorize: generating login page")); 206 srv_debug(1, xs_fmt("oauth authorize: generating login page"));
207 } 207 }
@@ -219,7 +219,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
219 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-get-token", 219 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-get-token",
220 "", "", "", USER_AGENT); 220 "", "", "", USER_AGENT);
221 *ctype = "text/html"; 221 *ctype = "text/html";
222 status = 200; 222 status = HTTP_STATUS_OK;
223 223
224 } 224 }
225 225
@@ -237,7 +237,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
237 if (!xs_startswith(q_path, "/oauth/")) 237 if (!xs_startswith(q_path, "/oauth/"))
238 return 0; 238 return 0;
239 239
240 int status = 404; 240 int status = HTTP_STATUS_NOT_FOUND;
241 241
242 const char *i_ctype = xs_dict_get(req, "content-type"); 242 const char *i_ctype = xs_dict_get(req, "content-type");
243 xs *args = NULL; 243 xs *args = NULL;
@@ -255,7 +255,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
255 args = xs_dup(xs_dict_get(req, "p_vars")); 255 args = xs_dup(xs_dict_get(req, "p_vars"));
256 256
257 if (args == NULL) 257 if (args == NULL)
258 return 400; 258 return HTTP_STATUS_BAD_REQUEST;
259 259
260 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); 260 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
261 261
@@ -274,7 +274,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
274 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-login", 274 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-login",
275 redir, cid, state, USER_AGENT); 275 redir, cid, state, USER_AGENT);
276 *ctype = "text/html"; 276 *ctype = "text/html";
277 status = 200; 277 status = HTTP_STATUS_OK;
278 278
279 if (login && passwd && redir && cid) { 279 if (login && passwd && redir && cid) {
280 snac snac; 280 snac snac;
@@ -296,7 +296,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
296 else 296 else
297 *body = xs_fmt("%s?code=%s", redir, code); 297 *body = xs_fmt("%s?code=%s", redir, code);
298 298
299 status = 303; 299 status = HTTP_STATUS_SEE_OTHER;
300 } 300 }
301 301
302 /* if there is a state, add it */ 302 /* if there is a state, add it */
@@ -375,12 +375,12 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
375 xs *app = app_get(cid); 375 xs *app = app_get(cid);
376 376
377 if (app == NULL) { 377 if (app == NULL) {
378 status = 401; 378 status = HTTP_STATUS_UNAUTHORIZED;
379 srv_log(xs_fmt("oauth token: invalid app %s", cid)); 379 srv_log(xs_fmt("oauth token: invalid app %s", cid));
380 } 380 }
381 else 381 else
382 if (strcmp(csec, xs_dict_get(app, "client_secret")) != 0) { 382 if (strcmp(csec, xs_dict_get(app, "client_secret")) != 0) {
383 status = 401; 383 status = HTTP_STATUS_UNAUTHORIZED;
384 srv_log(xs_fmt("oauth token: invalid client_secret for app %s", cid)); 384 srv_log(xs_fmt("oauth token: invalid client_secret for app %s", cid));
385 } 385 }
386 else { 386 else {
@@ -397,7 +397,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
397 397
398 *body = xs_json_dumps(rsp, 4); 398 *body = xs_json_dumps(rsp, 4);
399 *ctype = "application/json"; 399 *ctype = "application/json";
400 status = 200; 400 status = HTTP_STATUS_OK;
401 401
402 const char *uid = xs_dict_get(app, "uid"); 402 const char *uid = xs_dict_get(app, "uid");
403 403
@@ -416,7 +416,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
416 } 416 }
417 else { 417 else {
418 srv_debug(1, xs_fmt("oauth token: invalid or unset arguments")); 418 srv_debug(1, xs_fmt("oauth token: invalid or unset arguments"));
419 status = 400; 419 status = HTTP_STATUS_BAD_REQUEST;
420 } 420 }
421 } 421 }
422 else 422 else
@@ -433,12 +433,12 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
433 433
434 if (token == NULL || strcmp(csec, xs_dict_get(token, "client_secret")) != 0) { 434 if (token == NULL || strcmp(csec, xs_dict_get(token, "client_secret")) != 0) {
435 srv_debug(1, xs_fmt("oauth revoke: bad secret for token %s", tokid)); 435 srv_debug(1, xs_fmt("oauth revoke: bad secret for token %s", tokid));
436 status = 403; 436 status = HTTP_STATUS_FORBIDDEN;
437 } 437 }
438 else { 438 else {
439 token_del(tokid); 439 token_del(tokid);
440 srv_debug(1, xs_fmt("oauth revoke: revoked token %s", tokid)); 440 srv_debug(1, xs_fmt("oauth revoke: revoked token %s", tokid));
441 status = 200; 441 status = HTTP_STATUS_OK;
442 442
443 /* also delete the app, as it serves no purpose from now on */ 443 /* also delete the app, as it serves no purpose from now on */
444 app_del(cid); 444 app_del(cid);
@@ -446,7 +446,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
446 } 446 }
447 else { 447 else {
448 srv_debug(1, xs_fmt("oauth revoke: invalid or unset arguments")); 448 srv_debug(1, xs_fmt("oauth revoke: invalid or unset arguments"));
449 status = 403; 449 status = HTTP_STATUS_FORBIDDEN;
450 } 450 }
451 } 451 }
452 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ 452 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/
@@ -459,7 +459,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
459 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-get-token", 459 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-get-token",
460 "", "", "", USER_AGENT); 460 "", "", "", USER_AGENT);
461 *ctype = "text/html"; 461 *ctype = "text/html";
462 status = 200; 462 status = HTTP_STATUS_OK;
463 463
464 if (login && passwd) { 464 if (login && passwd) {
465 snac user; 465 snac user;
@@ -1150,109 +1150,123 @@ int process_auth_token(snac *snac, const xs_dict *req)
1150 return logged_in; 1150 return logged_in;
1151} 1151}
1152 1152
1153 1153void credentials_get(char **body, char **ctype, int *status, snac snac)
1154int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1155 char **body, int *b_size, char **ctype)
1156{ 1154{
1157 (void)b_size; 1155 xs *acct = xs_dict_new();
1156
1157 acct = xs_dict_append(acct, "id", snac.md5);
1158 acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid"));
1159 acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid"));
1160 acct = xs_dict_append(acct, "display_name", xs_dict_get(snac.config, "name"));
1161 acct = xs_dict_append(acct, "created_at", xs_dict_get(snac.config, "published"));
1162 acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac.config, "published"));
1163 acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio"));
1164 acct = xs_dict_append(acct, "url", snac.actor);
1165 acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE));
1166 acct = xs_dict_append(acct, "bot", xs_dict_get(snac.config, "bot"));
1158 1167
1159 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 1168 xs *src = xs_json_loads("{\"privacy\":\"public\","
1160 return 0; 1169 "\"sensitive\":false,\"fields\":[],\"note\":\"\"}");
1170 /* some apps take the note from the source object */
1171 src = xs_dict_set(src, "note", xs_dict_get(snac.config, "bio"));
1172 src = xs_dict_set(src, "privacy", xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE ? "private" : "public");
1161 1173
1162 int status = 404; 1174 const xs_str *cw = xs_dict_get(snac.config, "cw");
1163 const xs_dict *args = xs_dict_get(req, "q_vars"); 1175 src = xs_dict_set(src, "sensitive",
1164 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 1176 strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
1165 1177
1166 snac snac1 = {0}; 1178 src = xs_dict_set(src, "bot", xs_dict_get(snac.config, "bot"));
1167 int logged_in = process_auth_token(&snac1, req);
1168 1179
1169 if (strcmp(cmd, "/v1/accounts/verify_credentials") == 0) { /** **/ 1180 xs *avatar = NULL;
1170 if (logged_in) { 1181 const char *av = xs_dict_get(snac.config, "avatar");
1171 xs *acct = xs_dict_new();
1172
1173 acct = xs_dict_append(acct, "id", snac1.md5);
1174 acct = xs_dict_append(acct, "username", xs_dict_get(snac1.config, "uid"));
1175 acct = xs_dict_append(acct, "acct", xs_dict_get(snac1.config, "uid"));
1176 acct = xs_dict_append(acct, "display_name", xs_dict_get(snac1.config, "name"));
1177 acct = xs_dict_append(acct, "created_at", xs_dict_get(snac1.config, "published"));
1178 acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac1.config, "published"));
1179 acct = xs_dict_append(acct, "note", xs_dict_get(snac1.config, "bio"));
1180 acct = xs_dict_append(acct, "url", snac1.actor);
1181 acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE));
1182 acct = xs_dict_append(acct, "bot", xs_dict_get(snac1.config, "bot"));
1183
1184 xs *src = xs_json_loads("{\"privacy\":\"public\","
1185 "\"sensitive\":false,\"fields\":[],\"note\":\"\"}");
1186 acct = xs_dict_append(acct, "source", src);
1187
1188 xs *avatar = NULL;
1189 const char *av = xs_dict_get(snac1.config, "avatar");
1190
1191 if (xs_is_null(av) || *av == '\0')
1192 avatar = xs_fmt("%s/susie.png", srv_baseurl);
1193 else
1194 avatar = xs_dup(av);
1195 1182
1196 acct = xs_dict_append(acct, "avatar", avatar); 1183 if (xs_is_null(av) || *av == '\0')
1197 acct = xs_dict_append(acct, "avatar_static", avatar); 1184 avatar = xs_fmt("%s/susie.png", srv_baseurl);
1185 else
1186 avatar = xs_dup(av);
1198 1187
1199 xs *header = NULL; 1188 acct = xs_dict_append(acct, "avatar", avatar);
1200 const char *hd = xs_dict_get(snac1.config, "header"); 1189 acct = xs_dict_append(acct, "avatar_static", avatar);
1201 1190
1202 if (!xs_is_null(hd)) 1191 xs *header = NULL;
1203 header = xs_dup(hd); 1192 const char *hd = xs_dict_get(snac.config, "header");
1204 else
1205 header = xs_fmt("%s/header.png", srv_baseurl);
1206 1193
1207 acct = xs_dict_append(acct, "header", header); 1194 if (!xs_is_null(hd))
1208 acct = xs_dict_append(acct, "header_static", header); 1195 header = xs_dup(hd);
1196 else
1197 header = xs_fmt("%s/header.png", srv_baseurl);
1209 1198
1210 const xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); 1199 acct = xs_dict_append(acct, "header", header);
1211 if (xs_type(metadata) == XSTYPE_DICT) { 1200 acct = xs_dict_append(acct, "header_static", header);
1212 xs *fields = xs_list_new();
1213 const xs_str *k;
1214 const xs_str *v;
1215 1201
1216 xs_dict *val_links = snac1.links; 1202 const xs_dict *metadata = xs_dict_get(snac.config, "metadata");
1217 if (xs_is_null(val_links)) 1203 if (xs_type(metadata) == XSTYPE_DICT) {
1218 val_links = xs_stock(XSTYPE_DICT); 1204 xs *fields = xs_list_new();
1205 const xs_str *k;
1206 const xs_str *v;
1219 1207
1220 int c = 0; 1208 xs_dict *val_links = snac.links;
1221 while (xs_dict_next(metadata, &k, &v, &c)) { 1209 if (xs_is_null(val_links))
1222 xs *val_date = NULL; 1210 val_links = xs_stock(XSTYPE_DICT);
1223 1211
1224 const xs_number *verified_time = xs_dict_get(val_links, v); 1212 int c = 0;
1225 if (xs_type(verified_time) == XSTYPE_NUMBER) { 1213 while (xs_dict_next(metadata, &k, &v, &c)) {
1226 time_t t = xs_number_get(verified_time); 1214 xs *val_date = NULL;
1227 1215
1228 if (t > 0) 1216 const xs_number *verified_time = xs_dict_get(val_links, v);
1229 val_date = xs_str_utctime(t, ISO_DATE_SPEC); 1217 if (xs_type(verified_time) == XSTYPE_NUMBER) {
1230 } 1218 time_t t = xs_number_get(verified_time);
1231 1219
1232 xs *d = xs_dict_new(); 1220 if (t > 0)
1221 val_date = xs_str_utctime(t, ISO_DATE_SPEC);
1222 }
1233 1223
1234 d = xs_dict_append(d, "name", k); 1224 xs *d = xs_dict_new();
1235 d = xs_dict_append(d, "value", v);
1236 d = xs_dict_append(d, "verified_at",
1237 xs_type(val_date) == XSTYPE_STRING && *val_date ?
1238 val_date : xs_stock(XSTYPE_NULL));
1239 1225
1240 fields = xs_list_append(fields, d); 1226 d = xs_dict_append(d, "name", k);
1241 } 1227 d = xs_dict_append(d, "value", v);
1228 d = xs_dict_append(d, "verified_at",
1229 xs_type(val_date) == XSTYPE_STRING && *val_date ? val_date : xs_stock(XSTYPE_NULL));
1242 1230
1243 acct = xs_dict_set(acct, "fields", fields); 1231 fields = xs_list_append(fields, d);
1244 } 1232 }
1245 1233
1246 acct = xs_dict_append(acct, "followers_count", xs_stock(0)); 1234 acct = xs_dict_set(acct, "fields", fields);
1247 acct = xs_dict_append(acct, "following_count", xs_stock(0)); 1235 /* some apps take the fields from the source object */
1248 acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); 1236 src = xs_dict_set(src, "fields", fields);
1237 }
1249 1238
1250 *body = xs_json_dumps(acct, 4); 1239 acct = xs_dict_append(acct, "source", src);
1251 *ctype = "application/json"; 1240 acct = xs_dict_append(acct, "followers_count", xs_stock(0));
1252 status = 200; 1241 acct = xs_dict_append(acct, "following_count", xs_stock(0));
1242 acct = xs_dict_append(acct, "statuses_count", xs_stock(0));
1243
1244 *body = xs_json_dumps(acct, 4);
1245 *ctype = "application/json";
1246 *status = HTTP_STATUS_OK;
1247}
1248
1249int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1250 char **body, int *b_size, char **ctype)
1251{
1252 (void)b_size;
1253
1254 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
1255 return 0;
1256
1257 int status = HTTP_STATUS_NOT_FOUND;
1258 const xs_dict *args = xs_dict_get(req, "q_vars");
1259 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
1260
1261 snac snac1 = {0};
1262 int logged_in = process_auth_token(&snac1, req);
1263
1264 if (strcmp(cmd, "/v1/accounts/verify_credentials") == 0) { /** **/
1265 if (logged_in) {
1266 credentials_get(body, ctype, &status, snac1);
1253 } 1267 }
1254 else { 1268 else {
1255 status = 422; // "Unprocessable entity" (no login) 1269 status = HTTP_STATUS_UNPROCESSABLE_CONTENT; // (no login)
1256 } 1270 }
1257 } 1271 }
1258 else 1272 else
@@ -1279,10 +1293,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1279 1293
1280 *body = xs_json_dumps(res, 4); 1294 *body = xs_json_dumps(res, 4);
1281 *ctype = "application/json"; 1295 *ctype = "application/json";
1282 status = 200; 1296 status = HTTP_STATUS_OK;
1283 } 1297 }
1284 else 1298 else
1285 status = 422; 1299 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
1286 } 1300 }
1287 else 1301 else
1288 if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/ 1302 if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/
@@ -1304,7 +1318,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1304 1318
1305 *body = xs_json_dumps(macct, 4); 1319 *body = xs_json_dumps(macct, 4);
1306 *ctype = "application/json"; 1320 *ctype = "application/json";
1307 status = 200; 1321 status = HTTP_STATUS_OK;
1308 1322
1309 user_free(&user); 1323 user_free(&user);
1310 } 1324 }
@@ -1429,6 +1443,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1429 } 1443 }
1430 } 1444 }
1431 } 1445 }
1446 else
1447 if (strcmp(opt, "featured_tags") == 0) {
1448 /* snac doesn't have features tags, yet? */
1449 /* implement empty response so apps like Tokodon don't show an error */
1450 *body = xs_dup("[]");
1451 *ctype = "application/json";
1452 status = HTTP_STATUS_OK;
1453 }
1432 1454
1433 user_free(&snac2); 1455 user_free(&snac2);
1434 } 1456 }
@@ -1441,16 +1463,24 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1441 } 1463 }
1442 else 1464 else
1443 if (strcmp(opt, "statuses") == 0) { 1465 if (strcmp(opt, "statuses") == 0) {
1444 /* we don't serve statuses of others; return the empty list */ 1466 /* we don't serve statuses of others; return the empty list */
1445 out = xs_list_new(); 1467 out = xs_list_new();
1446 } 1468 }
1469 else
1470 if (strcmp(opt, "featured_tags") == 0) {
1471 /* snac doesn't have features tags, yet? */
1472 /* implement empty response so apps like Tokodon don't show an error */
1473 *body = xs_dup("[]");
1474 *ctype = "application/json";
1475 status = HTTP_STATUS_OK;
1476 }
1447 } 1477 }
1448 } 1478 }
1449 1479
1450 if (out != NULL) { 1480 if (out != NULL) {
1451 *body = xs_json_dumps(out, 4); 1481 *body = xs_json_dumps(out, 4);
1452 *ctype = "application/json"; 1482 *ctype = "application/json";
1453 status = 200; 1483 status = HTTP_STATUS_OK;
1454 } 1484 }
1455 } 1485 }
1456 } 1486 }
@@ -1554,12 +1584,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1554 1584
1555 *body = xs_json_dumps(out, 4); 1585 *body = xs_json_dumps(out, 4);
1556 *ctype = "application/json"; 1586 *ctype = "application/json";
1557 status = 200; 1587 status = HTTP_STATUS_OK;
1558 1588
1559 srv_debug(2, xs_fmt("mastoapi timeline: returned %d entries", xs_list_len(out))); 1589 srv_debug(2, xs_fmt("mastoapi timeline: returned %d entries", xs_list_len(out)));
1560 } 1590 }
1561 else { 1591 else {
1562 status = 401; // unauthorized 1592 status = HTTP_STATUS_UNAUTHORIZED;
1563 } 1593 }
1564 } 1594 }
1565 else 1595 else
@@ -1612,7 +1642,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1612 1642
1613 *body = xs_json_dumps(out, 4); 1643 *body = xs_json_dumps(out, 4);
1614 *ctype = "application/json"; 1644 *ctype = "application/json";
1615 status = 200; 1645 status = HTTP_STATUS_OK;
1616 } 1646 }
1617 else 1647 else
1618 if (xs_startswith(cmd, "/v1/timelines/tag/")) { /** **/ 1648 if (xs_startswith(cmd, "/v1/timelines/tag/")) { /** **/
@@ -1661,7 +1691,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1661 1691
1662 *body = xs_json_dumps(out, 4); 1692 *body = xs_json_dumps(out, 4);
1663 *ctype = "application/json"; 1693 *ctype = "application/json";
1664 status = 200; 1694 status = HTTP_STATUS_OK;
1665 } 1695 }
1666 else 1696 else
1667 if (xs_startswith(cmd, "/v1/timelines/list/")) { /** **/ 1697 if (xs_startswith(cmd, "/v1/timelines/list/")) { /** **/
@@ -1729,17 +1759,17 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1729 1759
1730 *body = xs_json_dumps(out, 4); 1760 *body = xs_json_dumps(out, 4);
1731 *ctype = "application/json"; 1761 *ctype = "application/json";
1732 status = 200; 1762 status = HTTP_STATUS_OK;
1733 } 1763 }
1734 else 1764 else
1735 status = 421; 1765 status = HTTP_STATUS_MISDIRECTED_REQUEST;
1736 } 1766 }
1737 else 1767 else
1738 if (strcmp(cmd, "/v1/conversations") == 0) { /** **/ 1768 if (strcmp(cmd, "/v1/conversations") == 0) { /** **/
1739 /* TBD */ 1769 /* TBD */
1740 *body = xs_dup("[]"); 1770 *body = xs_dup("[]");
1741 *ctype = "application/json"; 1771 *ctype = "application/json";
1742 status = 200; 1772 status = HTTP_STATUS_OK;
1743 } 1773 }
1744 else 1774 else
1745 if (strcmp(cmd, "/v1/notifications") == 0) { /** **/ 1775 if (strcmp(cmd, "/v1/notifications") == 0) { /** **/
@@ -1817,17 +1847,17 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1817 1847
1818 *body = xs_json_dumps(out, 4); 1848 *body = xs_json_dumps(out, 4);
1819 *ctype = "application/json"; 1849 *ctype = "application/json";
1820 status = 200; 1850 status = HTTP_STATUS_OK;
1821 } 1851 }
1822 else 1852 else
1823 status = 401; 1853 status = HTTP_STATUS_UNAUTHORIZED;
1824 } 1854 }
1825 else 1855 else
1826 if (strcmp(cmd, "/v1/filters") == 0) { /** **/ 1856 if (strcmp(cmd, "/v1/filters") == 0) { /** **/
1827 /* snac will never have filters */ 1857 /* snac will never have filters */
1828 *body = xs_dup("[]"); 1858 *body = xs_dup("[]");
1829 *ctype = "application/json"; 1859 *ctype = "application/json";
1830 status = 200; 1860 status = HTTP_STATUS_OK;
1831 } 1861 }
1832 else 1862 else
1833 if (strcmp(cmd, "/v2/filters") == 0) { /** **/ 1863 if (strcmp(cmd, "/v2/filters") == 0) { /** **/
@@ -1836,21 +1866,21 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1836 * in some apps */ 1866 * in some apps */
1837 *body = xs_dup("[]"); 1867 *body = xs_dup("[]");
1838 *ctype = "application/json"; 1868 *ctype = "application/json";
1839 status = 200; 1869 status = HTTP_STATUS_OK;
1840 } 1870 }
1841 else 1871 else
1842 if (strcmp(cmd, "/v1/favourites") == 0) { /** **/ 1872 if (strcmp(cmd, "/v1/favourites") == 0) { /** **/
1843 /* snac will never support a list of favourites */ 1873 /* snac will never support a list of favourites */
1844 *body = xs_dup("[]"); 1874 *body = xs_dup("[]");
1845 *ctype = "application/json"; 1875 *ctype = "application/json";
1846 status = 200; 1876 status = HTTP_STATUS_OK;
1847 } 1877 }
1848 else 1878 else
1849 if (strcmp(cmd, "/v1/bookmarks") == 0) { /** **/ 1879 if (strcmp(cmd, "/v1/bookmarks") == 0) { /** **/
1850 /* snac does not support bookmarks */ 1880 /* snac does not support bookmarks */
1851 *body = xs_dup("[]"); 1881 *body = xs_dup("[]");
1852 *ctype = "application/json"; 1882 *ctype = "application/json";
1853 status = 200; 1883 status = HTTP_STATUS_OK;
1854 } 1884 }
1855 else 1885 else
1856 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/ 1886 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/
@@ -1873,7 +1903,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1873 1903
1874 *body = xs_json_dumps(l, 4); 1904 *body = xs_json_dumps(l, 4);
1875 *ctype = "application/json"; 1905 *ctype = "application/json";
1876 status = 200; 1906 status = HTTP_STATUS_OK;
1877 } 1907 }
1878 } 1908 }
1879 else 1909 else
@@ -1903,7 +1933,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1903 1933
1904 *body = xs_json_dumps(out, 4); 1934 *body = xs_json_dumps(out, 4);
1905 *ctype = "application/json"; 1935 *ctype = "application/json";
1906 status = 200; 1936 status = HTTP_STATUS_OK;
1907 } 1937 }
1908 } 1938 }
1909 else 1939 else
@@ -1931,7 +1961,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1931 1961
1932 *body = xs_json_dumps(out, 4); 1962 *body = xs_json_dumps(out, 4);
1933 *ctype = "application/json"; 1963 *ctype = "application/json";
1934 status = 200; 1964 status = HTTP_STATUS_OK;
1935 } 1965 }
1936 } 1966 }
1937 } 1967 }
@@ -1941,28 +1971,28 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1941 /* snac does not schedule notes */ 1971 /* snac does not schedule notes */
1942 *body = xs_dup("[]"); 1972 *body = xs_dup("[]");
1943 *ctype = "application/json"; 1973 *ctype = "application/json";
1944 status = 200; 1974 status = HTTP_STATUS_OK;
1945 } 1975 }
1946 else 1976 else
1947 if (strcmp(cmd, "/v1/follow_requests") == 0) { /** **/ 1977 if (strcmp(cmd, "/v1/follow_requests") == 0) { /** **/
1948 /* snac does not support optional follow confirmations */ 1978 /* snac does not support optional follow confirmations */
1949 *body = xs_dup("[]"); 1979 *body = xs_dup("[]");
1950 *ctype = "application/json"; 1980 *ctype = "application/json";
1951 status = 200; 1981 status = HTTP_STATUS_OK;
1952 } 1982 }
1953 else 1983 else
1954 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/ 1984 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/
1955 /* snac has no announcements (yet?) */ 1985 /* snac has no announcements (yet?) */
1956 *body = xs_dup("[]"); 1986 *body = xs_dup("[]");
1957 *ctype = "application/json"; 1987 *ctype = "application/json";
1958 status = 200; 1988 status = HTTP_STATUS_OK;
1959 } 1989 }
1960 else 1990 else
1961 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/ 1991 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/
1962 /* are you kidding me? */ 1992 /* are you kidding me? */
1963 *body = xs_dup("[]"); 1993 *body = xs_dup("[]");
1964 *ctype = "application/json"; 1994 *ctype = "application/json";
1965 status = 200; 1995 status = HTTP_STATUS_OK;
1966 } 1996 }
1967 else 1997 else
1968 if (strcmp(cmd, "/v1/instance") == 0) { /** **/ 1998 if (strcmp(cmd, "/v1/instance") == 0) { /** **/
@@ -2017,7 +2047,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2017 "\"max_characters\":100000,\"max_media_attachments\":8}"); 2047 "\"max_characters\":100000,\"max_media_attachments\":8}");
2018 cfg = xs_dict_append(cfg, "statuses", d11); 2048 cfg = xs_dict_append(cfg, "statuses", d11);
2019 2049
2020 xs *d12 = xs_json_loads("{\"max_featured_tags\":10}"); 2050 xs *d12 = xs_json_loads("{\"max_featured_tags\":0}");
2021 cfg = xs_dict_append(cfg, "accounts", d12); 2051 cfg = xs_dict_append(cfg, "accounts", d12);
2022 2052
2023 xs *d13 = xs_json_loads("{\"image_matrix_limit\":33177600," 2053 xs *d13 = xs_json_loads("{\"image_matrix_limit\":33177600,"
@@ -2075,7 +2105,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2075 2105
2076 *body = xs_json_dumps(ins, 4); 2106 *body = xs_json_dumps(ins, 4);
2077 *ctype = "application/json"; 2107 *ctype = "application/json";
2078 status = 200; 2108 status = HTTP_STATUS_OK;
2079 } 2109 }
2080 else 2110 else
2081 if (xs_startswith(cmd, "/v1/statuses/")) { /** **/ 2111 if (xs_startswith(cmd, "/v1/statuses/")) { /** **/
@@ -2188,30 +2218,30 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2188 if (out != NULL) { 2218 if (out != NULL) {
2189 *body = xs_json_dumps(out, 4); 2219 *body = xs_json_dumps(out, 4);
2190 *ctype = "application/json"; 2220 *ctype = "application/json";
2191 status = 200; 2221 status = HTTP_STATUS_OK;
2192 } 2222 }
2193 } 2223 }
2194 } 2224 }
2195 else 2225 else
2196 status = 401; 2226 status = HTTP_STATUS_UNAUTHORIZED;
2197 } 2227 }
2198 else 2228 else
2199 if (strcmp(cmd, "/v1/preferences") == 0) { /** **/ 2229 if (strcmp(cmd, "/v1/preferences") == 0) { /** **/
2200 *body = xs_dup("{}"); 2230 *body = xs_dup("{}");
2201 *ctype = "application/json"; 2231 *ctype = "application/json";
2202 status = 200; 2232 status = HTTP_STATUS_OK;
2203 } 2233 }
2204 else 2234 else
2205 if (strcmp(cmd, "/v1/markers") == 0) { /** **/ 2235 if (strcmp(cmd, "/v1/markers") == 0) { /** **/
2206 *body = xs_dup("{}"); 2236 *body = xs_dup("{}");
2207 *ctype = "application/json"; 2237 *ctype = "application/json";
2208 status = 200; 2238 status = HTTP_STATUS_OK;
2209 } 2239 }
2210 else 2240 else
2211 if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/ 2241 if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/
2212 *body = xs_dup("[]"); 2242 *body = xs_dup("[]");
2213 *ctype = "application/json"; 2243 *ctype = "application/json";
2214 status = 200; 2244 status = HTTP_STATUS_OK;
2215 } 2245 }
2216 else 2246 else
2217 if (strcmp(cmd, "/v2/search") == 0) { /** **/ 2247 if (strcmp(cmd, "/v2/search") == 0) { /** **/
@@ -2290,10 +2320,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2290 2320
2291 *body = xs_json_dumps(res, 4); 2321 *body = xs_json_dumps(res, 4);
2292 *ctype = "application/json"; 2322 *ctype = "application/json";
2293 status = 200; 2323 status = HTTP_STATUS_OK;
2294 } 2324 }
2295 else 2325 else
2296 status = 401; 2326 status = HTTP_STATUS_UNAUTHORIZED;
2297 } 2327 }
2298 2328
2299 /* user cleanup */ 2329 /* user cleanup */
@@ -2316,7 +2346,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2316 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2346 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2317 return 0; 2347 return 0;
2318 2348
2319 int status = 404; 2349 int status = HTTP_STATUS_NOT_FOUND;
2320 xs *args = NULL; 2350 xs *args = NULL;
2321 const char *i_ctype = xs_dict_get(req, "content-type"); 2351 const char *i_ctype = xs_dict_get(req, "content-type");
2322 2352
@@ -2336,7 +2366,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2336 args = xs_dup(xs_dict_get(req, "p_vars")); 2366 args = xs_dup(xs_dict_get(req, "p_vars"));
2337 2367
2338 if (args == NULL) 2368 if (args == NULL)
2339 return 400; 2369 return HTTP_STATUS_BAD_REQUEST;
2340 2370
2341 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 2371 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
2342 2372
@@ -2378,7 +2408,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2378 2408
2379 *body = xs_json_dumps(app, 4); 2409 *body = xs_json_dumps(app, 4);
2380 *ctype = "application/json"; 2410 *ctype = "application/json";
2381 status = 200; 2411 status = HTTP_STATUS_OK;
2382 2412
2383 app = xs_dict_append(app, "code", ""); 2413 app = xs_dict_append(app, "code", "");
2384 2414
@@ -2470,10 +2500,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2470 2500
2471 *body = xs_json_dumps(st, 4); 2501 *body = xs_json_dumps(st, 4);
2472 *ctype = "application/json"; 2502 *ctype = "application/json";
2473 status = 200; 2503 status = HTTP_STATUS_OK;
2474 } 2504 }
2475 else 2505 else
2476 status = 401; 2506 status = HTTP_STATUS_UNAUTHORIZED;
2477 } 2507 }
2478 else 2508 else
2479 if (xs_startswith(cmd, "/v1/statuses")) { /** **/ 2509 if (xs_startswith(cmd, "/v1/statuses")) { /** **/
@@ -2552,7 +2582,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2552 if (pin(&snac, id)) 2582 if (pin(&snac, id))
2553 out = mastoapi_status(&snac, msg); 2583 out = mastoapi_status(&snac, msg);
2554 else 2584 else
2555 status = 422; 2585 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2556 } 2586 }
2557 else 2587 else
2558 if (strcmp(op, "unpin") == 0) { /** **/ 2588 if (strcmp(op, "unpin") == 0) { /** **/
@@ -2573,12 +2603,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2573 if (out != NULL) { 2603 if (out != NULL) {
2574 *body = xs_json_dumps(out, 4); 2604 *body = xs_json_dumps(out, 4);
2575 *ctype = "application/json"; 2605 *ctype = "application/json";
2576 status = 200; 2606 status = HTTP_STATUS_OK;
2577 } 2607 }
2578 } 2608 }
2579 } 2609 }
2580 else 2610 else
2581 status = 401; 2611 status = HTTP_STATUS_UNAUTHORIZED;
2582 } 2612 }
2583 else 2613 else
2584 if (strcmp(cmd, "/v1/notifications/clear") == 0) { /** **/ 2614 if (strcmp(cmd, "/v1/notifications/clear") == 0) { /** **/
@@ -2588,10 +2618,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2588 2618
2589 *body = xs_dup("{}"); 2619 *body = xs_dup("{}");
2590 *ctype = "application/json"; 2620 *ctype = "application/json";
2591 status = 200; 2621 status = HTTP_STATUS_OK;
2592 } 2622 }
2593 else 2623 else
2594 status = 401; 2624 status = HTTP_STATUS_UNAUTHORIZED;
2595 } 2625 }
2596 else 2626 else
2597 if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/ 2627 if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/
@@ -2616,10 +2646,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2616 2646
2617 *body = xs_json_dumps(wpush, 4); 2647 *body = xs_json_dumps(wpush, 4);
2618 *ctype = "application/json"; 2648 *ctype = "application/json";
2619 status = 200; 2649 status = HTTP_STATUS_OK;
2620 } 2650 }
2621 else 2651 else
2622 status = 401; 2652 status = HTTP_STATUS_UNAUTHORIZED;
2623 } 2653 }
2624 else 2654 else
2625 if (strcmp(cmd, "/v1/media") == 0 || strcmp(cmd, "/v2/media") == 0) { /** **/ 2655 if (strcmp(cmd, "/v1/media") == 0 || strcmp(cmd, "/v2/media") == 0) { /** **/
@@ -2630,7 +2660,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2630 if (xs_is_null(desc)) 2660 if (xs_is_null(desc))
2631 desc = ""; 2661 desc = "";
2632 2662
2633 status = 400; 2663 status = HTTP_STATUS_BAD_REQUEST;
2634 2664
2635 if (xs_type(file) == XSTYPE_LIST) { 2665 if (xs_type(file) == XSTYPE_LIST) {
2636 const char *fn = xs_list_get(file, 0); 2666 const char *fn = xs_list_get(file, 0);
@@ -2659,12 +2689,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2659 2689
2660 *body = xs_json_dumps(rsp, 4); 2690 *body = xs_json_dumps(rsp, 4);
2661 *ctype = "application/json"; 2691 *ctype = "application/json";
2662 status = 200; 2692 status = HTTP_STATUS_OK;
2663 } 2693 }
2664 } 2694 }
2665 } 2695 }
2666 else 2696 else
2667 status = 401; 2697 status = HTTP_STATUS_UNAUTHORIZED;
2668 } 2698 }
2669 else 2699 else
2670 if (xs_startswith(cmd, "/v1/accounts")) { /** **/ 2700 if (xs_startswith(cmd, "/v1/accounts")) { /** **/
@@ -2744,11 +2774,11 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2744 if (rsp != NULL) { 2774 if (rsp != NULL) {
2745 *body = xs_json_dumps(rsp, 4); 2775 *body = xs_json_dumps(rsp, 4);
2746 *ctype = "application/json"; 2776 *ctype = "application/json";
2747 status = 200; 2777 status = HTTP_STATUS_OK;
2748 } 2778 }
2749 } 2779 }
2750 else 2780 else
2751 status = 401; 2781 status = HTTP_STATUS_UNAUTHORIZED;
2752 } 2782 }
2753 else 2783 else
2754 if (xs_startswith(cmd, "/v1/polls")) { /** **/ 2784 if (xs_startswith(cmd, "/v1/polls")) { /** **/
@@ -2810,12 +2840,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2810 if (out != NULL) { 2840 if (out != NULL) {
2811 *body = xs_json_dumps(out, 4); 2841 *body = xs_json_dumps(out, 4);
2812 *ctype = "application/json"; 2842 *ctype = "application/json";
2813 status = 200; 2843 status = HTTP_STATUS_OK;
2814 } 2844 }
2815 } 2845 }
2816 } 2846 }
2817 else 2847 else
2818 status = 401; 2848 status = HTTP_STATUS_UNAUTHORIZED;
2819 } 2849 }
2820 else 2850 else
2821 if (strcmp(cmd, "/v1/lists") == 0) { 2851 if (strcmp(cmd, "/v1/lists") == 0) {
@@ -2831,18 +2861,18 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2831 out = xs_dict_append(out, "replies_policy", xs_dict_get_def(args, "replies_policy", "list")); 2861 out = xs_dict_append(out, "replies_policy", xs_dict_get_def(args, "replies_policy", "list"));
2832 out = xs_dict_append(out, "exclusive", xs_stock(XSTYPE_FALSE)); 2862 out = xs_dict_append(out, "exclusive", xs_stock(XSTYPE_FALSE));
2833 2863
2834 status = 200; 2864 status = HTTP_STATUS_OK;
2835 } 2865 }
2836 else { 2866 else {
2837 out = xs_dict_append(out, "error", "cannot create list"); 2867 out = xs_dict_append(out, "error", "cannot create list");
2838 status = 422; 2868 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2839 } 2869 }
2840 2870
2841 *body = xs_json_dumps(out, 4); 2871 *body = xs_json_dumps(out, 4);
2842 *ctype = "application/json"; 2872 *ctype = "application/json";
2843 } 2873 }
2844 else 2874 else
2845 status = 422; 2875 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2846 } 2876 }
2847 } 2877 }
2848 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/ 2878 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/
@@ -2861,12 +2891,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2861 list_content(&snac, id, v, 1); 2891 list_content(&snac, id, v, 1);
2862 } 2892 }
2863 2893
2864 status = 200; 2894 status = HTTP_STATUS_OK;
2865 } 2895 }
2866 } 2896 }
2867 } 2897 }
2868 else 2898 else
2869 status = 422; 2899 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2870 } 2900 }
2871 2901
2872 /* user cleanup */ 2902 /* user cleanup */
@@ -2891,7 +2921,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2891 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2921 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2892 return 0; 2922 return 0;
2893 2923
2894 int status = 404; 2924 int status = HTTP_STATUS_NOT_FOUND;
2895 xs *args = NULL; 2925 xs *args = NULL;
2896 const char *i_ctype = xs_dict_get(req, "content-type"); 2926 const char *i_ctype = xs_dict_get(req, "content-type");
2897 2927
@@ -2911,7 +2941,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2911 args = xs_dup(xs_dict_get(req, "p_vars")); 2941 args = xs_dup(xs_dict_get(req, "p_vars"));
2912 2942
2913 if (args == NULL) 2943 if (args == NULL)
2914 return 400; 2944 return HTTP_STATUS_BAD_REQUEST;
2915 2945
2916 snac snac = {0}; 2946 snac snac = {0};
2917 int logged_in = process_auth_token(&snac, req); 2947 int logged_in = process_auth_token(&snac, req);
@@ -2920,7 +2950,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2920 2950
2921 if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/ 2951 if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/
2922 // pretend we deleted it, since it doesn't exist anyway 2952 // pretend we deleted it, since it doesn't exist anyway
2923 status = 200; 2953 status = HTTP_STATUS_OK;
2924 } 2954 }
2925 else 2955 else
2926 if (xs_startswith(cmd, "/v1/lists/")) { 2956 if (xs_startswith(cmd, "/v1/lists/")) {
@@ -2948,10 +2978,10 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2948 } 2978 }
2949 } 2979 }
2950 2980
2951 status = 200; 2981 status = HTTP_STATUS_OK;
2952 } 2982 }
2953 else 2983 else
2954 status = 401; 2984 status = HTTP_STATUS_UNAUTHORIZED;
2955 } 2985 }
2956 2986
2957 /* user cleanup */ 2987 /* user cleanup */
@@ -2974,7 +3004,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
2974 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 3004 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2975 return 0; 3005 return 0;
2976 3006
2977 int status = 404; 3007 int status = HTTP_STATUS_NOT_FOUND;
2978 xs *args = NULL; 3008 xs *args = NULL;
2979 const char *i_ctype = xs_dict_get(req, "content-type"); 3009 const char *i_ctype = xs_dict_get(req, "content-type");
2980 3010
@@ -2986,7 +3016,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
2986 args = xs_dup(xs_dict_get(req, "p_vars")); 3016 args = xs_dup(xs_dict_get(req, "p_vars"));
2987 3017
2988 if (args == NULL) 3018 if (args == NULL)
2989 return 400; 3019 return HTTP_STATUS_BAD_REQUEST;
2990 3020
2991 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 3021 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
2992 3022
@@ -3017,11 +3047,11 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
3017 3047
3018 *body = xs_json_dumps(rsp, 4); 3048 *body = xs_json_dumps(rsp, 4);
3019 *ctype = "application/json"; 3049 *ctype = "application/json";
3020 status = 200; 3050 status = HTTP_STATUS_OK;
3021 } 3051 }
3022 } 3052 }
3023 else 3053 else
3024 status = 401; 3054 status = HTTP_STATUS_UNAUTHORIZED;
3025 } 3055 }
3026 else 3056 else
3027 if (xs_startswith(cmd, "/v1/statuses")) { 3057 if (xs_startswith(cmd, "/v1/statuses")) {
@@ -3060,12 +3090,12 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
3060 if (rsp != NULL) { 3090 if (rsp != NULL) {
3061 *body = xs_json_dumps(rsp, 4); 3091 *body = xs_json_dumps(rsp, 4);
3062 *ctype = "application/json"; 3092 *ctype = "application/json";
3063 status = 200; 3093 status = HTTP_STATUS_OK;
3064 } 3094 }
3065 } 3095 }
3066 } 3096 }
3067 else 3097 else
3068 status = 401; 3098 status = HTTP_STATUS_UNAUTHORIZED;
3069 } 3099 }
3070 3100
3071 /* user cleanup */ 3101 /* user cleanup */
@@ -3077,6 +3107,158 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
3077 return status; 3107 return status;
3078} 3108}
3079 3109
3110void persist_image(const char *key, const xs_val *data, const char *payload, snac *snac)
3111/* Store header or avatar */
3112{
3113 if (data != NULL) {
3114 if (xs_type(data) == XSTYPE_LIST) {
3115 const char *fn = xs_list_get(data, 0);
3116
3117 if (fn && *fn) {
3118 const char *ext = strrchr(fn, '.');
3119
3120 /* Mona iOS sends always jpg as application/octet-stream with no filename */
3121 if (ext == NULL || strcmp(fn, key) == 0) {
3122 ext = ".jpg";
3123 }
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));
3129 xs *id = xs_fmt("%s%s", hash, ext);
3130 xs *url = xs_fmt("%s/s/%s", snac->actor, id);
3131 int fo = xs_number_get(xs_list_get(data, 1));
3132 int fs = xs_number_get(xs_list_get(data, 2));
3133
3134 /* store */
3135 static_put(snac, id, payload + fo, fs);
3136
3137 snac->config = xs_dict_set(snac->config, key, url);
3138 }
3139 }
3140 }
3141}
3142
3143int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
3144 const char *payload, int p_size,
3145 char **body, int *b_size, char **ctype)
3146/* Handle profile updates */
3147{
3148 (void)p_size;
3149 (void)b_size;
3150
3151 if (!xs_startswith(q_path, "/api/v1/"))
3152 return 0;
3153
3154 int status = HTTP_STATUS_NOT_FOUND;
3155 xs *args = NULL;
3156 const char *i_ctype = xs_dict_get(req, "content-type");
3157
3158 if (i_ctype && xs_startswith(i_ctype, "application/json")) {
3159 if (!xs_is_null(payload))
3160 args = xs_json_loads(payload);
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 }
3170 else
3171 args = xs_dup(xs_dict_get(req, "p_vars"));
3172
3173 if (args == NULL)
3174 return HTTP_STATUS_BAD_REQUEST;
3175
3176 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
3177
3178 snac snac = {0};
3179 int logged_in = process_auth_token(&snac, req);
3180
3181 if (xs_startswith(cmd, "/v1/accounts/update_credentials")) {
3182 /* Update user profile fields */
3183 if (logged_in) {
3184 int c = 0;
3185 const xs_str *k;
3186 const xs_val *v;
3187 const xs_str *field_name = NULL;
3188 xs_dict *new_fields = xs_dict_new();
3189 while (xs_dict_next(args, &k, &v, &c)) {
3190 if (strcmp(k, "display_name") == 0) {
3191 if (v != NULL)
3192 snac.config = xs_dict_set(snac.config, "name", v);
3193 }
3194 else
3195 if (strcmp(k, "note") == 0) {
3196 if (v != NULL)
3197 snac.config = xs_dict_set(snac.config, "bio", v);
3198 }
3199 else
3200 if (strcmp(k, "bot") == 0) {
3201 if (v != NULL)
3202 snac.config = xs_dict_set(snac.config, "bot",
3203 (strcmp(v, "true") == 0 ||
3204 strcmp(v, "1") == 0) ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
3205 }
3206 else
3207 if (strcmp(k, "source[sensitive]") == 0) {
3208 if (v != NULL)
3209 snac.config = xs_dict_set(snac.config, "cw",
3210 strcmp(v, "true") == 0 ? "open" : "");
3211 }
3212 else
3213 if (strcmp(k, "source[privacy]") == 0) {
3214 if (v != NULL)
3215 snac.config = xs_dict_set(snac.config, "private",
3216 strcmp(v, "private") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
3217 }
3218 else
3219 if (strcmp(k, "header") == 0) {
3220 persist_image("header", v, payload, &snac);
3221 }
3222 else
3223 if (strcmp(k, "avatar") == 0) {
3224 persist_image("avatar", v, payload, &snac);
3225 }
3226 else
3227 if (xs_starts_and_ends("fields_attributes", k, "[name]")) {
3228 field_name = strcmp(v, "") != 0 ? v : NULL;
3229 }
3230 else
3231 if (xs_starts_and_ends("fields_attributes", k, "[value]")) {
3232 if (field_name != NULL) {
3233 new_fields = xs_dict_set(new_fields, field_name, v);
3234 snac.config = xs_dict_set(snac.config, "metadata", new_fields);
3235 }
3236 }
3237 /* we don't have support for the following options, yet
3238 - discoverable (0/1)
3239 - locked (0/1)
3240 */
3241 }
3242
3243 /* Persist profile */
3244 if (user_persist(&snac) == 0)
3245 credentials_get(body, ctype, &status, snac);
3246 else
3247 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
3248 }
3249 else
3250 status = HTTP_STATUS_UNAUTHORIZED;
3251 }
3252
3253 /* user cleanup */
3254 if (logged_in)
3255 user_free(&snac);
3256
3257 srv_debug(1, xs_fmt("mastoapi_patch_handler %s %d", q_path, status));
3258
3259 return status;
3260}
3261
3080 3262
3081void mastoapi_purge(void) 3263void mastoapi_purge(void)
3082{ 3264{