From 419364b5a9e553be817a0aa0a75174222206d6b8 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Tue, 13 Oct 2015 18:53:23 +1030 Subject: [PATCH] Improve REST HTTP response status codes List all the HTTP status codes in the REST API tech doc. Only use 403 Forbidden for requests originating from a disallowed origin (ie, not localhost). - Return 400 for missing, unknown, duplicate and out-of-order form parts in POST requests. - Return 415 Unsupported Media Type for unsupported form part Content-Disposition and Content-Type (including unsupported charset). - Return 414 Request-URI Too Long for any buffer exhaustion while parsing request. - Return 419 Authentication Timeout for missing crypto secret. --- doc/Servald-REST-API.md | 168 ++++++++++++------ http_server.c | 3 +- httpd.c | 68 ++++--- httpd.h | 6 +- .../servaldna/meshms/MeshMSCommon.java | 4 +- .../servaldna/rhizome/RhizomeCommon.java | 1 + keyring_restful.c | 14 +- meshms_restful.c | 34 ++-- rhizome_restful.c | 62 +++---- rhizome_store.c | 1 + tests/meshmsrestful | 36 ++-- tests/rhizomerestful | 38 ++-- 12 files changed, 247 insertions(+), 188 deletions(-) diff --git a/doc/Servald-REST-API.md b/doc/Servald-REST-API.md index cf605c24..882bc505 100644 --- a/doc/Servald-REST-API.md +++ b/doc/Servald-REST-API.md @@ -22,9 +22,10 @@ This document describes the second of these, the [HTTP REST][] API. ### Protocol and port -The Serval DNA [HTTP REST][] API is an [HTTP 1.0][] server that accepts +The Serval DNA [HTTP REST][] API is an [HTTP 1.0][] server that only accepts requests on the loopback interface (IPv4 address 127.0.0.1), TCP port 4110. It -rejects requests that do not originate on the local host. +rejects requests that do not originate on the local host, by replying +[403](#forbidden). ### Security @@ -73,9 +74,9 @@ An HTTP REST request is a normal [HTTP 1.0][] [GET](#get) or [POST](#post): #### GET -A **GET** request consists of an initial "GET" line, followed by zero or more -header lines, followed by a blank line. As usual for HTTP, all lines are -terminated by an ASCII CR-LF sequence. +A **GET** request consists of an initial "GET" line containing the *path* and +*HTTP version*, followed by zero or more header lines, followed by a blank +line. As usual for HTTP, all lines are terminated by an ASCII CR-LF sequence. For example: @@ -158,7 +159,7 @@ unless otherwise documented. Some responses contain non-standard HTTP headers as part of the result they return to the client; for example, [Rhizome response headers](#rhizome-response-headers). -### Response status codes +### Response status code The HTTP REST API response uses the [HTTP status code][] to indicate the outcome of the request as follows: @@ -204,8 +205,12 @@ fetched entity forms the body of the response) if the request supplied a #### 400 Bad Request -The HTTP request was malformed (incorrect syntax), and should not be repeated -without modifications. +The HTTP request was malformed, and should not be repeated without +modifications. This could be for several reasons: +- invalid syntax in the request header block +- a `POST` request MIME part is missing, duplicated or out of order +- a `POST` request was given an unsupported MIME part +- a `POST` request MIME part has missing or malformed content #### 401 Unauthorized @@ -225,8 +230,8 @@ the missing credential: #### 403 Forbidden -The request failed because the caller does not possess the necessary -cryptographic secret. +The request failed because the server does not accept requests from the +originating host. #### 404 Not Found @@ -254,16 +259,35 @@ path that only supports [POST](#post), or vice versa. A `POST` request did not supply a [Content-Length](#request-content-length) header. +#### 414 Request-URI Too Long + +The request failed because the [HTTP request URI][] was too long. The server +persists the path and a few other pieces of the request in a fixed size request +buffer, and this response is triggered if the collective size of these does not +leave enough buffer for receiving the remainder of the request. + #### 415 Unsupported Media Type -The `POST` request [Content-Type](#request-content-type) header specified -an unsupported media type. +A `POST` request failed because of an unsupported content type, which could be +for several reasons: +- the request's [Content-Type](#request-content-type) header specified an + unsupported media type +- a MIME part Content-Disposition was not “form-data” +- a MIME part Content-Type was unsupported +- a MIME part Content-Type specified an unsupported charset #### 416 Requested Range Not Satisfiable The [Range](#request-range) header specified a range whose start position falls outside the size of the requested entity. +#### 419 Authentication Timeout + +The request failed because the server does not possess and cannot derive the +necessary cryptographic secret or credential. For example, updating a Rhizome +bundle without providing the bundle secret. This code is not part of the HTTP +standard. + #### 422 Unprocessable Entity A `POST` request supplied data that was inconsistent or violates semantic @@ -288,14 +312,15 @@ development situations. The request cannot be performed because a necessary resource is temporarily unavailable due to a high volume of concurrent requests. -This code was originally used by Rhizome operations if the server's manifest -table ran out of free manifests, which would only happen if there were many -concurrent Rhizome requests holding manifest structures open in server memory. -It may be used for any resource that is occupied by running requests. +The original use of this code was for Rhizome operations if the server's +manifest table ran out of free manifests, which would only happen if there were +many concurrent Rhizome requests holding manifest structures open in server +memory. -If [Serval DNA][] is ever limited to service only a few HTTP requests at a -time, then this code will be returned to new requests that would exceed the -limit. +This code may also be used to indicate temporary exhaustion of other finite +resources. For example, if [Serval DNA][] is ever limited to service only a +few HTTP requests at a time, then this code will be returned to new requests +that would exceed the limit. #### 431 Request Header Fields Too Large @@ -326,6 +351,32 @@ following cases: - a request [Range](#request-range) header specifies a multi range +#### Cross-Origin Resource Sharing (CORS) + +To support client-side JavaScript applications, Serval DNA has a limited +implementation of [Cross-Origin Resource Sharing][CORS]. If a request contains +an **Origin** header with either “null” or a single URI with scheme “http” or +“https” or “file”, hostname “localhost” or “127.0.0.1” (or empty in the case of +a “file” scheme), and optionally any port number, then the response will +contain three **Access-Control** headers granting permission for other pages on +the same site to access resources in the returned response. + +For example, given the request: + + GET /restful/keyring/identities.json HTTP/1.0 + Origin: http://localhost:8080/ + ... + +Serval DNA will respond: + + HTTP/1.0 200 OK + Access-Control-Allow-Origin: http://localhost:8080 + Access-Control-Allow-Methods: GET, POST, OPTIONS + Access-Control-Allow-Headers: Authorization + ... + +[CORS]: http://www.w3.org/TR/cors/ + #### JSON result All responses that convey no special content return the following *JSON result* @@ -343,8 +394,8 @@ line of the response. The `http_status_message` field is usually the same as the *reason phrase* text that follows the code in the first line of the HTTP response. This reason phrase may be a [standard phrase][status code], or it may be more explanatory; -for example, *403 Forbidden* responses from Rhizome use the phrase, “Rhizome -operation failed”. +for example, some *404* responses from Rhizome have phrases like, “Bundle not +found”, “Payload not found”, etc. Some responses augment the *JSON result* object with extra fields; for example, [Rhizome JSON result](#rhizome-json-result). @@ -544,20 +595,21 @@ All Rhizome operations that involve fetching and/or inserting a single manifest into the Rhizome store return a *bundle status code*, which describes the outcome of the operation. Some codes have different meanings in the context of a fetch or an insertion, and some codes can only be produced by insertions. +The bundle status code determines the [HTTP response code](#response-status-code). -| code | meaning | -|:----:|:------------------------------------------------------------------------------- | -| -1 | internal error | -| 0 | "new"; (fetch) bundle not found; (insert) bundle added to store | -| 1 | "same"; (fetch) bundle found; (insert) bundle already in store | -| 2 | "duplicate"; (insert only) duplicate bundle already in store | -| 3 | "old"; (insert only) newer version of bundle already in store | -| 4 | "invalid"; (insert only) manifest is invalid | -| 5 | "fake"; (insert only) manifest signature is invalid | -| 6 | "inconsistent"; (insert only) manifest filesize/filehash does not match payload | -| 7 | "no room"; (insert only) doesn't fit; store may contain more important bundles | -| 8 | "readonly"; (insert only) cannot modify manifest because secret is unknown | -| 9 | "busy"; Rhizome store database is currently busy (re-try) | +| code | HTTP | meaning | +|:----:|:----:|:------------------------------------------------------------------------------- | +| -1 | 500 | internal error | +| 0 | 201 | "new"; (fetch) bundle not found; (insert) bundle added to store | +| 1 | 200 | "same"; (fetch) bundle found; (insert) bundle already in store | +| 2 | 200 | "duplicate"; (insert only) duplicate bundle already in store | +| 3 | 202 | "old"; (insert only) newer version of bundle already in store | +| 4 | 422 | "invalid"; (insert only) manifest is invalid | +| 5 | 419 | "fake"; (insert only) manifest signature is invalid | +| 6 | 422 | "inconsistent"; (insert only) manifest filesize/filehash does not match payload | +| 7 | 202 | "no room"; (insert only) doesn't fit; store may contain more important bundles | +| 8 | 419 | "readonly"; (insert only) cannot modify manifest because secret is unknown | +| 9 | 423 | "busy"; Rhizome store database is currently busy (re-try) | #### Bundle status message @@ -574,19 +626,21 @@ into the Rhizome store return a *payload status code*, which describes the outcome of the payload operation, and elaborates on the the reason for the accompanying *bundle status code*. Some codes have different meanings in the context of a fetch or an insertion, and some codes can only be produced by -insertions. +insertions. The payload status code overrides the [HTTP response +code](#response-status-code) derived from the [bundle status +code](#bundle-status-code) if it is numerically higher. -| code | meaning | -|:----:|:--------------------------------------------------------------------- | -| -1 | internal error | -| 0 | empty payload (zero length) | -| 1 | (fetch) payload not found; (insert) payload added to store | -| 2 | (fetch) payload found; (insert) payload already in store | -| 3 | payload size does not match manifest *filesize* field | -| 4 | payload hash does not match manifest *filehash* field | -| 5 | payload key unknown: (fetch) cannot decrypt; (insert) cannot encrypt | -| 6 | (insert only) payload is too big to fit in store | -| 7 | (insert only) payload evicted; other payloads are ranked higher | +| code | HTTP | meaning | +|:----:|:----:|:--------------------------------------------------------------------- | +| -1 | 500 | internal error | +| 0 | 201 | empty payload (zero length) | +| 1 | 201 | (fetch) payload not found; (insert) payload added to store | +| 2 | 200 | (fetch) payload found; (insert) payload already in store | +| 3 | 422 | payload size does not match manifest *filesize* field | +| 4 | 422 | payload hash does not match manifest *filehash* field | +| 5 | 419 | payload key unknown: (fetch) cannot decrypt; (insert) cannot encrypt | +| 6 | 202 | (insert only) payload is too big to fit in store | +| 7 | 202 | (insert only) payload evicted; other payloads are ranked higher | #### Payload status message @@ -685,7 +739,7 @@ be *200 OK* and: nul (0) byte followed by the manifest's binary signature If the **manifest is not found** in the local Rhizome store, then the response -will be *403 Forbidden* and: +will be *404 Bundle not found* and: * the [bundle status code](#bundle-status-code) will be 0 * the [payload status code](#payload-status-code), if present in the response, is not relevant, so must be ignored @@ -715,17 +769,8 @@ then the response will be *200 OK* and: if the payload is encrypted (the manifest's `crypt` field is 1) then the payload is not decrypted -If the **manifest is found** in the local Rhizome store but the **payload is -not found**, then the response will be *403 Forbidden* and: -* the [bundle status code](#bundle-status-code) will be 1 -* the [payload status code](#payload-status-code) will be 1 -* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) give - information about the found manifest -* the response's content is the [Rhizome JSON result](#rhizome-json-result) - object - If the **manifest is not found** in the local Rhizome store, then the response -will be *403 Forbidden* and: +will be *404 Bundle not found* and: * the [bundle status code](#bundle-status-code) will be 0 * the [payload status code](#payload-status-code), if present in the response, is not relevant, so must be ignored @@ -734,6 +779,15 @@ will be *403 Forbidden* and: * the response's content is the [Rhizome JSON result](#rhizome-json-result) object +If the **manifest is found** in the local Rhizome store but the **payload is +not found**, then the response will be *404 Payload not found* and: +* the [bundle status code](#bundle-status-code) will be 1 +* the [payload status code](#payload-status-code) will be 1 +* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) give + information about the found manifest +* the response's content is the [Rhizome JSON result](#rhizome-json-result) + object + ### GET /restful/rhizome/BID/decrypted.bin Fetches the decrypted payload for the bundle whose id is `BID` (64 hex digits), diff --git a/http_server.c b/http_server.c index 4f21c20e..bb19816e 100644 --- a/http_server.c +++ b/http_server.c @@ -237,7 +237,7 @@ static int _reserve(struct http_request *r, const char **resp, const char *src, assert(r->reserved <= reslim); size_t siz = sizeof(char**) + len + 1; if (r->reserved + siz > reslim) { - r->response.result_code = 414; + r->response.result_code = 414; // Request-URI Too Long return 0; } if (r->reserved + siz > r->parsed) { @@ -1972,6 +1972,7 @@ static const char *http_reason_phrase(int response_code) case 414: return "Request-URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Requested Range Not Satisfiable"; + case 419: return "Authentication Timeout"; case 422: return "Unprocessable Entity"; case 423: return "Locked"; case 429: return "Too Many Requests"; diff --git a/httpd.c b/httpd.c index 4e0eeead..f130e529 100644 --- a/httpd.c +++ b/httpd.c @@ -357,36 +357,28 @@ int authorize_restful(struct http_request *r) { if (!is_from_loopback(r)) return 403; - if (r->request_header.origin.hostname) { - assert(r->request_header.origin.scheme); - if ( ( ( strcmp(r->request_header.origin.scheme, "http") == 0 - || strcmp(r->request_header.origin.scheme, "https") == 0 - ) + // If a CORS Origin: header was supplied, then if it specifies a local site, then respond with + // Access-Control-Allow-Origin and Access-Control-Allow-Methods headers that permit other pages in + // the same local site to request this page, otherwise respond with 403 Forbidden. + if (r->request_header.origin.null || r->request_header.origin.scheme[0]) { + if ( r->request_header.origin.null + || ( ( strcmp(r->request_header.origin.scheme, "http") == 0 + || strcmp(r->request_header.origin.scheme, "https") == 0 + ) && ( strcmp(r->request_header.origin.hostname, "localhost") == 0 - || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0 - ) - ) + || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0 + ) + ) || ( strcmp(r->request_header.origin.scheme, "file") == 0 && ( strcmp(r->request_header.origin.hostname, "localhost") == 0 - || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0 - || strcmp(r->request_header.origin.hostname, "") == 0 - ) - ) + || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0 + || strcmp(r->request_header.origin.hostname, "") == 0 + ) + ) ) { - strbuf sb = strbuf_local(r->response.header.allow_origin, sizeof r->response.header.allow_origin); - strbuf_puts(sb, r->request_header.origin.scheme); - strbuf_puts(sb, "://"); - strbuf_puts(sb, r->request_header.origin.hostname); - if (r->request_header.origin.port) { - strbuf_sprintf(sb, ":%u", r->request_header.origin.port); - } - if (!strbuf_overrun(sb)) { - r->response.header.allow_methods = "GET, POST, OPTIONS"; - r->response.header.allow_headers = "Authorization"; - } else { - r->response.header.allow_origin[0] = '\0'; - return 403; - } + r->response.header.allow_origin = r->request_header.origin; + r->response.header.allow_methods = "GET, POST, OPTIONS"; + r->response.header.allow_headers = "Authorization"; } else { return 403; } @@ -413,7 +405,7 @@ int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_ ); strbuf msg = strbuf_alloca(100); strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); + http_request_simple_response(&r->http, 400, strbuf_str(msg)); return 0; } memcpy(textbuf + *textlenp, buf, len); @@ -442,8 +434,8 @@ int form_buf_malloc_accumulate(httpd_request *r, const char *partname, struct fo ); strbuf msg = strbuf_alloca(100); strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); - return 403; + http_request_simple_response(&r->http, 400, strbuf_str(msg)); + return 400; } if (newlen > f->buffer_alloc_size) { if ((f->buffer = erealloc(f->buffer, newlen)) == NULL) { @@ -468,7 +460,7 @@ void form_buf_malloc_release(struct form_buf_malloc *f) f->size_limit = 0; } -int http_response_content_type(httpd_request *r, const char *what, const struct mime_content_type *ct) +int http_response_content_type(httpd_request *r, uint16_t result, const char *what, const struct mime_content_type *ct) { DEBUGF(httpd, "%s Content-Type: %s/%s%s%s%s%s", what, ct->type, ct->subtype, ct->charset[0] ? "; charset=" : "", @@ -486,26 +478,26 @@ int http_response_content_type(httpd_request *r, const char *what, const struct strbuf_sprintf(msg, "; charset=%s", ct->charset); if (ct->multipart_boundary[0]) strbuf_sprintf(msg, "; boundary=%s", ct->multipart_boundary); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); - return 403; + http_request_simple_response(&r->http, result, strbuf_str(msg)); + return result; } -int http_response_content_disposition(httpd_request *r, const char *what, const char *type) +int http_response_content_disposition(httpd_request *r, uint16_t result, const char *what, const char *type) { DEBUGF(httpd, "%s Content-Disposition: %s %s", what, type); strbuf msg = strbuf_alloca(100); strbuf_sprintf(msg, "%s Content-Disposition: %s", what, type); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); - return 403; + http_request_simple_response(&r->http, result, strbuf_str(msg)); // Unsupported Media Type + return result; } -int http_response_form_part(httpd_request *r, const char *what, const char *partname, const char *text, size_t textlen) +int http_response_form_part(httpd_request *r, uint16_t result, const char *what, const char *partname, const char *text, size_t textlen) { DEBUGF(httpd, "%s \"%s\" form part %s", what, partname, text ? alloca_toprint(-1, text, textlen) : ""); strbuf msg = strbuf_alloca(100); strbuf_sprintf(msg, "%s \"%s\" form part", what, partname); - http_request_simple_response(&r->http, 403, strbuf_str(msg)); - return 403; + http_request_simple_response(&r->http, result, strbuf_str(msg)); // Unsupported Media Type + return result; } int http_response_init_content_range(httpd_request *r, size_t resource_length) diff --git a/httpd.h b/httpd.h index ae00b569..448ca335 100644 --- a/httpd.h +++ b/httpd.h @@ -235,9 +235,9 @@ DECLARE_SECTION(struct http_handler, httpd); int is_http_header_complete(const char *buf, size_t len, size_t read_since_last_call); int authorize_restful(struct http_request *r); -int http_response_content_type(httpd_request *r, const char *what, const struct mime_content_type *ct); -int http_response_content_disposition(httpd_request *r, const char *what, const char *type); -int http_response_form_part(httpd_request *r, const char *what, const char *partname, const char *text, size_t textlen); +int http_response_content_type(httpd_request *r, uint16_t result, const char *what, const struct mime_content_type *ct); +int http_response_content_disposition(httpd_request *r, uint16_t result, const char *what, const char *type); +int http_response_form_part(httpd_request *r, uint16_t result, const char *what, const char *partname, const char *text, size_t textlen); int http_response_init_content_range(httpd_request *r, size_t resource_length); int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_t textsiz, size_t *textlenp, const char *buf, size_t len); diff --git a/java/org/servalproject/servaldna/meshms/MeshMSCommon.java b/java/org/servalproject/servaldna/meshms/MeshMSCommon.java index b1df77a4..6b7bb47c 100644 --- a/java/org/servalproject/servaldna/meshms/MeshMSCommon.java +++ b/java/org/servalproject/servaldna/meshms/MeshMSCommon.java @@ -46,7 +46,9 @@ public class MeshMSCommon { if (!"application/json".equals(conn.getContentType())) throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); - if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) { + switch (conn.getResponseCode()) { + case HttpURLConnection.HTTP_NOT_FOUND: + case 419: // Authentication Timeout, for missing secret JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "UTF-8")); Status status = decodeRestfulStatus(json); throwRestfulResponseExceptions(status, conn.getURL()); diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java b/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java index 058b3838..bb538128 100644 --- a/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java +++ b/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java @@ -96,6 +96,7 @@ public class RhizomeCommon switch (status.http_status_code) { case HttpURLConnection.HTTP_FORBIDDEN: // for crypto failure (missing secret) case HttpURLConnection.HTTP_NOT_FOUND: // for unknown BID + case 419: // Authentication Timeout, for missing secret case 422: // Unprocessable Entity, for invalid/malformed manifest case 423: // Locked, for database busy case 429: // Too Many Requests, for out of manifests diff --git a/keyring_restful.c b/keyring_restful.c index f5db9fc0..d2b799c0 100644 --- a/keyring_restful.c +++ b/keyring_restful.c @@ -1,6 +1,6 @@ /* Serval DNA HTTP RESTful interface -Copyright (C) 2013,2014 Serval Project Inc. +Copyright (C) 2013-2015 Serval Project Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -86,7 +86,7 @@ static int http_request_keyring_response_identity(struct httpd_request *r, uint1 const char *name = NULL; keyring_identity_extract(id, &sidp, &did, &name); if (!sidp) - return http_request_keyring_response(r, 501, "Identity has no SID"); + return http_request_keyring_response(r, 500, "Identity has no SID"); struct json_atom json_id; struct json_key_value json_id_kv[3]; struct json_atom json_sid; @@ -213,9 +213,9 @@ static int restful_keyring_add(httpd_request *r, const char *remainder) const char *pin = http_request_get_query_param(&r->http, "pin"); const keyring_identity *id = keyring_create_identity(keyring, pin ? pin : ""); if (id == NULL) - return http_request_keyring_response(r, 501, "Could not create identity"); + return http_request_keyring_response(r, 500, "Could not create identity"); if (keyring_commit(keyring) == -1) - return http_request_keyring_response(r, 501, "Could not store new identity"); + return http_request_keyring_response(r, 500, "Could not store new identity"); return http_request_keyring_response_identity(r, 200, CONTENT_TYPE_JSON, id); } @@ -231,10 +231,10 @@ static int restful_keyring_set(httpd_request *r, const char *remainder) keyring_iterator it; keyring_iterator_start(keyring, &it); if (!keyring_find_sid(&it, &r->sid1)) - return http_request_keyring_response(r, 404, NULL); + return http_request_keyring_response(r, 404, "Identity not found"); if (keyring_set_did(it.identity, did ? did : "", name ? name : "") == -1) - return http_request_keyring_response(r, 501, "Could not set identity DID/Name"); + return http_request_keyring_response(r, 500, "Could not set identity DID/Name"); if (keyring_commit(keyring) == -1) - return http_request_keyring_response(r, 501, "Could not store new identity"); + return http_request_keyring_response(r, 500, "Could not store new identity"); return http_request_keyring_response_identity(r, 200, CONTENT_TYPE_JSON, it.identity); } diff --git a/meshms_restful.c b/meshms_restful.c index 2e48179e..25486757 100644 --- a/meshms_restful.c +++ b/meshms_restful.c @@ -71,16 +71,22 @@ static int strn_to_meshms_token(const char *str, rhizome_bid_t *bidp, uint64_t * static int http_request_meshms_response(struct httpd_request *r, uint16_t result, const char *message, enum meshms_status status) { uint16_t meshms_result = 0; + const char *meshms_message = NULL; switch (status) { case MESHMS_STATUS_OK: meshms_result = 200; break; case MESHMS_STATUS_UPDATED: - meshms_result = 201; + meshms_result = 201; // Created + meshms_message = "Updated"; break; case MESHMS_STATUS_SID_LOCKED: + meshms_result = 419; // Authentication Timeout + meshms_message = "Identity locked"; + break; case MESHMS_STATUS_PROTOCOL_FAULT: - meshms_result = 403; + meshms_result = 500; + meshms_message = "MeshMS protocol fault"; break; case MESHMS_STATUS_ERROR: meshms_result = 500; @@ -101,10 +107,10 @@ static int http_request_meshms_response(struct httpd_request *r, uint16_t result } if (meshms_result > result) { result = meshms_result; - message = NULL; + message = meshms_message; } assert(result != 0); - http_request_simple_response(&r->http, result, message ? message : result == 403 ? "MeshMS operation failed" : NULL); + http_request_simple_response(&r->http, result, message); return result; } @@ -120,7 +126,7 @@ static int restful_meshms_(httpd_request *r, const char *remainder) { r->http.response.header.content_type = CONTENT_TYPE_JSON; if (!is_rhizome_http_enabled()) - return 403; + return 404; int ret = authorize_restful(&r->http); if (ret) return ret; @@ -591,7 +597,7 @@ static int send_mime_part_end(struct http_request *hr) httpd_request *r = (httpd_request *) hr; if (r->u.sendmsg.current_part == PART_MESSAGE) { if (r->u.sendmsg.message.length == 0) - return http_response_form_part(r, "Invalid (empty)", PART_MESSAGE, NULL, 0); + return http_response_form_part(r, 400, "Invalid (empty)", PART_MESSAGE, NULL, 0); r->u.sendmsg.received_message = 1; DEBUGF(httpd, "received %s = %s", PART_MESSAGE, alloca_toprint(-1, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length)); } else @@ -604,23 +610,23 @@ static int send_mime_part_header(struct http_request *hr, const struct mime_part { httpd_request *r = (httpd_request *) hr; if (strcmp(h->content_disposition.type, "form-data") != 0) - return http_response_content_disposition(r, "Unsupported", h->content_disposition.type); + return http_response_content_disposition(r, 415, "Unsupported", h->content_disposition.type); if (strcmp(h->content_disposition.name, PART_MESSAGE) == 0) { if (r->u.sendmsg.received_message) - return http_response_form_part(r, "Duplicate", PART_MESSAGE, NULL, 0); + return http_response_form_part(r, 400, "Duplicate", PART_MESSAGE, NULL, 0); r->u.sendmsg.current_part = PART_MESSAGE; form_buf_malloc_init(&r->u.sendmsg.message, MESHMS_MESSAGE_MAX_LEN); } else - return http_response_form_part(r, "Unsupported", h->content_disposition.name, NULL, 0); + return http_response_form_part(r, 415, "Unsupported", h->content_disposition.name, NULL, 0); if (!h->content_type.type[0] || !h->content_type.subtype[0]) - return http_response_content_type(r, "Missing", &h->content_type); + return http_response_content_type(r, 400, "Missing", &h->content_type); if (strcmp(h->content_type.type, "text") != 0 || strcmp(h->content_type.subtype, "plain") != 0) - return http_response_content_type(r, "Unsupported", &h->content_type); + return http_response_content_type(r, 415, "Unsupported", &h->content_type); if (!h->content_type.charset[0]) - return http_response_content_type(r, "Missing charset", &h->content_type); + return http_response_content_type(r, 400, "Missing charset", &h->content_type); if (strcmp(h->content_type.charset, "utf-8") != 0) - return http_response_content_type(r, "Unsupported charset", &h->content_type); + return http_response_content_type(r, 415, "Unsupported charset", &h->content_type); return 0; } @@ -638,7 +644,7 @@ static int restful_meshms_sendmessage_end(struct http_request *hr) { httpd_request *r = (httpd_request *) hr; if (!r->u.sendmsg.received_message) - return http_response_form_part(r, "Missing", PART_MESSAGE, NULL, 0); + return http_response_form_part(r, 400, "Missing", PART_MESSAGE, NULL, 0); assert(r->u.sendmsg.message.length > 0); assert(r->u.sendmsg.message.length <= MESHMS_MESSAGE_MAX_LEN); enum meshms_status status; diff --git a/rhizome_restful.c b/rhizome_restful.c index ccc735ed..d0069929 100644 --- a/rhizome_restful.c +++ b/rhizome_restful.c @@ -74,20 +74,20 @@ static int http_request_rhizome_response(struct httpd_request *r, uint16_t resul { uint16_t rhizome_result = 0; switch (r->bundle_status) { - case RHIZOME_BUNDLE_STATUS_NEW: - rhizome_result = 201; // Created - break; case RHIZOME_BUNDLE_STATUS_SAME: case RHIZOME_BUNDLE_STATUS_DUPLICATE: rhizome_result = 200; // OK break; + case RHIZOME_BUNDLE_STATUS_NEW: + rhizome_result = 201; // Created + break; case RHIZOME_BUNDLE_STATUS_NO_ROOM: case RHIZOME_BUNDLE_STATUS_OLD: rhizome_result = 202; // Accepted break; case RHIZOME_BUNDLE_STATUS_FAKE: case RHIZOME_BUNDLE_STATUS_READONLY: - rhizome_result = 403; // Forbidden + rhizome_result = 419; // Authentication Timeout break; case RHIZOME_BUNDLE_STATUS_INVALID: case RHIZOME_BUNDLE_STATUS_INCONSISTENT: @@ -117,9 +117,11 @@ static int http_request_rhizome_response(struct httpd_request *r, uint16_t resul } rhizome_result = 0; switch (r->payload_status) { - case RHIZOME_PAYLOAD_STATUS_NEW: case RHIZOME_PAYLOAD_STATUS_STORED: case RHIZOME_PAYLOAD_STATUS_EMPTY: + rhizome_result = 200; + break; + case RHIZOME_PAYLOAD_STATUS_NEW: rhizome_result = 201; break; case RHIZOME_PAYLOAD_STATUS_TOO_BIG: @@ -127,7 +129,7 @@ static int http_request_rhizome_response(struct httpd_request *r, uint16_t resul rhizome_result = 202; // Accepted break; case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: - rhizome_result = 403; // Forbidden + rhizome_result = 419; // Authentication Timeout break; case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: @@ -395,7 +397,7 @@ static int insert_mime_part_start(struct http_request *hr) static int insert_make_manifest(httpd_request *r) { if (!r->u.insert.received_manifest) - return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0); + return http_response_form_part(r, 400, "Missing", PART_MANIFEST, NULL, 0); if ((r->manifest = rhizome_new_manifest()) == NULL) return http_request_rhizome_response(r, 429, "Manifest table full", NULL); // Too Many Requests assert(r->u.insert.manifest.length <= sizeof r->manifest->manifestdata); @@ -425,7 +427,7 @@ static int insert_make_manifest(httpd_request *r) r->u.insert.received_bundleid ? &r->bid: NULL, r->u.insert.received_secret ? &r->u.insert.bundle_secret : NULL, r->u.insert.received_author ? &r->u.insert.author: NULL, - NULL, 0, NULL, strbuf_local(message, sizeof message)); + NULL, 0, NULL, strbuf_local_buf(message)); int result_valid = 0; switch (result) { case RHIZOME_ADD_FILE_ERROR: @@ -447,7 +449,7 @@ static int insert_make_manifest(httpd_request *r) return http_request_rhizome_response(r, 422, message, NULL); // Unprocessable Entity case RHIZOME_ADD_FILE_WRONG_SECRET: r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY; // TODO separate enum for CLI return codes - return http_request_rhizome_response(r, 403, message, NULL); // Forbidden + return http_request_rhizome_response(r, 419, message, NULL); // Authentication Timeout } if (!result_valid) FATALF("result = %d", result); @@ -464,55 +466,55 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa { httpd_request *r = (httpd_request *) hr; if (strcmp(h->content_disposition.type, "form-data") != 0) - return http_response_content_disposition(r, "Unsupported", h->content_disposition.type); + return http_response_content_disposition(r, 415, "Unsupported", h->content_disposition.type); if (strcmp(h->content_disposition.name, PART_AUTHOR) == 0) { if (r->u.insert.received_author) - return http_response_form_part(r, "Duplicate", PART_AUTHOR, NULL, 0); + return http_response_form_part(r, 400, "Duplicate", PART_AUTHOR, NULL, 0); // Reject a request if this parameter comes after the manifest part. if (r->u.insert.received_manifest) - return http_response_form_part(r, "Spurious", PART_AUTHOR, NULL, 0); + return http_response_form_part(r, 400, "Spurious", PART_AUTHOR, NULL, 0); r->u.insert.current_part = PART_AUTHOR; assert(r->u.insert.author_hex_len == 0); } else if (strcmp(h->content_disposition.name, PART_SECRET) == 0) { if (r->u.insert.received_secret) - return http_response_form_part(r, "Duplicate", PART_SECRET, NULL, 0); + return http_response_form_part(r, 400, "Duplicate", PART_SECRET, NULL, 0); // Reject a request if this parameter comes after the manifest part. if (r->u.insert.received_manifest) - return http_response_form_part(r, "Spurious", PART_SECRET, NULL, 0); + return http_response_form_part(r, 400, "Spurious", PART_SECRET, NULL, 0); r->u.insert.current_part = PART_SECRET; assert(r->u.insert.secret_text_len == 0); } else if (strcmp(h->content_disposition.name, PART_BUNDLEID) == 0) { if (r->u.insert.received_bundleid) - return http_response_form_part(r, "Duplicate", PART_BUNDLEID, NULL, 0); + return http_response_form_part(r, 400, "Duplicate", PART_BUNDLEID, NULL, 0); // Reject a request if this parameter comes after the manifest part. if (r->u.insert.received_manifest) - return http_response_form_part(r, "Spurious", PART_BUNDLEID, NULL, 0); + return http_response_form_part(r, 400, "Spurious", PART_BUNDLEID, NULL, 0); r->u.insert.current_part = PART_BUNDLEID; assert(r->u.insert.bid_text_len == 0); } else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) { // Reject a request if it has a repeated manifest part. if (r->u.insert.received_manifest) - return http_response_form_part(r, "Duplicate", PART_MANIFEST, NULL, 0); + return http_response_form_part(r, 400, "Duplicate", PART_MANIFEST, NULL, 0); form_buf_malloc_init(&r->u.insert.manifest, MAX_MANIFEST_BYTES); if ( strcmp(h->content_type.type, "rhizome") != 0 || strcmp(h->content_type.subtype, "manifest") != 0 ) - return http_response_form_part(r, "Unsupported Content-Type in", PART_MANIFEST, NULL, 0); + return http_response_form_part(r, 415, "Unsupported Content-Type in", PART_MANIFEST, NULL, 0); if ((strcmp(h->content_type.format, "text+binarysig") != 0) &&strlen(h->content_type.format)) - return http_response_form_part(r, "Unsupported rhizome/manifest format in", PART_MANIFEST, NULL, 0); + return http_response_form_part(r, 415, "Unsupported rhizome/manifest format in", PART_MANIFEST, NULL, 0); r->u.insert.current_part = PART_MANIFEST; } else if (strcmp(h->content_disposition.name, PART_PAYLOAD) == 0) { // Reject a request if it has a repeated payload part. if (r->u.insert.received_payload) - return http_response_form_part(r, "Duplicate", PART_PAYLOAD, NULL, 0); + return http_response_form_part(r, 400, "Duplicate", PART_PAYLOAD, NULL, 0); // Reject a request if it has a missing manifest part preceding the payload part. if (!r->u.insert.received_manifest) - return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0); + return http_response_form_part(r, 400, "Missing", PART_MANIFEST, NULL, 0); assert(r->manifest != NULL); r->u.insert.current_part = PART_PAYLOAD; // If the manifest does not contain a 'name' field, then assign it from the payload filename. @@ -547,7 +549,7 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa r->u.insert.payload_size = 0; } else - return http_response_form_part(r, "Unsupported", h->content_disposition.name, NULL, 0); + return http_response_form_part(r, 400, "Unsupported", h->content_disposition.name, NULL, 0); return 0; } @@ -603,19 +605,19 @@ static int insert_mime_part_end(struct http_request *hr) if ( r->u.insert.author_hex_len != sizeof r->u.insert.author_hex || strn_to_sid_t(&r->u.insert.author, r->u.insert.author_hex, sizeof r->u.insert.author_hex) == -1 ) - return http_response_form_part(r, "Invalid", PART_AUTHOR, r->u.insert.author_hex, r->u.insert.author_hex_len); + return http_response_form_part(r, 400, "Invalid", PART_AUTHOR, r->u.insert.author_hex, r->u.insert.author_hex_len); r->u.insert.received_author = 1; DEBUGF(rhizome, "received %s = %s", PART_AUTHOR, alloca_tohex_sid_t(r->u.insert.author)); } else if (r->u.insert.current_part == PART_SECRET) { if (strn_to_rhizome_bsk_t(&r->u.insert.bundle_secret, r->u.insert.secret_text, r->u.insert.secret_text_len) == -1) - return http_response_form_part(r, "Invalid", PART_SECRET, r->u.insert.secret_text, r->u.insert.secret_text_len); + return http_response_form_part(r, 400, "Invalid", PART_SECRET, r->u.insert.secret_text, r->u.insert.secret_text_len); r->u.insert.received_secret = 1; DEBUGF(rhizome, "received %s = %s", PART_SECRET, alloca_tohex_rhizome_bk_t(r->u.insert.bundle_secret)); } else if (r->u.insert.current_part == PART_BUNDLEID) { if (strn_to_rhizome_bid_t(&r->bid, r->u.insert.bid_text, r->u.insert.bid_text_len) == -1) - return http_response_form_part(r, "Invalid", PART_BUNDLEID, r->u.insert.secret_text, r->u.insert.secret_text_len); + return http_response_form_part(r, 400, "Invalid", PART_BUNDLEID, r->u.insert.secret_text, r->u.insert.secret_text_len); r->u.insert.received_bundleid = 1; DEBUGF(rhizome, "received %s = %s", PART_BUNDLEID, alloca_tohex_rhizome_bid_t(r->bid)); } @@ -640,9 +642,9 @@ static int restful_rhizome_insert_end(struct http_request *hr) { httpd_request *r = (httpd_request *) hr; if (!r->u.insert.received_manifest) - return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0); + return http_response_form_part(r, 400, "Missing", PART_MANIFEST, NULL, 0); if (!r->u.insert.received_payload) - return http_response_form_part(r, "Missing", PART_PAYLOAD, NULL, 0); + return http_response_form_part(r, 400, "Missing", PART_PAYLOAD, NULL, 0); // Fill in the missing manifest fields and ensure payload and manifest are consistent. assert(r->manifest != NULL); DEBUGF(rhizome, "r->payload_status=%d %s", r->payload_status, rhizome_payload_status_message(r->payload_status)); @@ -693,7 +695,7 @@ static int restful_rhizome_insert_end(struct http_request *hr) } case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY; - return http_request_rhizome_response(r, 403, "Missing bundle secret", NULL); // Forbidden + return http_request_rhizome_response(r, 419, "Missing bundle secret", NULL); // Authentication Timeout case RHIZOME_PAYLOAD_STATUS_TOO_BIG: r->bundle_status = RHIZOME_BUNDLE_STATUS_NO_ROOM; return http_request_rhizome_response(r, 202, "Bundle too big", NULL); // Accepted @@ -717,7 +719,7 @@ static int restful_rhizome_insert_end(struct http_request *hr) } if (!r->manifest->haveSecret) { r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY; - return http_request_rhizome_response(r, 403, "Missing bundle secret", NULL); // Forbidden + return http_request_rhizome_response(r, 419, "Missing bundle secret", NULL); // Authentication Timeout } rhizome_manifest *mout = NULL; r->bundle_status = rhizome_manifest_finalise(r->manifest, &mout, !r->u.insert.force_new); @@ -918,7 +920,7 @@ int rhizome_response_content_init_payload(httpd_request *r, rhizome_manifest *m) case RHIZOME_PAYLOAD_STATUS_NEW: return http_request_rhizome_response(r, 404, "Payload not found", NULL); case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: - return http_request_rhizome_response(r, 403, NULL, NULL); // Forbidden + return http_request_rhizome_response(r, 419, NULL, NULL); // Authentication Timeout case RHIZOME_PAYLOAD_STATUS_ERROR: case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: diff --git a/rhizome_store.c b/rhizome_store.c index d63a6555..0fdef59b 100644 --- a/rhizome_store.c +++ b/rhizome_store.c @@ -813,6 +813,7 @@ enum rhizome_payload_status rhizome_finish_write(struct rhizome_write *write) } if (sqlite_exec_void_retry(&retry, "COMMIT;", END) == -1) goto dbfailure; + // A test case in tests/rhizomeprotocol depends on this debug message: DEBUGF(rhizome_store, "Stored file %s", alloca_tohex_rhizome_filehash_t(write->id)); } write->blob_rowid = 0; diff --git a/tests/meshmsrestful b/tests/meshmsrestful index f47b86f4..641b2a63 100755 --- a/tests/meshmsrestful +++ b/tests/meshmsrestful @@ -279,9 +279,9 @@ test_MeshmsListMessagesNoIdentity() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/messagelist.json" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' - assertJqGrep --ignore-case http.body '.http_status_message' 'meshms operation failed' + assertStdoutIs 419 + assertJq http.body 'contains({"http_status_code": 419})' + assertJqGrep --ignore-case http.body '.http_status_message' 'identity locked' assertJq http.body 'contains({"meshms_status_code": 2})' assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown' } @@ -431,8 +431,8 @@ test_MeshmsSendMissingMessage() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*message.*form.*part' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 @@ -454,8 +454,8 @@ test_MeshmsSendDuplicateMessage() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*message.*form.*part' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 @@ -476,8 +476,8 @@ test_MeshmsSendMessageMissingContentType() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*content.*type' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 @@ -498,8 +498,8 @@ test_MeshmsSendMessageUnsupportedContentType() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 415 + assertJq http.body 'contains({"http_status_code": 415})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*content.*type' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 @@ -520,8 +520,8 @@ test_MeshmsSendMessageMissingCharset() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*charset' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 @@ -542,8 +542,8 @@ test_MeshmsSendMessageUnsupportedCharset() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 415 + assertJq http.body 'contains({"http_status_code": 415})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*charset' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 @@ -564,9 +564,9 @@ test_MeshmsSendNoIdentity() { "http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' - assertJqGrep --ignore-case http.body '.http_status_message' 'meshms operation failed' + assertStdoutIs 419 + assertJq http.body 'contains({"http_status_code": 419})' + assertJqGrep --ignore-case http.body '.http_status_message' 'identity locked' assertJq http.body 'contains({"meshms_status_code": 2})' assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown' } diff --git a/tests/rhizomerestful b/tests/rhizomerestful index f175e866..61f527d8 100755 --- a/tests/rhizomerestful +++ b/tests/rhizomerestful @@ -479,7 +479,7 @@ test_RhizomePayloadDecryptedForeign() { --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/${BID[1]}/decrypted.bin" tfw_cat http.headers$n decrypted.bin$n - assertStdoutIs 403 + assertStdoutIs 419 assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$" assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$" assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 5$CR\$" @@ -648,8 +648,8 @@ test_RhizomeInsert() { assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$" assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$" else - assertStdoutIs 403 - assertJq nfile$n.manifest 'contains({"http_status_code": 403})' + assertStdoutIs 419 + assertJq nfile$n.manifest 'contains({"http_status_code": 419})' assertJqGrep --ignore-case nfile$n.manifest '.http_status_message' "missing bundle secret" fi done @@ -881,8 +881,8 @@ test_RhizomeInsertMissingManifest() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -904,8 +904,8 @@ test_RhizomeInsertIncorrectManifestType() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 415 + assertJq http.body 'contains({"http_status_code": 415})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported content-type.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -927,8 +927,8 @@ test_RhizomeInsertIncorrectManifestFormat() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 415 + assertJq http.body 'contains({"http_status_code": 415})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*format.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -953,8 +953,8 @@ test_RhizomeInsertDuplicateManifest() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -1001,8 +1001,8 @@ test_RhizomeInsertMissingPayload() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*payload.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -1027,8 +1027,8 @@ test_RhizomeInsertDuplicatePayload() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*payload.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -1051,8 +1051,8 @@ test_RhizomeInsertPartOrder() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list @@ -1076,8 +1076,8 @@ test_RhizomeInsertPartUnsupported() { "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 - assertStdoutIs 403 - assertJq http.body 'contains({"http_status_code": 403})' + assertStdoutIs 400 + assertJq http.body 'contains({"http_status_code": 400})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*form.*part' assertJqGrep http.body '.http_status_message' 'happyhappy' executeOk_servald rhizome list