Improve and document HTTP REST Rhizome import

The REST Rhizome import request now requires the 'id' and 'version'
query parameters to either both be supplied or neither, and fails if
they do not match the manifest that is supplied in the request body.
Added a test case for this.

Added a test case to ensure that if the 'id' and 'version' query
parameters cause a hit (already in store) then the response is sent
immediately without reading the request body.

Improve the documentation for the REST Rhizome import request.
This commit is contained in:
Andrew Bettison 2017-10-19 22:13:30 +10:30
parent 5eb19f1a16
commit 34e2e8d4bc
9 changed files with 467 additions and 220 deletions

View File

@ -222,6 +222,25 @@ The manifest's signature section is a sequence of one or more concatenated
signature, made using its Bundle Secret, although the manifest format allows
for the possibility of multi-signed bundles in future.
### Valid manifest
A [manifest](#manifest) is *valid* if:
* the `id` field is present and valid
* the `version` field is present and valid
* the `filesize` field is present and valid
* the `service` field is present and valid
* the `date` field is present and valid
* if `filesize` is zero then there is no `filehash` field, otherwise the
`filehash` field is present and valid
* if `service` is `file` then a `name` field is present
* if `service` is `MeshMS1` or `MeshMS2` then the `sender` and `recipient`
fields are both present and valid
Note that *validity* does not require that the manifest's signature be
*verified*. A manifest with an unverified or missing signature may still be
*valid*.
### Bundle author
A bundle's *author* is the identity whose [Rhizome Secret](#rhizome-secret)
@ -238,7 +257,7 @@ author's identity can only be deduced if it is an [unlocked
identity](#get-restfulkeyringidentitiesjson) in the local keyring. Serval
DNA tries the [Rhizome Secret](#rhizome-secret) of every unlocked identity
until it finds one that, when used to decode the Bundle Key, yields a [Bundle
Secret](#bundle secret) that correctly generates the manifest's signature. If
Secret](#bundle-secret) that correctly generates the manifest's signature. If
no unlocked identity is found, then the author is unknown.
Rhizome nodes that do not possess the unlocked author identity cannot derive
@ -514,17 +533,18 @@ 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 | 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 |
| code | HTTP | meaning |
|:----:|:-------:|:-------------------------------------------------------------------------- |
| -1 | [500][] | internal error |
| 0 | [201][] | "empty"; zero length payload |
| 1 | [201][] | "new"; (fetch) payload not found; (insert) payload added to store |
| 2 | [200][] | "found"; (fetch) payload found; (insert) payload already in store |
| 3 | [422][] | "wrong size"; payload size does not match manifest *filesize* field |
| 4 | [422][] | "wrong hash"; payload hash does not match manifest *filehash* field |
| 5 | [419][] | "key unknown"; (fetch) cannot decrypt; (insert) cannot encrypt |
| 6 | [202][] | "too big"; (insert only) payload is too big to fit in store |
| 7 | [202][] | "evicted"; (insert only) payload evicted; other payloads are ranked higher |
| 8 | [423][] | "busy"; Rhizome store database is currently busy (re-try) |
### Payload status message
@ -648,12 +668,11 @@ Fetches the manifest for the bundle whose id is `BID` (64 hex digits), eg:
If the **manifest is found** in the local Rhizome store, then the response will
be [200 OK][200] and:
* the [bundle status code](#bundle-status-code) will be 1
* the [bundle status code](#bundle-status-code) for "same"
* the [payload status code](#payload-status-code), if present in the response,
is not relevant, so must be ignored
* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) give
information about the found bundle, some of which is duplicated from the
manifest
* the [bundle headers](#rhizome-http-response-bundle-headers) give information
about the found bundle, some of which is duplicated from the manifest
* the response's Content-Type is [rhizome/manifest](#rihzomemanifest)
* the response's Content-Length is the size, in bytes, of the entire manifest,
including its binary signature
@ -662,11 +681,11 @@ be [200 OK][200] and:
If the **manifest is not found** in the local Rhizome store, then the response
will be [404 Not Found][404] and:
* the [bundle status code](#bundle-status-code) will be 0
* the [bundle status code](#bundle-status-code) for "new"
* the [payload status code](#payload-status-code), if present in the response,
is not relevant, so must be ignored
* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) are
absent from the response
* the [bundle headers](#rhizome-http-response-bundle-headers) are absent from
the response
* the response's content is the [Rhizome JSON result](#rhizome-json-result)
object
@ -680,12 +699,12 @@ digits), eg:
If the **manifest and the payload are both found** in the local Rhizome store,
then the response will be [200 OK][200] and:
* the [bundle status code](#bundle-status-code) will be 1
* the [payload status code](#payload-status-code) will be 0 if the payload has
zero length, otherwise 2
* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) give
information about the found bundle, some of which is duplicated from the
manifest
* the [bundle status code](#bundle-status-code) for "same"
* the [payload status code](#payload-status-code) for "empty" if the payload
has zero length, otherwise "found"
* the [bundle headers](#rhizome-http-response-bundle-headers) give information
about the found bundle, some of which is duplicated from
the manifest
* the response's Content-Type is **application/octet-stream**
* the response's Content-Length is the size, in bytes, of the raw payload
* the response's content is the bundle's payload exactly as stored in Rhizome;
@ -695,21 +714,21 @@ then the response will be [200 OK][200] and:
If the **manifest is not found** in the local Rhizome store, then the response
will be [404 Not Found][404] and:
* the [bundle status code](#bundle-status-code) will be 0
* the [bundle status code](#bundle-status-code) for "new"
* the [payload status code](#payload-status-code), if present in the response,
is not relevant, so must be ignored
* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) are
absent from the response
* the [bundle headers](#rhizome-http-response-bundle-headers) are absent from
the response
* 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 Not Found][404] 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 [bundle status code](#bundle-status-code) for "same"
* the [payload status code](#payload-status-code) for "new"
* the [bundle headers](#rhizome-http-response-bundle-headers) give information
about the found manifest
* the response's content is the [Rhizome JSON result](#rhizome-json-result)
object
@ -749,12 +768,11 @@ with the following variations:
If the **payload is encrypted** and the **payload secret is known**, then
the response will be [200 OK][200] and:
* the [bundle status code](#bundle-status-code) will be 1
* the [payload status code](#payload-status-code) will be 0 if the decrypted
payload has zero length, otherwise 2
* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) give
information about the found bundle, some of which is duplicated from the
manifest
* the [bundle status code](#bundle-status-code) for "same"
* the [payload status code](#payload-status-code) for "empty" if the decrypted
payload has zero length, otherwise "found"
* the [bundle headers](#rhizome-http-response-bundle-headers) give information
about the found bundle, some of which is duplicated from the manifest
* the response's Content-Type is **application/octet-stream**
* the response's Content-Length is the size, in bytes, of the decrypted
payload
@ -763,12 +781,12 @@ the response will be [200 OK][200] and:
If the **payload is encrypted** and the **payload secret is not known** then:
* the request will fail with status [419 Authentication Timeout][419]
* the [Rhizome response bundle headers](#rhizome-response-bundle-headers) give
information about the found manifest
* the [bundle headers](#rhizome-http-response-bundle-headers) give information
about the found manifest
* the response body is a [Rhizome JSON result](#rhizome-json-result) object,
in which:
* the [bundle status code](#bundle-status-code) is 0
* the [payload status code](#payload-status-code) is 5
* the [bundle status code](#bundle-status-code) is for "new"
* the [payload status code](#payload-status-code) is for "key unknown"
### POST /restful/rhizome/insert
@ -849,6 +867,10 @@ The parameters are all optional under various conditions:
* the *payload* parameter must not be supplied if the `filesize` field in
the *manifest* parameter is zero.
The response body is always a [Rhizome JSON result](#rhizome-json-result)
object, and the [bundle headers](#rhizome-http-response-bundle-headers) give
information about the manifest.
The insertion logic proceeds in the following steps:
1. If a *bundle-id* parameter was supplied and the given bundle exists in the
@ -859,8 +881,8 @@ The insertion logic proceeds in the following steps:
fields are copied into the new manifest, overwriting any that were copied
in step 1. If the partial manifest is malformed (syntax error) or contains
a core field with an invalid value, then the request fails with status [422
Unprocessable Entity][422] and the [bundle status
code](#bundle-status-code) for “invalid”.
Unprocessable Entity][422] and the [bundle status code](#bundle-status-code)
for “invalid”.
3. If the `tail` field is present in the new manifest then the new bundle is a
[journal](#journal), so the request fails with status [422 Unprocessable
@ -935,27 +957,13 @@ The insertion logic proceeds in the following steps:
stored in the store, and its size and [SHA-512][] digest computed. If the
manifest is missing either or both of the `filesize` and `filehash` fields,
then the missing ones are filled in from the computed values. If the
manifest had a `filesize` or `filehash` field that does not match the
computed value, then the request fails with status [422 Unprocessable
Entity][422] and the [bundle status code](#bundle-status-code) for
“inconsistent”.
manifest `filesize` or `filehash` fields do not match the computed values,
then the request fails with status [422 Unprocessable Entity][422] and the
[bundle status code](#bundle-status-code) for “inconsistent”.
8. The manifest is *validated* to ensure that:
* the `id` field is present
* the `version` field is present
* the `filesize` field is present
* if `filesize` is zero then there is no `filehash` field
* if `filesize` is non-zero then the `filehash` field is present
* if `service` is `file` then a `name` field is present
* if `service` is `MeshMS1` or `MeshMS2` then the `sender` and `recipient`
fields are both present
* the `service` field contains no invalid characters
* the `date` field is present
If validation fails, the request fails with status [422 Unprocessable
Entity][422] and the [bundle status code](#bundle-status-code) for
“invalid”.
8. If the manifest is not [valid](#valid-manifest) then the request fails with
status [422 Unprocessable Entity][422] and the [bundle status
code](#bundle-status-code) for “invalid”.
9. If step 4 set the `id` field (either derived from the *bundle-secret*
parameter or randomly generated) and the bundle is a *duplicate* of a
@ -988,10 +996,10 @@ The insertion logic proceeds in the following steps:
Accepted][202] and the [bundle status code](#bundle-status-code) for
“old”.
12. The new manifest is stored in the Rhizome store, replacing any existing
manifest with the same [Bundle ID](#bundle-id). The request returns status
[201 Created][201] and the [bundle status code](#bundle-status-code) for
“new”.
12. Otherwise, the new manifest is stored in the Rhizome store, replacing any
existing manifest with the same [Bundle ID](#bundle-id). The request
returns status [201 Created][201] and the [bundle status
code](#bundle-status-code) for “new”.
### POST /restful/rhizome/append
@ -1047,23 +1055,89 @@ other means, such as exporting from another store using the [manifest
request](#get-restfulrhizomebidrhm) and the [raw payload
request](#get-restfulrhizomebidrawbin).
This request accepts the following parameters using a [Content-Type][] of
The response body is always a [Rhizome JSON result](#rhizome-json-result)
object, and the [bundle headers](#rhizome-http-response-bundle-headers) give
information about the imported manifest, whether or not it was already present
in the store.
This request accepts the following optional [query parameters][] in the *path*:
* **id** = the [ID](#bundle-id) of the bundle
* **version** = the [version](#bundle-version) of the bundle
Both query parameters must be supplied together, or neither. If only one is
supplied, or has an invalid value, then then the request fails with status [400
Bad request][400] and a message like Missing "id" parameter or Invalid
"version" parameter.
This request also accepts the following parameters using a [Content-Type][] of
[multipart/form-data][], in which each parameter has its own content type:
* **manifest** (required) = a signed [manifest](#manifest):
* [Content-Type][] must be [rhizome/manifest](#rhizomemanifest)
* [Content-Type][] must be [rhizome/manifest](#rhizomemanifest);
* if the **id** and **version** query parameters were supplied, then their
values must match the corresponding manifest fields.
* **payload** = the content of the bundle's payload:
* [Content-Type][] is currently ignored, but in future it may be used to
determine the default values of some manifest fields;
* the *payload* parameter must be supplied if the `filesize` field in the
*manifest* parameter is non-zero, otherwise it must not be supplied;
* this parameter must come after the *manifest* parameter, otherwise the
request fails with status [400 Bad Request][400] and the message Missing
"manifest" form part;
* the *payload* parameter must not be supplied if the `filesize` field in
the *manifest* parameter is zero;
* [Content-Type][] is currently ignored, but in future it may be used to
determine the default values of some manifest fields;
* if the bundle is encrypted (the manifest `crypt` field is 1) then the
payload must be in encrypted form, not plain text.
The import logic proceeds in the following steps:
1. If the `id` and `version` [query parameters][] were given, then check if the
store already contains a bundle with that [Bundle ID](#bundle-id) and
[Bundle version](#bundle-version). If so, then the request succeeds with
status [200 OK][200] and:
* the remaining body of the request will not be read
* the [bundle status code](#bundle-status-code) is "same"
* the [payload status code](#payload-status-code) is "empty" if the
payload has zero length, otherwise "found"
* the only [bundle headers](#rhizome-http-response-bundle-headers) in the
response are:
* `Serval-Rhizome-Bundle-Id`
* `Serval-Rhizome-Bundle-Version`
* `Serval-Rhizome-Bundle-Filesize`
2. As soon as the `manifest` form part is received, if the manifest is not
[valid](#valid-manifest) or if the `id` and `version` query parameters were
supplied but do not match the `id` and `version` fields of the manifest,
then the request fails with status [422 Unprocessable Entity][422] and the
[bundle status code](#bundle-status-code) for “invalid”.
3. If the manifest's signature does not verify, then the request fails with
status [419 Authentication Timeout][419] and the [bundle status
code](#bundle-status-code) for “fake”.
4. If the *payload* form part is provided and is non-empty, then its content
is stored in the store, and its size and [SHA-512][] digest computed. If
the manifest `filesize` and `filehash` fields do not match the computed
values, then the request fails with status [422 Unprocessable Entity][422]
and the [bundle status code](#bundle-status-code) for “inconsistent”.
5. If the Rhizome store already contains a manifest with the same
[Bundle ID](#bundle-id), then its version is compared with the new
manifest's version.
* If they have the same version, then the new manifest is not stored, and
the request returns status [200 OK][200] and the [bundle status
code](#bundle-status-code) for “same”.
* If the new manifest's version is less than the stored manifest's, then
the new manifest is not stored, and the request returns status [202
Accepted][202] and the [bundle status code](#bundle-status-code) for
“old”.
6. Otherwise, the new manifest is stored in the Rhizome store, replacing any
existing manifest with the same [Bundle ID](#bundle-id). The request
returns status [201 Created][201] and the [bundle status
code](#bundle-status-code) for “new”.
-----
**Copyright 2015-2017 Serval Project Inc.**

View File

@ -101,8 +101,6 @@ For example:
GET requests only accept parameters as [percent encoded][] [query parameters][]
in the *path*.
[query parameters]: http://tools.ietf.org/html/rfc3986#section-3.4
### POST
A **POST** request has a similar structure to a GET request except that the
@ -356,6 +354,7 @@ fetched entity forms the body of the response) if the request supplied a
The HTTP request was malformed, and should not be repeated without
modifications. This could be for several reasons:
- invalid [query parameter][] in the *path*
- invalid syntax in the request header block
- a [POST](#post) request parameter is missing, duplicated or out of order
- a [POST](#post) request was given an unsupported parameter
@ -642,6 +641,7 @@ Available under the [Creative Commons Attribution 4.0 International licence][CC
[Content-Type]: https://tools.ietf.org/html/rfc7578#section-4.4
[application/x-www-form-urlencoded]: https://tools.ietf.org/html/rfc1866#section-8.2.1
[percent encoded]: https://en.wikipedia.org/wiki/Percent-encoding
[query parameter]: https://en.wikipedia.org/wiki/Query_string
[JSON]: https://en.wikipedia.org/wiki/JSON
[UTF-8]: https://en.wikipedia.org/wiki/UTF-8
[jq(1)]: https://stedolan.github.io/jq/

View File

@ -188,7 +188,7 @@ typedef int (HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER)(struct http_request *, strbu
int generate_http_content_from_strbuf_chunks(struct http_request *, char *, size_t, struct http_content_generator_result *, HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER *);
typedef int HTTP_REQUEST_PARSER(struct http_request *);
typedef void HTTP_RENDERER(struct http_request *, strbuf);
typedef void HTTP_RENDERER(const struct http_request *, strbuf);
struct http_request {
struct sched_ent alarm; // MUST BE FIRST ELEMENT

View File

@ -80,7 +80,8 @@ typedef struct httpd_request
*/
rhizome_bid_t bid;
/* For requests/responses that contain a 64-bit unsigned integer (eg, SQLite ROWID, byte offset).
/* For requests/responses that contain a 64-bit unsigned integer (eg,
* manifest version, SQLite ROWID, byte offset).
*/
uint64_t ui64;
@ -140,7 +141,12 @@ typedef struct httpd_request
// If this is really a (journal) append request
bool_t appending:1;
// Whether this is an import request
bool_t importing:1;
// Whether the Bundle ID and version were supplied on the command line
// (must be consistent with the supplied manifest); the supplied values are
// in 'bid' and 'ui64'.
bool_t verify_id_version:1;
// Which parts have already been received
bool_t received_author:1;
bool_t received_secret:1;

View File

@ -74,9 +74,9 @@ typedef struct rhizome_manifest
* before the first byte in the payload), its size (number of bytes) and the
* hash of its content. Bundle size = tail + filesize.
*/
uint64_t tail;
uint64_t tail; // only valid if is_journal
uint64_t filesize;
rhizome_filehash_t filehash;
rhizome_filehash_t filehash; // only valid if filesize != 0 and has_filehash
/* All the manifest fields in original order (the order affects the manifest
* hash which was used to sign the manifest, so the signature can only be
@ -121,7 +121,7 @@ typedef struct rhizome_manifest
*/
bool_t selfSigned:1;
/* Set if the ID field (sign_key.public_key) contains a bundle ID.
/* Set if the ID field (keypair.public_key) contains a bundle ID.
*/
bool_t has_id:1;
@ -622,8 +622,8 @@ int rhizome_manifest_extract_signature(rhizome_manifest *m, unsigned *ofs);
enum rhizome_bundle_status rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found);
int rhizome_manifest_to_bar(rhizome_manifest *m, rhizome_bar_t *bar);
enum rhizome_bundle_status rhizome_is_bar_interesting(const rhizome_bar_t *bar);
enum rhizome_bundle_status rhizome_is_interesting(const rhizome_bid_t *bid, uint64_t version);
#define rhizome_is_manifest_interesting(M) rhizome_is_interesting(&(M)->keypair.public_key, (M)->version)
enum rhizome_bundle_status rhizome_is_interesting(const rhizome_bid_t *bid, uint64_t version, uint64_t *filesizep);
#define rhizome_is_manifest_interesting(M) rhizome_is_interesting(&(M)->keypair.public_key, (M)->version, NULL)
enum rhizome_bundle_status rhizome_retrieve_manifest(const rhizome_bid_t *bid, rhizome_manifest *m);
enum rhizome_bundle_status rhizome_retrieve_manifest_by_prefix(const unsigned char *prefix, unsigned prefix_len, rhizome_manifest *m);
enum rhizome_bundle_status rhizome_retrieve_manifest_by_hash_prefix(const uint8_t *prefix, unsigned prefix_len, rhizome_manifest *m);

View File

@ -1401,7 +1401,7 @@ struct rhizome_bundle_result rhizome_fill_manifest(rhizome_manifest *m, const ch
FALLTHROUGH; // to set the BK field...
case NEW_BUNDLE_ID:
assert(m->has_id);
// If no 'authorSidp' parameter was supplied but the manifest has a 'sender' field, then use the
// If the manifest has no author but does have a 'sender' field, then use the
// sender as the author.
if (m->authorship == ANONYMOUS && m->has_sender)
rhizome_manifest_set_author(m, &m->sender);

View File

@ -2049,14 +2049,14 @@ int rhizome_delete_manifest(const rhizome_bid_t *bidp)
return rhizome_delete_manifest_retry(&retry, bidp);
}
static enum rhizome_bundle_status is_interesting(const char *id_hex, uint64_t version)
static enum rhizome_bundle_status is_interesting(const char *id_hex, uint64_t version, uint64_t *filesizep)
{
IN();
// do we have this bundle [or later]?
sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT;
sqlite3_stmt *statement = sqlite_prepare_bind(&retry,
"SELECT version, filehash FROM MANIFESTS WHERE id LIKE ? AND version >= ?",
"SELECT version, filesize, filehash FROM MANIFESTS WHERE id LIKE ? AND version >= ?",
TEXT_TOUPPER, id_hex,
INT64, version,
END);
@ -2067,33 +2067,42 @@ static enum rhizome_bundle_status is_interesting(const char *id_hex, uint64_t ve
int stepcode;
if ((stepcode = sqlite_step_retry(&retry, statement)) == SQLITE_ROW){
uint64_t q_version = sqlite3_column_int64(statement, 0);
const char *q_filehash = (const char *) sqlite3_column_text(statement, 1);
uint64_t q_filesize = sqlite3_column_int64(statement, 1);
const char *q_filehash = (const char *) sqlite3_column_text(statement, 2);
if (filesizep)
*filesizep = q_filesize;
if (q_version > version){
status = RHIZOME_BUNDLE_STATUS_OLD;
}else{
status = RHIZOME_BUNDLE_STATUS_SAME;
// unless we are missing the payload...
if (q_filehash && *q_filehash) {
rhizome_filehash_t hash;
if (str_to_rhizome_filehash_t(&hash, q_filehash) == -1) {
WHYF("Malformed filehash %s", q_filehash);
status = RHIZOME_BUNDLE_STATUS_ERROR;
}else{
enum rhizome_payload_status pstatus;
switch((pstatus = rhizome_exists(&hash))){
case RHIZOME_PAYLOAD_STATUS_NEW:
status = RHIZOME_BUNDLE_STATUS_NEW;
break;
case RHIZOME_PAYLOAD_STATUS_STORED:
break;
case RHIZOME_PAYLOAD_STATUS_BUSY:
status = RHIZOME_BUNDLE_STATUS_BUSY;
break;
default:
status = RHIZOME_BUNDLE_STATUS_ERROR;
break;
if (q_filesize) {
if (q_filehash && *q_filehash) {
rhizome_filehash_t hash;
if (str_to_rhizome_filehash_t(&hash, q_filehash) == -1) {
WHYF("Malformed filehash %s", q_filehash);
status = RHIZOME_BUNDLE_STATUS_ERROR;
}else{
// unless we are missing the payload...
enum rhizome_payload_status pstatus;
switch((pstatus = rhizome_exists(&hash))){
case RHIZOME_PAYLOAD_STATUS_NEW:
status = RHIZOME_BUNDLE_STATUS_NEW;
break;
case RHIZOME_PAYLOAD_STATUS_STORED:
break;
case RHIZOME_PAYLOAD_STATUS_BUSY:
status = RHIZOME_BUNDLE_STATUS_BUSY;
break;
default:
status = RHIZOME_BUNDLE_STATUS_ERROR;
break;
}
}
} else {
WHYF("Missing filehash");
status = RHIZOME_BUNDLE_STATUS_ERROR;
}
}
}
@ -2114,10 +2123,10 @@ enum rhizome_bundle_status rhizome_is_bar_interesting(const rhizome_bar_t *bar)
char id_hex[RHIZOME_BAR_PREFIX_BYTES *2 + 2];
tohex(id_hex, RHIZOME_BAR_PREFIX_BYTES * 2, rhizome_bar_prefix(bar));
strcat(id_hex, "%");
return is_interesting(id_hex, rhizome_bar_version(bar));
return is_interesting(id_hex, rhizome_bar_version(bar), NULL);
}
enum rhizome_bundle_status rhizome_is_interesting(const rhizome_bid_t *bid, uint64_t version)
enum rhizome_bundle_status rhizome_is_interesting(const rhizome_bid_t *bid, uint64_t version, uint64_t *filesizep)
{
return is_interesting(alloca_tohex_rhizome_bid_t(*bid), version);
return is_interesting(alloca_tohex_rhizome_bid_t(*bid), version, filesizep);
}

View File

@ -35,7 +35,9 @@ DECLARE_HANDLER("/restful/rhizome/import", restful_rhizome_import);
DECLARE_HANDLER("/restful/rhizome/append", restful_rhizome_append);
DECLARE_HANDLER("/restful/rhizome/", restful_rhizome_);
static HTTP_RENDERER render_manifest_headers;
static HTTP_RENDERER render_status_and_manifest_headers;
static HTTP_RENDERER render_status_and_import_headers;
static void on_rhizome_bundle_added(httpd_request *r, rhizome_manifest *m);
static void finalise_union_read_state(httpd_request *r)
@ -160,6 +162,8 @@ static int http_request_rhizome_response(struct httpd_request *r, uint16_t http_
rhizome_http_status = 422; // Unprocessable Entity
break;
case RHIZOME_PAYLOAD_STATUS_BUSY:
rhizome_http_status = 423; // Locked
break;
case RHIZOME_PAYLOAD_STATUS_ERROR:
rhizome_http_status = 500;
break;
@ -234,7 +238,6 @@ static int restful_open_cursor(httpd_request *r)
static int restful_rhizome_bundlelist_json(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
r->http.render_extra_headers = render_manifest_headers;
if (!is_rhizome_http_enabled())
return 404;
int ret = authorize_restful(&r->http);
@ -420,7 +423,7 @@ static int insert_mime_part_body(struct http_request *, char *, size_t);
static int restful_rhizome_insert(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
r->http.render_extra_headers = render_manifest_headers;
r->http.render_extra_headers = render_status_and_manifest_headers;
if (!is_rhizome_http_enabled())
return 404;
int ret = authorize_restful(&r->http);
@ -451,33 +454,56 @@ static int restful_rhizome_insert(httpd_request *r, const char *remainder)
static int restful_rhizome_import(httpd_request *r, const char *remainder)
{
int ret;
// Do this first to check for request enabled, authorization, and POST.
r->u.insert.importing = 1;
if ((ret = restful_rhizome_insert(r, remainder))!=1)
int ret;
if ((ret = restful_rhizome_insert(r, remainder)) != 1)
return ret;
r->http.render_extra_headers = render_status_and_import_headers;
const char *s_id = http_request_get_query_param(&r->http, "id");
const char *s_version = http_request_get_query_param(&r->http, "version");
// if we can reject the request now, the client won't have to send us the full manifest and payload
if (s_id && s_version){
rhizome_bid_t bid;
uint64_t version;
if (str_to_rhizome_bid_t(&bid, s_id) != -1
&& str_to_uint64(s_version, 10, &version, NULL) == 1){
r->bundle_result.status = rhizome_is_interesting(&bid, version);
switch (r->bundle_result.status){
case RHIZOME_BUNDLE_STATUS_NEW:
case RHIZOME_BUNDLE_STATUS_BUSY:
break;
default:
return http_request_rhizome_response(r, 0, NULL);
}
// If the client provides the Bundle ID and version on the command line, we can potentially reject
// the request immediately, to save the client sending the full manifest and payload.
#define PARAM_ID "id"
#define PARAM_VERSION "version"
const char *q_id = http_request_get_query_param(&r->http, PARAM_ID);
const char *q_version = http_request_get_query_param(&r->http, PARAM_VERSION);
if (q_id && !q_version)
return http_request_rhizome_response(r, 400, "Missing \"" PARAM_VERSION "\" parameter");
if (!q_id && q_version)
return http_request_rhizome_response(r, 400, "Missing \"" PARAM_ID "\" parameter");
if (q_id && str_to_rhizome_bid_t(&r->bid, q_id) == -1)
return http_request_rhizome_response(r, 400, "Invalid \"" PARAM_ID "\" parameter");
if (q_version && !str_to_uint64(q_version, 10, &r->ui64, NULL))
return http_request_rhizome_response(r, 400, "Invalid \"" PARAM_VERSION "\" parameter");
#undef PARAM_ID
#undef PARAM_VERSION
if (q_id && q_version) {
r->u.insert.verify_id_version = 1; // must agree with manifest
r->u.insert.payload_size = RHIZOME_SIZE_UNSET;
r->bundle_result.status = rhizome_is_interesting(&r->bid, r->ui64, &r->u.insert.payload_size);
switch (r->bundle_result.status) {
case RHIZOME_BUNDLE_STATUS_SAME:
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
r->payload_status = r->u.insert.payload_size ? RHIZOME_PAYLOAD_STATUS_STORED
: RHIZOME_PAYLOAD_STATUS_EMPTY;
return http_request_rhizome_response(r, 0, NULL);
case RHIZOME_BUNDLE_STATUS_ERROR:
r->payload_status = RHIZOME_PAYLOAD_STATUS_ERROR;
return http_request_rhizome_response(r, 0, NULL);
case RHIZOME_BUNDLE_STATUS_NEW:
case RHIZOME_BUNDLE_STATUS_BUSY:
case RHIZOME_BUNDLE_STATUS_OLD:
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
case RHIZOME_BUNDLE_STATUS_INVALID:
case RHIZOME_BUNDLE_STATUS_FAKE:
case RHIZOME_BUNDLE_STATUS_READONLY:
case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG:
break;
}
}
return 1;
return ret;
}
static int restful_rhizome_append(httpd_request *r, const char *remainder)
@ -511,43 +537,56 @@ static int insert_make_manifest(httpd_request *r)
int n = rhizome_manifest_parse(r->manifest);
switch (n) {
case 0:
if (r->u.insert.importing){
if (r->manifest->malformed) {
r->bundle_result = rhizome_bundle_result_sprintf(RHIZOME_BUNDLE_STATUS_INVALID, "Malformed manifest: %s", r->manifest->malformed);
break;
}
if (r->u.insert.importing) {
const char *invalid_reason;
if ((invalid_reason = rhizome_manifest_validate_reason(r->manifest))){
if ((invalid_reason = rhizome_manifest_validate_reason(r->manifest))) {
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, invalid_reason);
break;
}
if (!rhizome_manifest_verify(r->manifest)){
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, "Invalid manifest: Invalid signature");
assert(r->manifest->has_id);
assert(r->manifest->version != 0);
if (r->u.insert.verify_id_version && (
cmp_rhizome_bid_t(&r->manifest->keypair.public_key, &r->bid) != 0 ||
r->manifest->version != r->ui64)
) {
r->bundle_result = rhizome_bundle_result_static(
RHIZOME_BUNDLE_STATUS_INVALID,
"Manifest does not match id/version query parameters"
);
break;
}
if (!rhizome_manifest_verify(r->manifest)) {
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_FAKE, NULL);
break;
}
r->bundle_result.status = rhizome_manifest_check_stored(r->manifest, NULL);
break;
}
if (r->manifest->malformed) {
r->bundle_result = rhizome_bundle_result_sprintf(RHIZOME_BUNDLE_STATUS_INVALID, "Malformed manifest: %s", r->manifest->malformed);
} else {
rhizome_manifest *mout = NULL;
r->bundle_result = rhizome_manifest_add_file(r->u.insert.appending, r->manifest, &mout,
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);
if (mout && mout != r->manifest){
rhizome_manifest_free(r->manifest);
r->manifest = mout;
}
rhizome_manifest *mout = NULL;
r->bundle_result = rhizome_manifest_add_file(r->u.insert.appending, r->manifest, &mout,
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);
if (mout && mout != r->manifest) {
rhizome_manifest_free(r->manifest);
r->manifest = mout;
}
break;
case 1:
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, "Invalid manifest");
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, NULL);
break;
default:
WHYF("rhizome_manifest_parse() returned %d", n);
// fall through
FALLTHROUGH;
case -1:
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_ERROR, "Error while parsing manifest");
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_ERROR, "Internal error while parsing manifest");
break;
}
@ -578,7 +617,8 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa
return http_response_content_disposition(r, 415, "Missing", h->content_disposition.type);
if (strcmp(h->content_disposition.type, "form-data") != 0)
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.importing && strcmp(h->content_disposition.name, PART_AUTHOR) == 0) {
if (r->u.insert.received_author)
return http_response_form_part(r, 400, "Duplicate", PART_AUTHOR, NULL, 0);
// Reject a request if this parameter comes after the manifest part.
@ -592,7 +632,7 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa
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) {
else if (!r->u.insert.importing && strcmp(h->content_disposition.name, PART_SECRET) == 0) {
if (r->u.insert.received_secret)
return http_response_form_part(r, 400, "Duplicate", PART_SECRET, NULL, 0);
// Reject a request if this parameter comes after the manifest part.
@ -606,7 +646,7 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa
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) {
else if (!r->u.insert.importing && strcmp(h->content_disposition.name, PART_BUNDLEID) == 0) {
if (r->u.insert.received_bundleid)
return http_response_form_part(r, 400, "Duplicate", PART_BUNDLEID, NULL, 0);
// Reject a request if this parameter comes after the manifest part.
@ -902,7 +942,7 @@ static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
static int restful_rhizome_(httpd_request *r, const char *remainder)
{
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
r->http.render_extra_headers = render_manifest_headers;
r->http.render_extra_headers = render_status_and_manifest_headers;
if (!is_rhizome_http_enabled())
return 404;
int ret = authorize_restful(&r->http);
@ -1091,9 +1131,8 @@ int rhizome_payload_content(struct http_request *hr, unsigned char *buf, size_t
return remain ? 1 : 0;
}
static void render_manifest_headers(struct http_request *hr, strbuf sb)
static void render_status_headers(const struct httpd_request *r, strbuf sb)
{
httpd_request *r = (httpd_request *) hr;
switch (r->bundle_result.status) {
case RHIZOME_BUNDLE_STATUS_NEW:
case RHIZOME_BUNDLE_STATUS_SAME:
@ -1120,54 +1159,86 @@ static void render_manifest_headers(struct http_request *hr, strbuf sb)
strbuf_json_string(sb, status_message);
strbuf_puts(sb, "\r\n");
}
rhizome_manifest *m = r->manifest;
if (m) {
if (m->has_id)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->keypair.public_key));
if (m->version)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", m->version);
if (m->filesize != RHIZOME_SIZE_UNSET)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize);
if (m->has_filehash)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filehash: %s\r\n", alloca_tohex_rhizome_filehash_t(m->filehash));
if (m->has_sender)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Sender: %s\r\n", alloca_tohex_sid_t(m->sender));
if (m->has_recipient)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Recipient: %s\r\n", alloca_tohex_sid_t(m->recipient));
if (m->has_bundle_key)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-BK: %s\r\n", alloca_tohex_rhizome_bk_t(m->bundle_key));
switch (m->payloadEncryption) {
case PAYLOAD_CRYPT_UNKNOWN:
break;
case PAYLOAD_CLEAR:
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 0\r\n");
break;
case PAYLOAD_ENCRYPTED:
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 1\r\n");
break;
}
if (m->is_journal)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Tail: %"PRIu64"\r\n", m->tail);
if (m->has_date)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Date: %"PRIu64"\r\n", m->date);
if (m->name) {
strbuf_puts(sb, "Serval-Rhizome-Bundle-Name: ");
strbuf_append_quoted_string(sb, m->name);
strbuf_puts(sb, "\r\n");
}
if (m->service)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Service: %s\r\n", m->service);
assert(m->authorship != AUTHOR_LOCAL);
if (m->authorship == AUTHOR_AUTHENTIC)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Author: %s\r\n", alloca_tohex_sid_t(m->author));
if (m->haveSecret) {
char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1];
rhizome_bytes_to_hex_upper(m->keypair.binary, secret, RHIZOME_BUNDLE_KEY_BYTES);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Secret: %s\r\n", secret);
}
if (m->rowid)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Rowid: %"PRIu64"\r\n", m->rowid);
if (m->inserttime)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Inserttime: %"PRIu64"\r\n", m->inserttime);
}
static void render_manifest_headers(const rhizome_manifest *m, strbuf sb)
{
assert(m != NULL);
if (m->has_id)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->keypair.public_key));
if (m->version)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", m->version);
if (m->filesize != RHIZOME_SIZE_UNSET)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize);
if (m->has_filehash)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filehash: %s\r\n", alloca_tohex_rhizome_filehash_t(m->filehash));
if (m->has_sender)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Sender: %s\r\n", alloca_tohex_sid_t(m->sender));
if (m->has_recipient)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Recipient: %s\r\n", alloca_tohex_sid_t(m->recipient));
if (m->has_bundle_key)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-BK: %s\r\n", alloca_tohex_rhizome_bk_t(m->bundle_key));
switch (m->payloadEncryption) {
case PAYLOAD_CRYPT_UNKNOWN:
break;
case PAYLOAD_CLEAR:
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 0\r\n");
break;
case PAYLOAD_ENCRYPTED:
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 1\r\n");
break;
}
if (m->is_journal)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Tail: %"PRIu64"\r\n", m->tail);
if (m->has_date)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Date: %"PRIu64"\r\n", m->date);
if (m->name) {
strbuf_puts(sb, "Serval-Rhizome-Bundle-Name: ");
strbuf_append_quoted_string(sb, m->name);
strbuf_puts(sb, "\r\n");
}
if (m->service)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Service: %s\r\n", m->service);
assert(m->authorship != AUTHOR_LOCAL);
if (m->authorship == AUTHOR_AUTHENTIC)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Author: %s\r\n", alloca_tohex_sid_t(m->author));
if (m->haveSecret) {
char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1];
rhizome_bytes_to_hex_upper(m->keypair.binary, secret, RHIZOME_BUNDLE_KEY_BYTES);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Secret: %s\r\n", secret);
}
if (m->rowid)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Rowid: %"PRIu64"\r\n", m->rowid);
if (m->inserttime)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Inserttime: %"PRIu64"\r\n", m->inserttime);
}
static void render_import_headers(const struct httpd_request *r, strbuf sb)
{
if (r->u.insert.verify_id_version) {
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(r->bid));
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", r->ui64);
}
if (r->u.insert.payload_size != RHIZOME_SIZE_UNSET)
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", r->u.insert.payload_size);
}
static void render_status_and_manifest_headers(const struct http_request *hr, strbuf sb)
{
const httpd_request *r = (httpd_request *) hr;
render_status_headers(r, sb);
if (r->manifest) {
render_manifest_headers(r->manifest, sb);
}
}
static void render_status_and_import_headers(const struct http_request *hr, strbuf sb)
{
const httpd_request *r = (httpd_request *) hr;
render_status_headers(r, sb);
if (r->manifest) {
render_manifest_headers(r->manifest, sb);
} else {
render_import_headers(r, sb);
}
}

View File

@ -1326,7 +1326,7 @@ test_RhizomeInsertIncorrectFilehash() {
assert_rhizome_list
}
doc_RhizomeImport="HTTP RESTful import Rhizome bundle"
doc_RhizomeImport="HTTP RESTful Rhizome import"
setup_RhizomeImport() {
setup
set_instance +B
@ -1335,45 +1335,82 @@ setup_RhizomeImport() {
executeOk_servald rhizome add file $SIDB file1 file1.manifest
extract_manifest_id manifestid file1.manifest
extract_manifest_version version file1.manifest
executeOk_servald rhizome export manifest "${manifestid}" file1.manifest
extract_manifest_filehash filehash file1.manifest
executeOk_servald rhizome export manifest "$manifestid" file1.manifest
set_instance +A
}
test_RhizomeImport() {
# The first import with no 'id' and 'version' params adds the bundle to the
# store.
execute curl \
--silent --show-error --write-out '%{http_code}' \
--header 'Transfer-Encoding: chunked' \
--output http.body \
--trace-ascii curl.trace \
--trace-ascii curl1.trace \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome/manifest;format=text+binarysig" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/import?id=${manifestid}&version=${version}"
tfw_preserve curl.trace
tfw_preserve curl1.trace
tfw_cat http.header http.body
assertStdoutIs 201
assertStdoutIs 201 # added to store
assertGrep http.header '100 Continue'
assertGrep http.header "^Serval-Rhizome-Bundle-Id: $manifestid$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Version: $version$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Filesize: 100$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Filehash: $filehash$CR\$"
assertJq http.body 'contains({"http_status_code": 201})'
assertJq http.body 'contains({"http_status_message": "Created"})'
# Repeating the first import with no 'id' and 'version' params detects that
# the bundle is already in the store.
execute curl \
--silent --show-error --write-out '%{http_code}' \
--header 'Transfer-Encoding: chunked' \
--output http.body \
--trace-ascii curl.trace \
--trace-ascii curl2.trace \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome/manifest;format=text+binarysig" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/import?id=${manifestid}&version=${version}"
tfw_preserve curl.trace
"http://$addr_localhost:$PORTA/restful/rhizome/import"
tfw_preserve curl2.trace
tfw_cat http.header http.body
assertStdoutIs 200
assertStdoutIs 200 # already in store
assertGrep http.header '100 Continue'
assertGrep http.header "^Serval-Rhizome-Bundle-Id: $manifestid$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Version: $version$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Filesize: 100$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Filehash: $filehash$CR\$"
assertJq http.body 'contains({"http_status_code": 200})'
assertJq http.body 'contains({"http_status_message": "Bundle already in store"})'
# Repeating the first import with 'id' and 'version' query params, we get an
# early hit so the 'manifest' and 'payload' form parts can be omitted (and a
# dummy form part supplied to cause curl(1) to construct a POST request
# instead of GET) without error, because the server never tries to read the
# request body.
execute curl \
--silent --show-error --write-out '%{http_code}' \
--header 'Transfer-Encoding: chunked' \
--output http.body \
--trace-ascii curl3.trace \
--dump-header http.header \
--basic --user harry:potter \
--form 'dummy=never-parsed' \
"http://$addr_localhost:$PORTA/restful/rhizome/import?id=${manifestid}&version=${version}"
tfw_preserve curl3.trace
tfw_cat http.header http.body
assertStdoutIs 200 # already in store
assertGrep --matches=0 http.header '100 Continue'
assertGrep http.header "^Serval-Rhizome-Bundle-Id: $manifestid$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Version: $version$CR\$"
assertGrep http.header "^Serval-Rhizome-Bundle-Filesize: 100$CR\$"
assertGrep --matches=0 http.header "^Serval-Rhizome-Bundle-Filehash:"
assertJq http.body 'contains({"http_status_code": 200})'
assertJq http.body 'contains({"http_status_message": "Bundle already in store"})'
}
doc_RhizomeImportLarge="HTTP RESTful import 50 MiB Rhizome bundle"
doc_RhizomeImportLarge="HTTP RESTful Rhizome import 50 MiB"
setup_RhizomeImportLarge() {
setup
set_instance +B
@ -1382,7 +1419,7 @@ setup_RhizomeImportLarge() {
executeOk_servald rhizome add file $SIDB file1 file1.manifest
extract_manifest_id manifestid file1.manifest
extract_manifest_version version file1.manifest
executeOk_servald rhizome export manifest "${manifestid}" file1.manifest
executeOk_servald rhizome export manifest "$manifestid" file1.manifest
set_instance +A
}
test_RhizomeImportLarge() {
@ -1404,6 +1441,56 @@ test_RhizomeImportLarge() {
assertJq http.body 'contains({"http_status_message": "Created"})'
}
doc_RhizomeImportParamsBad="HTTP RESTful Rhizome import parameters must match manifest"
setup_RhizomeImportParamsBad() {
setup
set_instance +B
create_single_identity
create_file file1 100
executeOk_servald rhizome add file $SIDB file1 file1.manifest
extract_manifest_id manifestid file1.manifest
extract_manifest_version version file1.manifest
executeOk_servald rhizome export manifest "$manifestid" file1.manifest
set_instance +A
}
test_RhizomeImportParamsBad() {
# The 'id' query parameter must match the manifest's ID.
execute curl \
--silent --show-error --write-out '%{http_code}' \
--header 'Transfer-Encoding: chunked' \
--output http.body \
--trace-ascii curl1.trace \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome/manifest;format=text+binarysig" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/import?id=${BID_NONEXISTENT}&version=${version}"
tfw_preserve curl1.trace
tfw_cat http.header http.body
assertStdoutIs 422
assertGrep http.header '100 Continue'
assertJq http.body 'contains({"http_status_code": 422})'
assertJq http.body 'contains({"http_status_message": "query"})'
# The 'version' query parameter must match the manifest's version.
wrongversion=$((version + 1))
execute curl \
--silent --show-error --write-out '%{http_code}' \
--header 'Transfer-Encoding: chunked' \
--output http.body \
--trace-ascii curl2.trace \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome/manifest;format=text+binarysig" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/import?id=${manifestid}&version=${wrongversion}"
tfw_preserve curl2.trace
tfw_cat http.header http.body
assertStdoutIs 422
assertGrep http.header '100 Continue'
assertJq http.body 'contains({"http_status_code": 422})'
assertJq http.body 'contains({"http_status_message": "query"})'
}
doc_RhizomeJournalAppend="HTTP RESTful Rhizome journal create and append"
setup_RhizomeJournalAppend() {
setup