From 0e21d35e802bf859aa14bce688cd9544458e9e9c Mon Sep 17 00:00:00 2001 From: Louis Brauer Date: Sun, 26 May 2024 21:45:41 +0200 Subject: Use enum instead of numeric status codes for HTTP statuses --- snac.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'snac.h') diff --git a/snac.h b/snac.h index 79e144a..d79f3ea 100644 --- a/snac.h +++ b/snac.h @@ -361,3 +361,53 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path, void mastoapi_purge(void); void verify_links(snac *user); + +/* HTTP responses RFC 9110 plus some extensions */ + +typedef enum { + HTTP_STATUS_CONTINUE = 100, + HTTP_STATUS_SWITCHTING_PROTOCOLS = 101, + HTTP_STATUS_PROCESSING = 102, + HTTP_STATUS_EARLY_HINTS = 103, + HTTP_STATUS_OK = 200, + HTTP_STATUS_CREATED = 201, + HTTP_STATUS_ACCEPTED = 202, + HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_RESET_CONTENT = 205, + HTTP_STATUS_PARTIAL_CONTENT = 206, + HTTP_STATUS_MULTI_STATUS = 207, + HTTP_STATUS_ALREADY_REPORTED = 208, + HTTP_STATUS_THIS_IS_FINE = 218, + HTTP_STATUS_IM_USED = 226, + HTTP_STATUS_MULTIPLE_CHOICES = 300, + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + HTTP_STATUS_USE_PROXY = 305, + HTTP_STATUS_SWITCH_PROXY = 306, + HTTP_STATUS_TEMPORARY_REDIRECT = 307, + HTTP_STATUS_PERMANENT_REDIRECT = 308, + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED = 401, + HTTP_STATUS_PAYMENT_REQUIRED = 402, + HTTP_STATUS_FORBIDDEN = 403, + HTTP_STATUS_NOT_FOUND = 404, + HTTP_STATUS_METHOD_NOT_ALLOWED = 405, + HTTP_STATUS_NOT_ACCEPTABLE = 406, + HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_STATUS_REQUEST_TIMEOUT = 408, + HTTP_STATUS_CONFLICT = 409, + HTTP_STATUS_GONE = 410, + HTTP_STATUS_MISDIRECTED_REQUEST = 421, + HTTP_STATUS_UNPROCESSABLE_CONTENT = 422, + HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED = 501, + HTTP_STATUS_BAD_GATEWAY = 502, + HTTP_STATUS_SERVICE_UNAVAILABLE = 503, + HTTP_STATUS_GATEWAY_TIMEOUT = 504, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_STATUS_INSUFFICIENT_STORAGE = 507 +} http_status; -- cgit v1.2.3 From 81cf309e4d0ba6c2debccc21ea4f85e1e6245dc5 Mon Sep 17 00:00:00 2001 From: Louis Brauer Date: Mon, 27 May 2024 12:24:17 +0200 Subject: Implement Mastodon PATCH endpoint for account profile updates --- data.c | 27 ++++++ html.c | 22 +---- httpd.c | 10 ++ mastoapi.c | 316 +++++++++++++++++++++++++++++++++++++++++++++---------------- snac.h | 5 + 5 files changed, 279 insertions(+), 101 deletions(-) (limited to 'snac.h') diff --git a/data.c b/data.c index 8fdd292..e24bf16 100644 --- a/data.c +++ b/data.c @@ -303,6 +303,33 @@ int user_open_by_md5(snac *snac, const char *md5) return 0; } +int user_persist(snac *snac) +/* store user */ +{ + xs *fn = xs_fmt("%s/user.json", snac->basedir); + xs *bfn = xs_fmt("%s.bak", fn); + FILE *f; + + rename(fn, bfn); + + if ((f = fopen(fn, "w")) != NULL) { + xs_json_dump(snac->config, 4, f); + fclose(f); + } + else + rename(bfn, fn); + + history_del(snac, "timeline.html_"); + + xs *a_msg = msg_actor(snac); + xs *u_msg = msg_update(snac, a_msg); + + enqueue_message(snac, u_msg); + enqueue_verify_links(snac); + + return 0; +} + double mtime_nl(const char *fn, int *n_link) /* returns the mtime and number of links of a file or directory, or 0.0 */ diff --git a/html.c b/html.c index 1840799..6267adf 100644 --- a/html.c +++ b/html.c @@ -3334,27 +3334,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, snac.config = xs_dict_set(snac.config, "passwd", pw); } - xs *fn = xs_fmt("%s/user.json", snac.basedir); - xs *bfn = xs_fmt("%s.bak", fn); - FILE *f; - - rename(fn, bfn); - - if ((f = fopen(fn, "w")) != NULL) { - xs_json_dump(snac.config, 4, f); - fclose(f); - } - else - rename(bfn, fn); - - history_del(&snac, "timeline.html_"); - - xs *a_msg = msg_actor(&snac); - xs *u_msg = msg_update(&snac, a_msg); - - enqueue_message(&snac, u_msg); - - enqueue_verify_links(&snac); + user_persist(&snac); status = HTTP_STATUS_SEE_OTHER; } diff --git a/httpd.c b/httpd.c index 60afe28..1c60a56 100644 --- a/httpd.c +++ b/httpd.c @@ -360,6 +360,16 @@ void httpd_connection(FILE *f) payload, p_size, &body, &b_size, &ctype); #endif + } + else + if (strcmp(method, "PATCH") == 0) { + +#ifndef NO_MASTODON_API + if (status == 0) + status = mastoapi_patch_handler(req, q_path, + payload, p_size, &body, &b_size, &ctype); +#endif + } else if (strcmp(method, "OPTIONS") == 0) { diff --git a/mastoapi.c b/mastoapi.c index 8a54230..120c1aa 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1150,106 +1150,120 @@ int process_auth_token(snac *snac, const xs_dict *req) return logged_in; } - -int mastoapi_get_handler(const xs_dict *req, const char *q_path, - char **body, int *b_size, char **ctype) +void credentials_get(char **body, char **ctype, int *status, snac snac) { - (void)b_size; + xs *acct = xs_dict_new(); + + acct = xs_dict_append(acct, "id", snac.md5); + acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid")); + acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid")); + acct = xs_dict_append(acct, "display_name", xs_dict_get(snac.config, "name")); + acct = xs_dict_append(acct, "created_at", xs_dict_get(snac.config, "published")); + acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac.config, "published")); + acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio")); + acct = xs_dict_append(acct, "url", snac.actor); + acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); + acct = xs_dict_append(acct, "bot", xs_dict_get(snac.config, "bot")); - if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) - return 0; + xs *src = xs_json_loads("{\"privacy\":\"public\"," + "\"sensitive\":false,\"fields\":[],\"note\":\"\"}"); + /* some apps take the note from the source object */ + src = xs_dict_set(src, "note", xs_dict_get(snac.config, "bio")); + src = xs_dict_set(src, "privacy", xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE ? "private" : "public"); - int status = HTTP_STATUS_NOT_FOUND; - const xs_dict *args = xs_dict_get(req, "q_vars"); - xs *cmd = xs_replace_n(q_path, "/api", "", 1); + const xs_str *cw = xs_dict_get(snac.config, "cw"); + src = xs_dict_set(src, "sensitive", + strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); - snac snac1 = {0}; - int logged_in = process_auth_token(&snac1, req); + src = xs_dict_set(src, "bot", xs_dict_get(snac.config, "bot")); - if (strcmp(cmd, "/v1/accounts/verify_credentials") == 0) { /** **/ - if (logged_in) { - xs *acct = xs_dict_new(); - - acct = xs_dict_append(acct, "id", snac1.md5); - acct = xs_dict_append(acct, "username", xs_dict_get(snac1.config, "uid")); - acct = xs_dict_append(acct, "acct", xs_dict_get(snac1.config, "uid")); - acct = xs_dict_append(acct, "display_name", xs_dict_get(snac1.config, "name")); - acct = xs_dict_append(acct, "created_at", xs_dict_get(snac1.config, "published")); - acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac1.config, "published")); - acct = xs_dict_append(acct, "note", xs_dict_get(snac1.config, "bio")); - acct = xs_dict_append(acct, "url", snac1.actor); - acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); - acct = xs_dict_append(acct, "bot", xs_dict_get(snac1.config, "bot")); - - xs *src = xs_json_loads("{\"privacy\":\"public\"," - "\"sensitive\":false,\"fields\":[],\"note\":\"\"}"); - acct = xs_dict_append(acct, "source", src); - - xs *avatar = NULL; - const char *av = xs_dict_get(snac1.config, "avatar"); - - if (xs_is_null(av) || *av == '\0') - avatar = xs_fmt("%s/susie.png", srv_baseurl); - else - avatar = xs_dup(av); + xs *avatar = NULL; + const char *av = xs_dict_get(snac.config, "avatar"); - acct = xs_dict_append(acct, "avatar", avatar); - acct = xs_dict_append(acct, "avatar_static", avatar); + if (xs_is_null(av) || *av == '\0') + avatar = xs_fmt("%s/susie.png", srv_baseurl); + else + avatar = xs_dup(av); - xs *header = NULL; - const char *hd = xs_dict_get(snac1.config, "header"); + acct = xs_dict_append(acct, "avatar", avatar); + acct = xs_dict_append(acct, "avatar_static", avatar); - if (!xs_is_null(hd)) - header = xs_dup(hd); - else - header = xs_fmt("%s/header.png", srv_baseurl); + xs *header = NULL; + const char *hd = xs_dict_get(snac.config, "header"); - acct = xs_dict_append(acct, "header", header); - acct = xs_dict_append(acct, "header_static", header); + if (!xs_is_null(hd)) + header = xs_dup(hd); + else + header = xs_fmt("%s/header.png", srv_baseurl); - const xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); - if (xs_type(metadata) == XSTYPE_DICT) { - xs *fields = xs_list_new(); - const xs_str *k; - const xs_str *v; + acct = xs_dict_append(acct, "header", header); + acct = xs_dict_append(acct, "header_static", header); - xs_dict *val_links = snac1.links; - if (xs_is_null(val_links)) - val_links = xs_stock(XSTYPE_DICT); + const xs_dict *metadata = xs_dict_get(snac.config, "metadata"); + if (xs_type(metadata) == XSTYPE_DICT) { + xs *fields = xs_list_new(); + const xs_str *k; + const xs_str *v; - int c = 0; - while (xs_dict_next(metadata, &k, &v, &c)) { - xs *val_date = NULL; + xs_dict *val_links = snac.links; + if (xs_is_null(val_links)) + val_links = xs_stock(XSTYPE_DICT); - const xs_number *verified_time = xs_dict_get(val_links, v); - if (xs_type(verified_time) == XSTYPE_NUMBER) { - time_t t = xs_number_get(verified_time); + int c = 0; + while (xs_dict_next(metadata, &k, &v, &c)) { + xs *val_date = NULL; - if (t > 0) - val_date = xs_str_utctime(t, ISO_DATE_SPEC); - } + const xs_number *verified_time = xs_dict_get(val_links, v); + if (xs_type(verified_time) == XSTYPE_NUMBER) { + time_t t = xs_number_get(verified_time); - xs *d = xs_dict_new(); + if (t > 0) + val_date = xs_str_utctime(t, ISO_DATE_SPEC); + } - d = xs_dict_append(d, "name", k); - d = xs_dict_append(d, "value", v); - d = xs_dict_append(d, "verified_at", - xs_type(val_date) == XSTYPE_STRING && *val_date ? - val_date : xs_stock(XSTYPE_NULL)); + xs *d = xs_dict_new(); - fields = xs_list_append(fields, d); - } + d = xs_dict_append(d, "name", k); + d = xs_dict_append(d, "value", v); + d = xs_dict_append(d, "verified_at", + xs_type(val_date) == XSTYPE_STRING && *val_date ? val_date : xs_stock(XSTYPE_NULL)); - acct = xs_dict_set(acct, "fields", fields); - } + fields = xs_list_append(fields, d); + } - acct = xs_dict_append(acct, "followers_count", xs_stock(0)); - acct = xs_dict_append(acct, "following_count", xs_stock(0)); - acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); + acct = xs_dict_set(acct, "fields", fields); + /* some apps take the fields from the source object */ + src = xs_dict_set(src, "fields", fields); + } - *body = xs_json_dumps(acct, 4); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + acct = xs_dict_append(acct, "source", src); + acct = xs_dict_append(acct, "followers_count", xs_stock(0)); + acct = xs_dict_append(acct, "following_count", xs_stock(0)); + acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); + + *body = xs_json_dumps(acct, 4); + *ctype = "application/json"; + *status = HTTP_STATUS_OK; +} + +int mastoapi_get_handler(const xs_dict *req, const char *q_path, + char **body, int *b_size, char **ctype) +{ + (void)b_size; + + if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) + return 0; + + int status = HTTP_STATUS_NOT_FOUND; + const xs_dict *args = xs_dict_get(req, "q_vars"); + xs *cmd = xs_replace_n(q_path, "/api", "", 1); + + snac snac1 = {0}; + int logged_in = process_auth_token(&snac1, req); + + if (strcmp(cmd, "/v1/accounts/verify_credentials") == 0) { /** **/ + if (logged_in) { + credentials_get(body, ctype, &status, snac1); } else { status = HTTP_STATUS_UNPROCESSABLE_CONTENT; // (no login) @@ -3077,6 +3091,148 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path, return status; } +void persist_image(const char *key, const xs_val *data, const char *payload, snac *snac) +/* Store header or avatar */ +{ + if (data != NULL) { + if (xs_type(data) == XSTYPE_LIST) { + const char *fn = xs_list_get(data, 0); + + 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. + */ + if (ext == NULL || strcmp(fn, key) == 0) { + fn = random_str(); + ext = ".jpg"; + } + xs *hash = xs_md5_hex(fn, strlen(fn)); + 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)); + int fs = xs_number_get(xs_list_get(data, 2)); + + /* store */ + static_put(snac, id, payload + fo, fs); + + snac->config = xs_dict_set(snac->config, key, url); + } + } + } +} + +int mastoapi_patch_handler(const xs_dict *req, const char *q_path, + const char *payload, int p_size, + char **body, int *b_size, char **ctype) +/* Handle profile updates */ +{ + (void)p_size; + (void)b_size; + + if (!xs_startswith(q_path, "/api/v1/")) + return 0; + + int status = HTTP_STATUS_NOT_FOUND; + xs *args = NULL; + const char *i_ctype = xs_dict_get(req, "content-type"); + + if (i_ctype && xs_startswith(i_ctype, "application/json")) { + if (!xs_is_null(payload)) + args = xs_json_loads(payload); + } + else + args = xs_dup(xs_dict_get(req, "p_vars")); + + if (args == NULL) + return HTTP_STATUS_BAD_REQUEST; + + xs *cmd = xs_replace_n(q_path, "/api", "", 1); + + snac snac = {0}; + int logged_in = process_auth_token(&snac, req); + + 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; + const xs_str *field_name = NULL; + xs_dict *new_fields = xs_dict_new(); + while (xs_dict_next(args, &k, &v, &c)) { + if (strcmp(k, "display_name") == 0) { + if (v != NULL) + snac.config = xs_dict_set(snac.config, "name", v); + } + else + if (strcmp(k, "note") == 0) { + if (v != NULL) + snac.config = xs_dict_set(snac.config, "bio", v); + } + else + 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)); + } + else + if (strcmp(k, "source[sensitive]") == 0) { + if (v != NULL) + snac.config = xs_dict_set(snac.config, "cw", + strcmp(v, "true") == 0 ? "open" : ""); + } + else + if (strcmp(k, "source[privacy]") == 0) { + if (v != NULL) + snac.config = xs_dict_set(snac.config, "private", + strcmp(v, "private") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); + } + else + if (strcmp(k, "header") == 0) { + persist_image("header", v, payload, &snac); + } + else + if (strcmp(k, "avatar") == 0) { + persist_image("avatar", v, payload, &snac); + } + else + if (xs_starts_and_ends("fields_attributes", k, "[name]")) { + field_name = strcmp(v, "") != 0 ? v : NULL; + } + else + if (xs_starts_and_ends("fields_attributes", k, "[value]")) { + if (field_name != NULL) { + new_fields = xs_dict_set(new_fields, field_name, v); + snac.config = xs_dict_set(snac.config, "metadata", new_fields); + } + } + } + + /* Persist profile */ + if (user_persist(&snac) == 0) + credentials_get(body, ctype, &status, snac); + else + status = HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + else + status = HTTP_STATUS_UNAUTHORIZED; + } + + /* user cleanup */ + if (logged_in) + user_free(&snac); + + srv_debug(1, xs_fmt("mastoapi_patch_handler %s %d", q_path, status)); + + return status; +} + void mastoapi_purge(void) { diff --git a/snac.h b/snac.h index d79f3ea..5c2f731 100644 --- a/snac.h +++ b/snac.h @@ -76,6 +76,7 @@ int user_open(snac *snac, const char *uid); void user_free(snac *snac); xs_list *user_list(void); int user_open_by_md5(snac *snac, const char *md5); +int user_persist(snac *snac); int validate_uid(const char *uid); @@ -358,6 +359,10 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, int mastoapi_put_handler(const xs_dict *req, const char *q_path, const char *payload, int p_size, char **body, int *b_size, char **ctype); +void persist_image(const char *key, const xs_val *data, const char *payload, snac *snac); +int mastoapi_patch_handler(const xs_dict *req, const char *q_path, + const char *payload, int p_size, + char **body, int *b_size, char **ctype); void mastoapi_purge(void); void verify_links(snac *user); -- cgit v1.2.3 From 26fbda787d0d5c8ba9259f79f4d2f937bd6c8ead Mon Sep 17 00:00:00 2001 From: Louis Brauer Date: Mon, 27 May 2024 16:25:20 +0200 Subject: Translate status codes to canonical status texts Use those in HTTP responses instead of "OK"/"ERROR". Apps like Tokodon show only the status text in unexpected responses. --- http_codes.h | 45 +++++++++++++++++++++++++++++++++++++++++++++ httpd.c | 2 +- snac.c | 12 ++++++++++++ snac.h | 51 +++++---------------------------------------------- xs_httpd.h | 6 +++--- 5 files changed, 66 insertions(+), 50 deletions(-) create mode 100644 http_codes.h (limited to 'snac.h') diff --git a/http_codes.h b/http_codes.h new file mode 100644 index 0000000..795f92a --- /dev/null +++ b/http_codes.h @@ -0,0 +1,45 @@ +HTTP_STATUS(100, CONTINUE, Continue) +HTTP_STATUS(101, SWITCHING_PROTOCOLS, Switching Protocols) +HTTP_STATUS(102, PROCESSING, Processing) +HTTP_STATUS(103, EARLY_HINTS, Early Hints) +HTTP_STATUS(200, OK, OK) +HTTP_STATUS(201, CREATED, Created) +HTTP_STATUS(202, ACCEPTED, Accepted) +HTTP_STATUS(203, NON_AUTHORITATIVE_INFORMATION, Non Authoritative Information) +HTTP_STATUS(204, NO_CONTENT, No Content) +HTTP_STATUS(205, RESET_CONTENT, Reset Content) +HTTP_STATUS(206, PARTIAL_CONTENT, Partial Content) +HTTP_STATUS(207, MULTI_STATUS, Multi Status) +HTTP_STATUS(208, ALREADY_REPORTED, Already Reported) +HTTP_STATUS(218, THIS_IS_FINE, This Is Fine) +HTTP_STATUS(226, IM_USED, IM Used) +HTTP_STATUS(300, MULTIPLE_CHOICES, Multiple Choices) +HTTP_STATUS(301, MOVED_PERMANENTLY, Moved Permanently) +HTTP_STATUS(302, FOUND, Found) +HTTP_STATUS(303, SEE_OTHER, See Other) +HTTP_STATUS(304, NOT_MODIFIED, Not Modified) +HTTP_STATUS(305, USE_PROXY, Use Proxy) +HTTP_STATUS(306, SWITCH_PROXY, Switch Proxy) +HTTP_STATUS(307, TEMPORARY_REDIRECT, Temporary Redirect) +HTTP_STATUS(308, PERMANENT_REDIRECT, Permanent Redirect) +HTTP_STATUS(400, BAD_REQUEST, Bad Request) +HTTP_STATUS(401, UNAUTHORIZED, Unauthorized) +HTTP_STATUS(402, PAYMENT_REQUIRED, Payment Required) +HTTP_STATUS(403, FORBIDDEN, Forbidden) +HTTP_STATUS(404, NOT_FOUND, Not Found) +HTTP_STATUS(405, METHOD_NOT_ALLOWED, Method Not Allowed) +HTTP_STATUS(406, NOT_ACCEPTABLE, Not Acceptable) +HTTP_STATUS(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) +HTTP_STATUS(408, REQUEST_TIMEOUT, Request Timeout) +HTTP_STATUS(409, CONFLICT, Conflict) +HTTP_STATUS(410, GONE, Gone) +HTTP_STATUS(421, MISDIRECTED_REQUEST, Misdirected Request) +HTTP_STATUS(422, UNPROCESSABLE_CONTENT, Unprocessable Content) +HTTP_STATUS(499, CLIENT_CLOSED_REQUEST, Client Closed Request) +HTTP_STATUS(500, INTERNAL_SERVER_ERROR, Internal Server Error) +HTTP_STATUS(501, NOT_IMPLEMENTED, Not Implemented) +HTTP_STATUS(502, BAD_GATEWAY, Bad Gateway) +HTTP_STATUS(503, SERVICE_UNAVAILABLE, Service Unavailable) +HTTP_STATUS(504, GATEWAY_TIMEOUT, Gateway Timeout) +HTTP_STATUS(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) +HTTP_STATUS(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ No newline at end of file diff --git a/httpd.c b/httpd.c index 1c60a56..d566b1e 100644 --- a/httpd.c +++ b/httpd.c @@ -442,7 +442,7 @@ void httpd_connection(FILE *f) if (p_state->use_fcgi) xs_fcgi_response(f, status, headers, body, b_size, fcgi_id); else - xs_httpd_response(f, status, headers, body, b_size); + xs_httpd_response(f, status, http_status_text(status), headers, body, b_size); fclose(f); diff --git a/snac.c b/snac.c index 5ba98e1..0df8691 100644 --- a/snac.c +++ b/snac.c @@ -170,3 +170,15 @@ int check_password(const char *uid, const char *passwd, const char *hash) return ret; } + + +const char *http_status_text(int status) +/* translate status codes to canonical status texts */ +{ + switch (status) { +#define HTTP_STATUS(code, name, text) case HTTP_STATUS_ ## name: return #text; +#include "http_codes.h" +#undef HTTP_STATUS + default: return "Unknown"; + } +} diff --git a/snac.h b/snac.h index a821b18..2561b6c 100644 --- a/snac.h +++ b/snac.h @@ -367,52 +367,11 @@ void mastoapi_purge(void); void verify_links(snac *user); -/* HTTP responses RFC 9110 plus some extensions */ typedef enum { - HTTP_STATUS_CONTINUE = 100, - HTTP_STATUS_SWITCHTING_PROTOCOLS = 101, - HTTP_STATUS_PROCESSING = 102, - HTTP_STATUS_EARLY_HINTS = 103, - HTTP_STATUS_OK = 200, - HTTP_STATUS_CREATED = 201, - HTTP_STATUS_ACCEPTED = 202, - HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, - HTTP_STATUS_NO_CONTENT = 204, - HTTP_STATUS_RESET_CONTENT = 205, - HTTP_STATUS_PARTIAL_CONTENT = 206, - HTTP_STATUS_MULTI_STATUS = 207, - HTTP_STATUS_ALREADY_REPORTED = 208, - HTTP_STATUS_THIS_IS_FINE = 218, - HTTP_STATUS_IM_USED = 226, - HTTP_STATUS_MULTIPLE_CHOICES = 300, - HTTP_STATUS_MOVED_PERMANENTLY = 301, - HTTP_STATUS_FOUND = 302, - HTTP_STATUS_SEE_OTHER = 303, - HTTP_STATUS_NOT_MODIFIED = 304, - HTTP_STATUS_USE_PROXY = 305, - HTTP_STATUS_SWITCH_PROXY = 306, - HTTP_STATUS_TEMPORARY_REDIRECT = 307, - HTTP_STATUS_PERMANENT_REDIRECT = 308, - HTTP_STATUS_BAD_REQUEST = 400, - HTTP_STATUS_UNAUTHORIZED = 401, - HTTP_STATUS_PAYMENT_REQUIRED = 402, - HTTP_STATUS_FORBIDDEN = 403, - HTTP_STATUS_NOT_FOUND = 404, - HTTP_STATUS_METHOD_NOT_ALLOWED = 405, - HTTP_STATUS_NOT_ACCEPTABLE = 406, - HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, - HTTP_STATUS_REQUEST_TIMEOUT = 408, - HTTP_STATUS_CONFLICT = 409, - HTTP_STATUS_GONE = 410, - HTTP_STATUS_MISDIRECTED_REQUEST = 421, - HTTP_STATUS_UNPROCESSABLE_CONTENT = 422, - HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, - HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, - HTTP_STATUS_NOT_IMPLEMENTED = 501, - HTTP_STATUS_BAD_GATEWAY = 502, - HTTP_STATUS_SERVICE_UNAVAILABLE = 503, - HTTP_STATUS_GATEWAY_TIMEOUT = 504, - HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, - HTTP_STATUS_INSUFFICIENT_STORAGE = 507 +#define HTTP_STATUS(code, name, text) HTTP_STATUS_ ## name = code, +#include "http_codes.h" +#undef HTTP_STATUS } http_status; + +const char *http_status_text(int status); diff --git a/xs_httpd.h b/xs_httpd.h index 4195b81..d080b39 100644 --- a/xs_httpd.h +++ b/xs_httpd.h @@ -5,7 +5,7 @@ #define _XS_HTTPD_H xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size); -void xs_httpd_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size); +void xs_httpd_response(FILE *f, int status, const char *status_text, xs_dict *headers, xs_str *body, int b_size); #ifdef XS_IMPLEMENTATION @@ -95,14 +95,14 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size) } -void xs_httpd_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size) +void xs_httpd_response(FILE *f, int status, const char *status_text, xs_dict *headers, xs_str *body, int b_size) /* sends an httpd response */ { xs *proto; const xs_str *k; const xs_val *v; - proto = xs_fmt("HTTP/1.1 %d %s", status, status / 100 == 2 ? "OK" : "ERROR"); + proto = xs_fmt("HTTP/1.1 %d %s", status, status_text); fprintf(f, "%s\r\n", proto); int c = 0; -- cgit v1.2.3