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.
This commit is contained in:
Andrew Bettison 2015-10-13 18:53:23 +10:30
parent e189bcf32a
commit 419364b5a9
12 changed files with 247 additions and 188 deletions

View File

@ -22,9 +22,10 @@ This document describes the second of these, the [HTTP REST][] API.
### Protocol and port ### 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 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 ### Security
@ -73,9 +74,9 @@ An HTTP REST request is a normal [HTTP 1.0][] [GET](#get) or [POST](#post):
#### GET #### GET
A **GET** request consists of an initial "GET" line, followed by zero or more A **GET** request consists of an initial "GET" line containing the *path* and
header lines, followed by a blank line. As usual for HTTP, all lines are *HTTP version*, followed by zero or more header lines, followed by a blank
terminated by an ASCII CR-LF sequence. line. As usual for HTTP, all lines are terminated by an ASCII CR-LF sequence.
For example: For example:
@ -158,7 +159,7 @@ unless otherwise documented.
Some responses contain non-standard HTTP headers as part of the result they 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). 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 The HTTP REST API response uses the [HTTP status code][] to indicate the
outcome of the request as follows: 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 #### 400 Bad Request
The HTTP request was malformed (incorrect syntax), and should not be repeated The HTTP request was malformed, and should not be repeated without
without modifications. 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 #### 401 Unauthorized
@ -225,8 +230,8 @@ the missing credential:
#### 403 Forbidden #### 403 Forbidden
The request failed because the caller does not possess the necessary The request failed because the server does not accept requests from the
cryptographic secret. originating host.
#### 404 Not Found #### 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) A `POST` request did not supply a [Content-Length](#request-content-length)
header. 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 #### 415 Unsupported Media Type
The `POST` request [Content-Type](#request-content-type) header specified A `POST` request failed because of an unsupported content type, which could be
an unsupported media type. 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 #### 416 Requested Range Not Satisfiable
The [Range](#request-range) header specified a range whose start position falls The [Range](#request-range) header specified a range whose start position falls
outside the size of the requested entity. 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 #### 422 Unprocessable Entity
A `POST` request supplied data that was inconsistent or violates semantic 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 The request cannot be performed because a necessary resource is temporarily
unavailable due to a high volume of concurrent requests. unavailable due to a high volume of concurrent requests.
This code was originally used by Rhizome operations if the server's manifest The original use of this code was for Rhizome operations if the server's
table ran out of free manifests, which would only happen if there were many manifest table ran out of free manifests, which would only happen if there were
concurrent Rhizome requests holding manifest structures open in server memory. many concurrent Rhizome requests holding manifest structures open in server
It may be used for any resource that is occupied by running requests. memory.
If [Serval DNA][] is ever limited to service only a few HTTP requests at a This code may also be used to indicate temporary exhaustion of other finite
time, then this code will be returned to new requests that would exceed the resources. For example, if [Serval DNA][] is ever limited to service only a
limit. 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 #### 431 Request Header Fields Too Large
@ -326,6 +351,32 @@ following cases:
- a request [Range](#request-range) header specifies a multi range - 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 #### JSON result
All responses that convey no special content return the following *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 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 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; 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 for example, some *404* responses from Rhizome have phrases like, “Bundle not
operation failed”. found”, “Payload not found”, etc.
Some responses augment the *JSON result* object with extra fields; for example, Some responses augment the *JSON result* object with extra fields; for example,
[Rhizome JSON result](#rhizome-json-result). [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 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 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. 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 | | code | HTTP | meaning |
|:----:|:------------------------------------------------------------------------------- | |:----:|:----:|:------------------------------------------------------------------------------- |
| -1 | internal error | | -1 | 500 | internal error |
| 0 | "new"; (fetch) bundle not found; (insert) bundle added to store | | 0 | 201 | "new"; (fetch) bundle not found; (insert) bundle added to store |
| 1 | "same"; (fetch) bundle found; (insert) bundle already in store | | 1 | 200 | "same"; (fetch) bundle found; (insert) bundle already in store |
| 2 | "duplicate"; (insert only) duplicate bundle already in store | | 2 | 200 | "duplicate"; (insert only) duplicate bundle already in store |
| 3 | "old"; (insert only) newer version of bundle already in store | | 3 | 202 | "old"; (insert only) newer version of bundle already in store |
| 4 | "invalid"; (insert only) manifest is invalid | | 4 | 422 | "invalid"; (insert only) manifest is invalid |
| 5 | "fake"; (insert only) manifest signature is invalid | | 5 | 419 | "fake"; (insert only) manifest signature is invalid |
| 6 | "inconsistent"; (insert only) manifest filesize/filehash does not match payload | | 6 | 422 | "inconsistent"; (insert only) manifest filesize/filehash does not match payload |
| 7 | "no room"; (insert only) doesn't fit; store may contain more important bundles | | 7 | 202 | "no room"; (insert only) doesn't fit; store may contain more important bundles |
| 8 | "readonly"; (insert only) cannot modify manifest because secret is unknown | | 8 | 419 | "readonly"; (insert only) cannot modify manifest because secret is unknown |
| 9 | "busy"; Rhizome store database is currently busy (re-try) | | 9 | 423 | "busy"; Rhizome store database is currently busy (re-try) |
#### Bundle status message #### 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 outcome of the payload operation, and elaborates on the the reason for the
accompanying *bundle status code*. Some codes have different meanings in 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 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 | | code | HTTP | meaning |
|:----:|:--------------------------------------------------------------------- | |:----:|:----:|:--------------------------------------------------------------------- |
| -1 | internal error | | -1 | 500 | internal error |
| 0 | empty payload (zero length) | | 0 | 201 | empty payload (zero length) |
| 1 | (fetch) payload not found; (insert) payload added to store | | 1 | 201 | (fetch) payload not found; (insert) payload added to store |
| 2 | (fetch) payload found; (insert) payload already in store | | 2 | 200 | (fetch) payload found; (insert) payload already in store |
| 3 | payload size does not match manifest *filesize* field | | 3 | 422 | payload size does not match manifest *filesize* field |
| 4 | payload hash does not match manifest *filehash* field | | 4 | 422 | payload hash does not match manifest *filehash* field |
| 5 | payload key unknown: (fetch) cannot decrypt; (insert) cannot encrypt | | 5 | 419 | payload key unknown: (fetch) cannot decrypt; (insert) cannot encrypt |
| 6 | (insert only) payload is too big to fit in store | | 6 | 202 | (insert only) payload is too big to fit in store |
| 7 | (insert only) payload evicted; other payloads are ranked higher | | 7 | 202 | (insert only) payload evicted; other payloads are ranked higher |
#### Payload status message #### Payload status message
@ -685,7 +739,7 @@ be *200 OK* and:
nul (0) byte followed by the manifest's binary signature nul (0) byte followed by the manifest's binary signature
If the **manifest is not found** in the local Rhizome store, then the response 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 [bundle status code](#bundle-status-code) will be 0
* the [payload status code](#payload-status-code), if present in the response, * the [payload status code](#payload-status-code), if present in the response,
is not relevant, so must be ignored 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 if the payload is encrypted (the manifest's `crypt` field is 1) then the
payload is not decrypted 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 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 [bundle status code](#bundle-status-code) will be 0
* the [payload status code](#payload-status-code), if present in the response, * the [payload status code](#payload-status-code), if present in the response,
is not relevant, so must be ignored 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) * the response's content is the [Rhizome JSON result](#rhizome-json-result)
object 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 ### GET /restful/rhizome/BID/decrypted.bin
Fetches the decrypted payload for the bundle whose id is `BID` (64 hex digits), Fetches the decrypted payload for the bundle whose id is `BID` (64 hex digits),

View File

@ -237,7 +237,7 @@ static int _reserve(struct http_request *r, const char **resp, const char *src,
assert(r->reserved <= reslim); assert(r->reserved <= reslim);
size_t siz = sizeof(char**) + len + 1; size_t siz = sizeof(char**) + len + 1;
if (r->reserved + siz > reslim) { if (r->reserved + siz > reslim) {
r->response.result_code = 414; r->response.result_code = 414; // Request-URI Too Long
return 0; return 0;
} }
if (r->reserved + siz > r->parsed) { 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 414: return "Request-URI Too Long";
case 415: return "Unsupported Media Type"; case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable"; case 416: return "Requested Range Not Satisfiable";
case 419: return "Authentication Timeout";
case 422: return "Unprocessable Entity"; case 422: return "Unprocessable Entity";
case 423: return "Locked"; case 423: return "Locked";
case 429: return "Too Many Requests"; case 429: return "Too Many Requests";

46
httpd.c
View File

@ -357,9 +357,12 @@ int authorize_restful(struct http_request *r)
{ {
if (!is_from_loopback(r)) if (!is_from_loopback(r))
return 403; return 403;
if (r->request_header.origin.hostname) { // If a CORS Origin: header was supplied, then if it specifies a local site, then respond with
assert(r->request_header.origin.scheme); // Access-Control-Allow-Origin and Access-Control-Allow-Methods headers that permit other pages in
if ( ( ( strcmp(r->request_header.origin.scheme, "http") == 0 // 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.scheme, "https") == 0
) )
&& ( strcmp(r->request_header.origin.hostname, "localhost") == 0 && ( strcmp(r->request_header.origin.hostname, "localhost") == 0
@ -373,20 +376,9 @@ int authorize_restful(struct http_request *r)
) )
) )
) { ) {
strbuf sb = strbuf_local(r->response.header.allow_origin, sizeof r->response.header.allow_origin); r->response.header.allow_origin = r->request_header.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_methods = "GET, POST, OPTIONS";
r->response.header.allow_headers = "Authorization"; r->response.header.allow_headers = "Authorization";
} else {
r->response.header.allow_origin[0] = '\0';
return 403;
}
} else { } else {
return 403; return 403;
} }
@ -413,7 +405,7 @@ int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_
); );
strbuf msg = strbuf_alloca(100); strbuf msg = strbuf_alloca(100);
strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); 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; return 0;
} }
memcpy(textbuf + *textlenp, buf, len); 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 msg = strbuf_alloca(100);
strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname); 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 403; return 400;
} }
if (newlen > f->buffer_alloc_size) { if (newlen > f->buffer_alloc_size) {
if ((f->buffer = erealloc(f->buffer, newlen)) == NULL) { 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; 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, DEBUGF(httpd, "%s Content-Type: %s/%s%s%s%s%s", what, ct->type, ct->subtype,
ct->charset[0] ? "; charset=" : "", 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); strbuf_sprintf(msg, "; charset=%s", ct->charset);
if (ct->multipart_boundary[0]) if (ct->multipart_boundary[0])
strbuf_sprintf(msg, "; boundary=%s", ct->multipart_boundary); strbuf_sprintf(msg, "; boundary=%s", ct->multipart_boundary);
http_request_simple_response(&r->http, 403, strbuf_str(msg)); http_request_simple_response(&r->http, result, strbuf_str(msg));
return 403; 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); DEBUGF(httpd, "%s Content-Disposition: %s %s", what, type);
strbuf msg = strbuf_alloca(100); strbuf msg = strbuf_alloca(100);
strbuf_sprintf(msg, "%s Content-Disposition: %s", what, type); strbuf_sprintf(msg, "%s Content-Disposition: %s", what, type);
http_request_simple_response(&r->http, 403, strbuf_str(msg)); http_request_simple_response(&r->http, result, strbuf_str(msg)); // Unsupported Media Type
return 403; 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) : ""); DEBUGF(httpd, "%s \"%s\" form part %s", what, partname, text ? alloca_toprint(-1, text, textlen) : "");
strbuf msg = strbuf_alloca(100); strbuf msg = strbuf_alloca(100);
strbuf_sprintf(msg, "%s \"%s\" form part", what, partname); strbuf_sprintf(msg, "%s \"%s\" form part", what, partname);
http_request_simple_response(&r->http, 403, strbuf_str(msg)); http_request_simple_response(&r->http, result, strbuf_str(msg)); // Unsupported Media Type
return 403; return result;
} }
int http_response_init_content_range(httpd_request *r, size_t resource_length) int http_response_init_content_range(httpd_request *r, size_t resource_length)

View File

@ -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 is_http_header_complete(const char *buf, size_t len, size_t read_since_last_call);
int authorize_restful(struct http_request *r); 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_type(httpd_request *r, uint16_t result, 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_content_disposition(httpd_request *r, uint16_t result, 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_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 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); int accumulate_text(httpd_request *r, const char *partname, char *textbuf, size_t textsiz, size_t *textlenp, const char *buf, size_t len);

View File

@ -46,7 +46,9 @@ public class MeshMSCommon
{ {
if (!"application/json".equals(conn.getContentType())) if (!"application/json".equals(conn.getContentType()))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + 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")); JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "UTF-8"));
Status status = decodeRestfulStatus(json); Status status = decodeRestfulStatus(json);
throwRestfulResponseExceptions(status, conn.getURL()); throwRestfulResponseExceptions(status, conn.getURL());

View File

@ -96,6 +96,7 @@ public class RhizomeCommon
switch (status.http_status_code) { switch (status.http_status_code) {
case HttpURLConnection.HTTP_FORBIDDEN: // for crypto failure (missing secret) case HttpURLConnection.HTTP_FORBIDDEN: // for crypto failure (missing secret)
case HttpURLConnection.HTTP_NOT_FOUND: // for unknown BID case HttpURLConnection.HTTP_NOT_FOUND: // for unknown BID
case 419: // Authentication Timeout, for missing secret
case 422: // Unprocessable Entity, for invalid/malformed manifest case 422: // Unprocessable Entity, for invalid/malformed manifest
case 423: // Locked, for database busy case 423: // Locked, for database busy
case 429: // Too Many Requests, for out of manifests case 429: // Too Many Requests, for out of manifests

View File

@ -1,6 +1,6 @@
/* /*
Serval DNA HTTP RESTful interface 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 This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License 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; const char *name = NULL;
keyring_identity_extract(id, &sidp, &did, &name); keyring_identity_extract(id, &sidp, &did, &name);
if (!sidp) 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_atom json_id;
struct json_key_value json_id_kv[3]; struct json_key_value json_id_kv[3];
struct json_atom json_sid; 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 char *pin = http_request_get_query_param(&r->http, "pin");
const keyring_identity *id = keyring_create_identity(keyring, pin ? pin : ""); const keyring_identity *id = keyring_create_identity(keyring, pin ? pin : "");
if (id == NULL) 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) 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); 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 it;
keyring_iterator_start(keyring, &it); keyring_iterator_start(keyring, &it);
if (!keyring_find_sid(&it, &r->sid1)) 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) 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) 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); return http_request_keyring_response_identity(r, 200, CONTENT_TYPE_JSON, it.identity);
} }

View File

@ -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) 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; uint16_t meshms_result = 0;
const char *meshms_message = NULL;
switch (status) { switch (status) {
case MESHMS_STATUS_OK: case MESHMS_STATUS_OK:
meshms_result = 200; meshms_result = 200;
break; break;
case MESHMS_STATUS_UPDATED: case MESHMS_STATUS_UPDATED:
meshms_result = 201; meshms_result = 201; // Created
meshms_message = "Updated";
break; break;
case MESHMS_STATUS_SID_LOCKED: case MESHMS_STATUS_SID_LOCKED:
meshms_result = 419; // Authentication Timeout
meshms_message = "Identity locked";
break;
case MESHMS_STATUS_PROTOCOL_FAULT: case MESHMS_STATUS_PROTOCOL_FAULT:
meshms_result = 403; meshms_result = 500;
meshms_message = "MeshMS protocol fault";
break; break;
case MESHMS_STATUS_ERROR: case MESHMS_STATUS_ERROR:
meshms_result = 500; meshms_result = 500;
@ -101,10 +107,10 @@ static int http_request_meshms_response(struct httpd_request *r, uint16_t result
} }
if (meshms_result > result) { if (meshms_result > result) {
result = meshms_result; result = meshms_result;
message = NULL; message = meshms_message;
} }
assert(result != 0); 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; 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; r->http.response.header.content_type = CONTENT_TYPE_JSON;
if (!is_rhizome_http_enabled()) if (!is_rhizome_http_enabled())
return 403; return 404;
int ret = authorize_restful(&r->http); int ret = authorize_restful(&r->http);
if (ret) if (ret)
return ret; return ret;
@ -591,7 +597,7 @@ static int send_mime_part_end(struct http_request *hr)
httpd_request *r = (httpd_request *) hr; httpd_request *r = (httpd_request *) hr;
if (r->u.sendmsg.current_part == PART_MESSAGE) { if (r->u.sendmsg.current_part == PART_MESSAGE) {
if (r->u.sendmsg.message.length == 0) 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; 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)); DEBUGF(httpd, "received %s = %s", PART_MESSAGE, alloca_toprint(-1, r->u.sendmsg.message.buffer, r->u.sendmsg.message.length));
} else } 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; httpd_request *r = (httpd_request *) hr;
if (strcmp(h->content_disposition.type, "form-data") != 0) 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 (strcmp(h->content_disposition.name, PART_MESSAGE) == 0) {
if (r->u.sendmsg.received_message) 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; r->u.sendmsg.current_part = PART_MESSAGE;
form_buf_malloc_init(&r->u.sendmsg.message, MESHMS_MESSAGE_MAX_LEN); form_buf_malloc_init(&r->u.sendmsg.message, MESHMS_MESSAGE_MAX_LEN);
} }
else 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]) 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) 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]) 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) 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; return 0;
} }
@ -638,7 +644,7 @@ static int restful_meshms_sendmessage_end(struct http_request *hr)
{ {
httpd_request *r = (httpd_request *) hr; httpd_request *r = (httpd_request *) hr;
if (!r->u.sendmsg.received_message) 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 > 0);
assert(r->u.sendmsg.message.length <= MESHMS_MESSAGE_MAX_LEN); assert(r->u.sendmsg.message.length <= MESHMS_MESSAGE_MAX_LEN);
enum meshms_status status; enum meshms_status status;

View File

@ -74,20 +74,20 @@ static int http_request_rhizome_response(struct httpd_request *r, uint16_t resul
{ {
uint16_t rhizome_result = 0; uint16_t rhizome_result = 0;
switch (r->bundle_status) { switch (r->bundle_status) {
case RHIZOME_BUNDLE_STATUS_NEW:
rhizome_result = 201; // Created
break;
case RHIZOME_BUNDLE_STATUS_SAME: case RHIZOME_BUNDLE_STATUS_SAME:
case RHIZOME_BUNDLE_STATUS_DUPLICATE: case RHIZOME_BUNDLE_STATUS_DUPLICATE:
rhizome_result = 200; // OK rhizome_result = 200; // OK
break; break;
case RHIZOME_BUNDLE_STATUS_NEW:
rhizome_result = 201; // Created
break;
case RHIZOME_BUNDLE_STATUS_NO_ROOM: case RHIZOME_BUNDLE_STATUS_NO_ROOM:
case RHIZOME_BUNDLE_STATUS_OLD: case RHIZOME_BUNDLE_STATUS_OLD:
rhizome_result = 202; // Accepted rhizome_result = 202; // Accepted
break; break;
case RHIZOME_BUNDLE_STATUS_FAKE: case RHIZOME_BUNDLE_STATUS_FAKE:
case RHIZOME_BUNDLE_STATUS_READONLY: case RHIZOME_BUNDLE_STATUS_READONLY:
rhizome_result = 403; // Forbidden rhizome_result = 419; // Authentication Timeout
break; break;
case RHIZOME_BUNDLE_STATUS_INVALID: case RHIZOME_BUNDLE_STATUS_INVALID:
case RHIZOME_BUNDLE_STATUS_INCONSISTENT: 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; rhizome_result = 0;
switch (r->payload_status) { switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_NEW:
case RHIZOME_PAYLOAD_STATUS_STORED: case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_EMPTY: case RHIZOME_PAYLOAD_STATUS_EMPTY:
rhizome_result = 200;
break;
case RHIZOME_PAYLOAD_STATUS_NEW:
rhizome_result = 201; rhizome_result = 201;
break; break;
case RHIZOME_PAYLOAD_STATUS_TOO_BIG: 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 rhizome_result = 202; // Accepted
break; break;
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
rhizome_result = 403; // Forbidden rhizome_result = 419; // Authentication Timeout
break; break;
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: 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) static int insert_make_manifest(httpd_request *r)
{ {
if (!r->u.insert.received_manifest) 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) if ((r->manifest = rhizome_new_manifest()) == NULL)
return http_request_rhizome_response(r, 429, "Manifest table full", NULL); // Too Many Requests return http_request_rhizome_response(r, 429, "Manifest table full", NULL); // Too Many Requests
assert(r->u.insert.manifest.length <= sizeof r->manifest->manifestdata); 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_bundleid ? &r->bid: NULL,
r->u.insert.received_secret ? &r->u.insert.bundle_secret : NULL, r->u.insert.received_secret ? &r->u.insert.bundle_secret : NULL,
r->u.insert.received_author ? &r->u.insert.author: 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; int result_valid = 0;
switch (result) { switch (result) {
case RHIZOME_ADD_FILE_ERROR: 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 return http_request_rhizome_response(r, 422, message, NULL); // Unprocessable Entity
case RHIZOME_ADD_FILE_WRONG_SECRET: case RHIZOME_ADD_FILE_WRONG_SECRET:
r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY; // TODO separate enum for CLI return codes 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) if (!result_valid)
FATALF("result = %d", result); 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; httpd_request *r = (httpd_request *) hr;
if (strcmp(h->content_disposition.type, "form-data") != 0) 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 (strcmp(h->content_disposition.name, PART_AUTHOR) == 0) {
if (r->u.insert.received_author) 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. // Reject a request if this parameter comes after the manifest part.
if (r->u.insert.received_manifest) 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; r->u.insert.current_part = PART_AUTHOR;
assert(r->u.insert.author_hex_len == 0); assert(r->u.insert.author_hex_len == 0);
} }
else if (strcmp(h->content_disposition.name, PART_SECRET) == 0) { else if (strcmp(h->content_disposition.name, PART_SECRET) == 0) {
if (r->u.insert.received_secret) 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. // Reject a request if this parameter comes after the manifest part.
if (r->u.insert.received_manifest) 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; r->u.insert.current_part = PART_SECRET;
assert(r->u.insert.secret_text_len == 0); assert(r->u.insert.secret_text_len == 0);
} }
else if (strcmp(h->content_disposition.name, PART_BUNDLEID) == 0) { else if (strcmp(h->content_disposition.name, PART_BUNDLEID) == 0) {
if (r->u.insert.received_bundleid) 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. // Reject a request if this parameter comes after the manifest part.
if (r->u.insert.received_manifest) 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; r->u.insert.current_part = PART_BUNDLEID;
assert(r->u.insert.bid_text_len == 0); assert(r->u.insert.bid_text_len == 0);
} }
else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) { else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) {
// Reject a request if it has a repeated manifest part. // Reject a request if it has a repeated manifest part.
if (r->u.insert.received_manifest) 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); form_buf_malloc_init(&r->u.insert.manifest, MAX_MANIFEST_BYTES);
if ( strcmp(h->content_type.type, "rhizome") != 0 if ( strcmp(h->content_type.type, "rhizome") != 0
|| strcmp(h->content_type.subtype, "manifest") != 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) if ((strcmp(h->content_type.format, "text+binarysig") != 0)
&&strlen(h->content_type.format)) &&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; r->u.insert.current_part = PART_MANIFEST;
} }
else if (strcmp(h->content_disposition.name, PART_PAYLOAD) == 0) { else if (strcmp(h->content_disposition.name, PART_PAYLOAD) == 0) {
// Reject a request if it has a repeated payload part. // Reject a request if it has a repeated payload part.
if (r->u.insert.received_payload) 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. // Reject a request if it has a missing manifest part preceding the payload part.
if (!r->u.insert.received_manifest) 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); assert(r->manifest != NULL);
r->u.insert.current_part = PART_PAYLOAD; r->u.insert.current_part = PART_PAYLOAD;
// If the manifest does not contain a 'name' field, then assign it from the payload filename. // 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; r->u.insert.payload_size = 0;
} }
else 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; 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 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 || 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; r->u.insert.received_author = 1;
DEBUGF(rhizome, "received %s = %s", PART_AUTHOR, alloca_tohex_sid_t(r->u.insert.author)); DEBUGF(rhizome, "received %s = %s", PART_AUTHOR, alloca_tohex_sid_t(r->u.insert.author));
} }
else if (r->u.insert.current_part == PART_SECRET) { 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) 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; r->u.insert.received_secret = 1;
DEBUGF(rhizome, "received %s = %s", PART_SECRET, alloca_tohex_rhizome_bk_t(r->u.insert.bundle_secret)); 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) { 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) 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; r->u.insert.received_bundleid = 1;
DEBUGF(rhizome, "received %s = %s", PART_BUNDLEID, alloca_tohex_rhizome_bid_t(r->bid)); 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; httpd_request *r = (httpd_request *) hr;
if (!r->u.insert.received_manifest) 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) 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. // Fill in the missing manifest fields and ensure payload and manifest are consistent.
assert(r->manifest != NULL); assert(r->manifest != NULL);
DEBUGF(rhizome, "r->payload_status=%d %s", r->payload_status, rhizome_payload_status_message(r->payload_status)); 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: case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY; 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: case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
r->bundle_status = RHIZOME_BUNDLE_STATUS_NO_ROOM; r->bundle_status = RHIZOME_BUNDLE_STATUS_NO_ROOM;
return http_request_rhizome_response(r, 202, "Bundle too big", NULL); // Accepted 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) { if (!r->manifest->haveSecret) {
r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY; 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; rhizome_manifest *mout = NULL;
r->bundle_status = rhizome_manifest_finalise(r->manifest, &mout, !r->u.insert.force_new); 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: case RHIZOME_PAYLOAD_STATUS_NEW:
return http_request_rhizome_response(r, 404, "Payload not found", NULL); return http_request_rhizome_response(r, 404, "Payload not found", NULL);
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: 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_ERROR:
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:

View File

@ -813,6 +813,7 @@ enum rhizome_payload_status rhizome_finish_write(struct rhizome_write *write)
} }
if (sqlite_exec_void_retry(&retry, "COMMIT;", END) == -1) if (sqlite_exec_void_retry(&retry, "COMMIT;", END) == -1)
goto dbfailure; 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)); DEBUGF(rhizome_store, "Stored file %s", alloca_tohex_rhizome_filehash_t(write->id));
} }
write->blob_rowid = 0; write->blob_rowid = 0;

View File

@ -279,9 +279,9 @@ test_MeshmsListMessagesNoIdentity() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/messagelist.json" "http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/messagelist.json"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 419
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 419})'
assertJqGrep --ignore-case http.body '.http_status_message' 'meshms operation failed' assertJqGrep --ignore-case http.body '.http_status_message' 'identity locked'
assertJq http.body 'contains({"meshms_status_code": 2})' assertJq http.body 'contains({"meshms_status_code": 2})'
assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown' 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" "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*message.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*message.*form.*part'
executeOk_servald meshms list messages $SIDA1 $SIDA2 executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutLineCount '==' 2 assertStdoutLineCount '==' 2
@ -454,8 +454,8 @@ test_MeshmsSendDuplicateMessage() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*message.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*message.*form.*part'
executeOk_servald meshms list messages $SIDA1 $SIDA2 executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutLineCount '==' 2 assertStdoutLineCount '==' 2
@ -476,8 +476,8 @@ test_MeshmsSendMessageMissingContentType() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*content.*type' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*content.*type'
executeOk_servald meshms list messages $SIDA1 $SIDA2 executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutLineCount '==' 2 assertStdoutLineCount '==' 2
@ -498,8 +498,8 @@ test_MeshmsSendMessageUnsupportedContentType() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 415
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 415})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*content.*type' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*content.*type'
executeOk_servald meshms list messages $SIDA1 $SIDA2 executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutLineCount '==' 2 assertStdoutLineCount '==' 2
@ -520,8 +520,8 @@ test_MeshmsSendMessageMissingCharset() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*charset' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*charset'
executeOk_servald meshms list messages $SIDA1 $SIDA2 executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutLineCount '==' 2 assertStdoutLineCount '==' 2
@ -542,8 +542,8 @@ test_MeshmsSendMessageUnsupportedCharset() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 415
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 415})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*charset' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*charset'
executeOk_servald meshms list messages $SIDA1 $SIDA2 executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutLineCount '==' 2 assertStdoutLineCount '==' 2
@ -564,9 +564,9 @@ test_MeshmsSendNoIdentity() {
"http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/sendmessage" "http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/sendmessage"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 419
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 419})'
assertJqGrep --ignore-case http.body '.http_status_message' 'meshms operation failed' assertJqGrep --ignore-case http.body '.http_status_message' 'identity locked'
assertJq http.body 'contains({"meshms_status_code": 2})' assertJq http.body 'contains({"meshms_status_code": 2})'
assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown' assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown'
} }

View File

@ -479,7 +479,7 @@ test_RhizomePayloadDecryptedForeign() {
--basic --user harry:potter \ --basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[1]}/decrypted.bin" "http://$addr_localhost:$PORTA/restful/rhizome/${BID[1]}/decrypted.bin"
tfw_cat http.headers$n decrypted.bin$n 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-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-Bundle-Status-Message: .*bundle already in store.*$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 5$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-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$" assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
else else
assertStdoutIs 403 assertStdoutIs 419
assertJq nfile$n.manifest 'contains({"http_status_code": 403})' assertJq nfile$n.manifest 'contains({"http_status_code": 419})'
assertJqGrep --ignore-case nfile$n.manifest '.http_status_message' "missing bundle secret" assertJqGrep --ignore-case nfile$n.manifest '.http_status_message' "missing bundle secret"
fi fi
done done
@ -881,8 +881,8 @@ test_RhizomeInsertMissingManifest() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -904,8 +904,8 @@ test_RhizomeInsertIncorrectManifestType() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 415
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 415})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported content-type.*manifest.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported content-type.*manifest.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -927,8 +927,8 @@ test_RhizomeInsertIncorrectManifestFormat() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 415
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 415})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*format.*manifest.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*format.*manifest.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -953,8 +953,8 @@ test_RhizomeInsertDuplicateManifest() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*manifest.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*manifest.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -1001,8 +1001,8 @@ test_RhizomeInsertMissingPayload() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*payload.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*payload.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -1027,8 +1027,8 @@ test_RhizomeInsertDuplicatePayload() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*payload.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*payload.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -1051,8 +1051,8 @@ test_RhizomeInsertPartOrder() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part'
executeOk_servald rhizome list executeOk_servald rhizome list
assert_rhizome_list assert_rhizome_list
@ -1076,8 +1076,8 @@ test_RhizomeInsertPartUnsupported() {
"http://$addr_localhost:$PORTA/restful/rhizome/insert" "http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body tfw_cat http.header http.body
assertExitStatus == 0 assertExitStatus == 0
assertStdoutIs 403 assertStdoutIs 400
assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"http_status_code": 400})'
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*form.*part' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*form.*part'
assertJqGrep http.body '.http_status_message' 'happyhappy' assertJqGrep http.body '.http_status_message' 'happyhappy'
executeOk_servald rhizome list executeOk_servald rhizome list