Improve Rhizome HTTP RESTful interface

Add RHIZOME_BUNDLE_STATUS_READONLY enum value

Tighten up switch statements on bundle and payload status enums (no
default labels)

Rename some recently added enum entries

Return bundle status and payload status in HTTP responses

Add test for failing to decrypt a foreign encrypted bundle payload,
fix bug that caused an assertion failure

Add tests for fetching a non-existent manifest and fetching bundles
whose payload blob is not in the store
This commit is contained in:
Andrew Bettison 2014-07-02 17:16:19 +09:30
parent 34b6ff48bf
commit cf43635789
17 changed files with 604 additions and 245 deletions

View File

@ -1761,9 +1761,9 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
case RHIZOME_PAYLOAD_STATUS_NEW:
break;
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
status = RHIZOME_BUNDLE_STATUS_DONOTWANT;
WHY("Insufficient space to store payload");
case RHIZOME_PAYLOAD_STATUS_EVICTED:
status = RHIZOME_BUNDLE_STATUS_NO_ROOM;
INFO("Insufficient space to store payload");
break;
case RHIZOME_PAYLOAD_STATUS_ERROR:
status = RHIZOME_BUNDLE_STATUS_ERROR;
@ -1773,12 +1773,12 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
break;
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
status = RHIZOME_BUNDLE_STATUS_FAKE;
status = RHIZOME_BUNDLE_STATUS_READONLY;
break;
default:
FATALF("pstatus = %d", pstatus);
}
rhizome_manifest *mout = m;
rhizome_manifest *mout = NULL;
if (status == RHIZOME_BUNDLE_STATUS_NEW) {
if (!rhizome_manifest_validate(m) || m->malformed)
status = RHIZOME_BUNDLE_STATUS_INVALID;
@ -1790,6 +1790,7 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
}
}
}
int status_valid = 0;
switch (status) {
case RHIZOME_BUNDLE_STATUS_NEW:
if (mout && mout != m)
@ -1799,25 +1800,30 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
case RHIZOME_BUNDLE_STATUS_SAME:
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
case RHIZOME_BUNDLE_STATUS_OLD:
assert(mout != NULL);
cli_put_manifest(context, mout);
if ( manifestpath && *manifestpath
&& rhizome_write_manifest_file(mout, manifestpath, 0) == -1
)
WHYF("Could not write manifest to %s", alloca_str_toprint(manifestpath));
status_valid = 1;
break;
case RHIZOME_BUNDLE_STATUS_READONLY:
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
case RHIZOME_BUNDLE_STATUS_ERROR:
case RHIZOME_BUNDLE_STATUS_INVALID:
case RHIZOME_BUNDLE_STATUS_FAKE:
case RHIZOME_BUNDLE_STATUS_DONOTWANT:
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
status_valid = 1;
break;
default:
FATALF("status=%d", status);
// Do not use a default: label! With no default, if a new value is added to the enum, then the
// compiler will issue a warning on switch statements that do not cover all the values, which is
// a valuable tool for the developer.
}
if (mout && mout != m) {
if (!status_valid)
FATALF("status=%d", status);
if (mout && mout != m)
rhizome_manifest_free(mout);
m = NULL;
}
rhizome_manifest_free(m);
keyring_free(keyring);
keyring = NULL;

View File

@ -1840,12 +1840,14 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char
) {
hr->header.content_type = CONTENT_TYPE_TEXT;
strbuf_sprintf(sb, "%03u %s", hr->result_code, message);
if (hr->result_extra_label) {
strbuf_puts(sb, "\r\n");
strbuf_puts(sb, hr->result_extra_label);
strbuf_puts(sb, "=");
strbuf_json_atom_as_text(sb, &hr->result_extra_value);
}
unsigned i;
for (i = 0; i < NELS(hr->result_extra); ++i)
if (hr->result_extra[i].label) {
strbuf_puts(sb, "\r\n");
strbuf_puts(sb, hr->result_extra[i].label);
strbuf_puts(sb, "=");
strbuf_json_atom_as_text(sb, &hr->result_extra[i].value);
}
strbuf_puts(sb, "\r\n");
}
else if ( hr->header.content_type == CONTENT_TYPE_JSON
@ -1854,24 +1856,28 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char
hr->header.content_type = CONTENT_TYPE_JSON;
strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->result_code);
strbuf_json_string(sb, message);
if (hr->result_extra_label) {
strbuf_puts(sb, ",\n ");
strbuf_json_string(sb, hr->result_extra_label);
strbuf_puts(sb, ": ");
strbuf_json_atom_as_html(sb, &hr->result_extra_value);
}
unsigned i;
for (i = 0; i < NELS(hr->result_extra); ++i)
if (hr->result_extra[i].label) {
strbuf_puts(sb, ",\n ");
strbuf_json_string(sb, hr->result_extra[i].label);
strbuf_puts(sb, ": ");
strbuf_json_atom(sb, &hr->result_extra[i].value);
}
strbuf_puts(sb, "\n}");
}
else {
hr->header.content_type = CONTENT_TYPE_HTML;
strbuf_sprintf(sb, "<html>\n<h1>%03u %s</h1>", hr->result_code, message);
if (hr->result_extra_label) {
strbuf_puts(sb, "\n<dl><dt>");
strbuf_html_escape(sb, hr->result_extra_label, strlen(hr->result_extra_label));
strbuf_puts(sb, "</dt><dd>");
strbuf_json_atom_as_html(sb, &hr->result_extra_value);
strbuf_puts(sb, "</dd></dl>");
}
unsigned i;
for (i = 0; i < NELS(hr->result_extra); ++i)
if (hr->result_extra[i].label) {
strbuf_puts(sb, "\n<dl><dt>");
strbuf_html_escape(sb, hr->result_extra[i].label, strlen(hr->result_extra[i].label));
strbuf_puts(sb, "</dt><dd>");
strbuf_json_atom_as_html(sb, &hr->result_extra[i].value);
strbuf_puts(sb, "</dd></dl>");
}
strbuf_puts(sb, "\n</html>");
}
return sb;

View File

@ -111,8 +111,10 @@ typedef int (HTTP_CONTENT_GENERATOR)(struct http_request *, unsigned char *, siz
struct http_response {
uint16_t result_code;
const char *result_extra_label;
struct json_atom result_extra_value;
struct {
const char *label;
struct json_atom value;
} result_extra[4];
struct http_response_headers header;
const char *content;
HTTP_CONTENT_GENERATOR *content_generator; // callback to produce more content

View File

@ -276,6 +276,8 @@ void httpd_server_poll(struct sched_ent *alarm)
} else {
++httpd_request_count;
request->uuid = http_request_uuid_counter++;
request->payload_status = INVALID_RHIZOME_PAYLOAD_STATUS;
request->bundle_status = INVALID_RHIZOME_BUNDLE_STATUS;
if (peerip)
request->http.client_sockaddr_in = *peerip;
request->http.handle_headers = httpd_dispatch;

View File

@ -60,6 +60,8 @@ typedef struct httpd_request
/* For requests/responses that pertain to a single manifest.
*/
rhizome_manifest *manifest;
enum rhizome_payload_status payload_status;
enum rhizome_bundle_status bundle_status;
/* For requests/responses that contain one or two SIDs.
*/
@ -123,7 +125,6 @@ typedef struct httpd_request
// For storing the manifest text (malloc/realloc) as we receive it
struct form_buf_malloc manifest;
// For receiving the payload
enum rhizome_payload_status payload_status;
uint64_t payload_size;
struct rhizome_write write;
}

View File

@ -66,43 +66,40 @@ 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)
{
r->http.response.result_extra_label = "meshms_status_code";
r->http.response.result_extra_value.type = JSON_INTEGER;
r->http.response.result_extra_value.u.integer = status;
r->http.response.result_extra[0].label = "meshms_status_code";
r->http.response.result_extra[0].value.type = JSON_INTEGER;
r->http.response.result_extra[0].value.u.integer = status;
uint16_t meshms_result = 0;
const char *meshms_message = NULL;
switch (status) {
case MESHMS_STATUS_OK:
if (!result)
result = 200;
if (!message)
message = "OK";
meshms_result = 200;
meshms_message = "OK";
break;
case MESHMS_STATUS_UPDATED:
if (!result)
result = 201;
if (!message)
message = "Updated";
meshms_result = 201;
meshms_message = "Updated";
break;
case MESHMS_STATUS_SID_LOCKED:
if (!result)
result = 403;
if (!message)
message = "Identity unknown";
meshms_result = 403;
meshms_message = "Identity unknown";
break;
case MESHMS_STATUS_PROTOCOL_FAULT:
if (!result)
result = 403;
if (!message)
message = "MeshMS protocol fault";
meshms_result = 403;
meshms_message = "MeshMS protocol fault";
break;
case MESHMS_STATUS_ERROR:
if (!result)
result = 500;
break;
default:
WHYF("Invalid MeshMS status code %d", status);
result = 500;
meshms_result = 500;
break;
}
if (!meshms_result) {
WHYF("Invalid MeshMS status code %d", status);
result = 500;
} else if (!result) {
result = meshms_result;
if (message == NULL)
message = meshms_message;
}
assert(result != 0);
http_request_simple_response(&r->http, result, message);
return result;

View File

@ -158,31 +158,27 @@ enum rhizome_bundle_status rhizome_bundle_import_files(rhizome_manifest *m, rhiz
)
return RHIZOME_BUNDLE_STATUS_INVALID;
enum rhizome_bundle_status status = rhizome_manifest_check_stored(m, mout);
if (status == RHIZOME_BUNDLE_STATUS_NEW) {
enum rhizome_payload_status pstatus = rhizome_import_payload_from_file(m, filepath);
switch (pstatus) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_NEW:
if (rhizome_store_manifest(m) == -1)
return -1;
break;
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
status = RHIZOME_BUNDLE_STATUS_DONOTWANT;
break;
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
if (status != RHIZOME_BUNDLE_STATUS_NEW)
return status;
enum rhizome_payload_status pstatus = rhizome_import_payload_from_file(m, filepath);
switch (pstatus) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_NEW:
if (rhizome_store_manifest(m) == -1)
return -1;
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
break;
default:
FATALF("pstatus = %d", pstatus);
}
return status;
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
return RHIZOME_BUNDLE_STATUS_NO_ROOM;
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
return -1;
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
return RHIZOME_BUNDLE_STATUS_INCONSISTENT;
}
return status;
FATALF("rhizome_import_payload_from_file() returned status = %d", pstatus);
}
/* Sets the bundle key "BK" field of a manifest. Returns 1 if the field was set, 0 if not.
@ -325,8 +321,12 @@ enum rhizome_bundle_status rhizome_manifest_check_stored(rhizome_manifest *m, rh
enum rhizome_bundle_status rhizome_add_manifest(rhizome_manifest *m, rhizome_manifest **mout)
{
if (config.debug.rhizome)
DEBUGF("rhizome_add_manifest(m=manifest[%d](%p), mout=%p)", m->manifest_record_number, m, mout);
if (config.debug.rhizome) {
if (mout == NULL)
DEBUGF("rhizome_add_manifest(m=manifest[%d](%p), mout=NULL)", m->manifest_record_number, m);
else
DEBUGF("rhizome_add_manifest(m=manifest[%d](%p), *mout=manifest[%d](%p))", m->manifest_record_number, m, *mout ? (*mout)->manifest_record_number : -1, *mout);
}
if (!m->finalised && !rhizome_manifest_validate(m))
return RHIZOME_BUNDLE_STATUS_INVALID;
assert(m->finalised);
@ -351,3 +351,36 @@ int rhizome_saw_voice_traffic()
rhizome_voice_timeout=gettime_ms()+1000;
return 0;
}
const char *rhizome_bundle_status_message(enum rhizome_bundle_status status)
{
switch (status) {
case RHIZOME_BUNDLE_STATUS_NEW: return "Bundle new to store";
case RHIZOME_BUNDLE_STATUS_SAME: return "Bundle already in store";
case RHIZOME_BUNDLE_STATUS_DUPLICATE: return "Duplicate bundle already in store";
case RHIZOME_BUNDLE_STATUS_OLD: return "Newer bundle already in store";
case RHIZOME_BUNDLE_STATUS_INVALID: return "Invalid manifest";
case RHIZOME_BUNDLE_STATUS_FAKE: return "Manifest signature does not verify";
case RHIZOME_BUNDLE_STATUS_INCONSISTENT: return "Manifest inconsistent with supplied payload";
case RHIZOME_BUNDLE_STATUS_NO_ROOM: return "No room in store for bundle";
case RHIZOME_BUNDLE_STATUS_READONLY: return "Bundle is read-only";
case RHIZOME_BUNDLE_STATUS_ERROR: return "Internal error";
}
return NULL;
}
const char *rhizome_payload_status_message(enum rhizome_payload_status status)
{
switch (status) {
case RHIZOME_PAYLOAD_STATUS_NEW: return "Payload new to store";
case RHIZOME_PAYLOAD_STATUS_STORED: return "Payload already in store";
case RHIZOME_PAYLOAD_STATUS_EMPTY: return "Payload empty";
case RHIZOME_PAYLOAD_STATUS_TOO_BIG: return "Payload size exceeds store";
case RHIZOME_PAYLOAD_STATUS_EVICTED: return "Payload evicted";
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: return "Payload size contradicts manifest";
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: return "Payload hash contradicts manifest";
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: return "Incorrect bundle secret";
case RHIZOME_PAYLOAD_STATUS_ERROR: return "Internal error";
}
return NULL;
}

View File

@ -362,21 +362,30 @@ enum rhizome_bundle_status {
RHIZOME_BUNDLE_STATUS_INVALID = 4, // manifest is invalid
RHIZOME_BUNDLE_STATUS_FAKE = 5, // manifest signature not valid
RHIZOME_BUNDLE_STATUS_INCONSISTENT = 6, // manifest filesize/filehash does not match supplied payload
RHIZOME_BUNDLE_STATUS_DONOTWANT = 7, // Wont fit or we already have more important bundles
RHIZOME_BUNDLE_STATUS_NO_ROOM = 7, // doesn't fit; store may contain more important bundles
RHIZOME_BUNDLE_STATUS_READONLY = 8, // cannot modify manifest; secret unknown
};
#define INVALID_RHIZOME_BUNDLE_STATUS ((enum rhizome_bundle_status)-2)
const char *rhizome_bundle_status_message(enum rhizome_bundle_status);
enum rhizome_payload_status {
RHIZOME_PAYLOAD_STATUS_ERROR = -1,
RHIZOME_PAYLOAD_STATUS_EMPTY = 0, // payload is empty (zero length)
RHIZOME_PAYLOAD_STATUS_NEW = 1, // payload is not yet in store
RHIZOME_PAYLOAD_STATUS_NEW = 1, // payload is not yet in store (added)
RHIZOME_PAYLOAD_STATUS_STORED = 2, // payload is already in store
RHIZOME_PAYLOAD_STATUS_WRONG_SIZE = 3, // payload's size does not match manifest
RHIZOME_PAYLOAD_STATUS_WRONG_HASH = 4, // payload's hash does not match manifest
RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL = 5, // cannot encrypt/decrypt (payload key unknown)
RHIZOME_PAYLOAD_STATUS_TOO_BIG = 6, // payload will never fit in our store
RHIZOME_PAYLOAD_STATUS_UNINITERESTING = 7, // other payloads in our store are more interesting
RHIZOME_PAYLOAD_STATUS_EVICTED = 7, // other payloads in our store are more important
};
#define INVALID_RHIZOME_PAYLOAD_STATUS ((enum rhizome_bundle_status)-2)
const char *rhizome_payload_status_message(enum rhizome_payload_status);
int rhizome_write_manifest_file(rhizome_manifest *m, const char *filename, char append);
int rhizome_manifest_selfsign(rhizome_manifest *m);
int rhizome_read_manifest_from_file(rhizome_manifest *m, const char *filename);

View File

@ -1086,17 +1086,23 @@ int rhizome_manifest_dump(rhizome_manifest *m, const char *msg)
enum rhizome_bundle_status rhizome_manifest_finalise(rhizome_manifest *m, rhizome_manifest **mout, int deduplicate)
{
IN();
assert(*mout == NULL);
if (!m->finalised && !rhizome_manifest_validate(m))
RETURN(RHIZOME_BUNDLE_STATUS_INVALID);
// if a manifest was supplied with an ID, don't bother to check for a duplicate.
// we only want to filter out added files with no existing manifest.
if (deduplicate && m->haveSecret != EXISTING_BUNDLE_ID) {
enum rhizome_bundle_status status = rhizome_find_duplicate(m, mout);
switch (status) {
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
assert(*mout != NULL);
assert(*mout != m);
RETURN(status);
case RHIZOME_BUNDLE_STATUS_ERROR:
if (*mout != NULL && *mout != m) {
rhizome_manifest_free(*mout);
*mout = NULL;
}
RETURN(status);
case RHIZOME_BUNDLE_STATUS_NEW:
break;
@ -1104,6 +1110,7 @@ enum rhizome_bundle_status rhizome_manifest_finalise(rhizome_manifest *m, rhizom
FATALF("rhizome_find_duplicate() returned %d", status);
}
}
assert(*mout == NULL);
*mout = m;
/* Convert to final form for signing and writing to disk */

View File

@ -600,7 +600,6 @@ int rhizome_crypt_xor_block(unsigned char *buffer, size_t buffer_size, uint64_t
*/
int rhizome_derive_payload_key(rhizome_manifest *m)
{
// don't do anything if the manifest isn't flagged as being encrypted
assert(m->payloadEncryption == PAYLOAD_ENCRYPTED);
unsigned char hash[crypto_hash_sha512_BYTES];
if (m->has_sender && m->has_recipient) {

View File

@ -1272,10 +1272,7 @@ int rhizome_cleanup(struct rhizome_cleanup_report *report)
int rhizome_store_manifest(rhizome_manifest *m)
{
assert(m->finalised);
// If we don't have the secret for this manifest, only store it if its self-signature is valid
if (!m->haveSecret && !m->selfSigned)
return WHY("Manifest is not signed, and I don't have the key. Manifest might be forged or corrupt.");
assert(m->haveSecret || m->selfSigned); // should not store an invalid or fake manifest
/* Bind BAR to data field */
rhizome_bar_t bar;

View File

@ -119,9 +119,10 @@ static int rhizome_direct_import_end(struct http_request *hr)
case RHIZOME_BUNDLE_STATUS_FAKE:
http_request_simple_response(&r->http, 403, "Manifest not signed");
return 0;
case RHIZOME_BUNDLE_STATUS_DONOTWANT:
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
http_request_simple_response(&r->http, 403, "Not enough space");
return 0;
case RHIZOME_BUNDLE_STATUS_READONLY:
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
case RHIZOME_BUNDLE_STATUS_ERROR:
break;
@ -729,17 +730,20 @@ void rhizome_direct_http_dispatch(rhizome_direct_sync_request *r)
switch (pstatus) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_STORED:
break;
goto pstatus_ok;
case RHIZOME_PAYLOAD_STATUS_NEW:
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
goto closeit;
default:
FATALF("pstatus = %d", pstatus);
// No "default" label, so the compiler will warn us if a case is not handled.
}
FATALF("pstatus = %d", pstatus);
pstatus_ok:
;
uint64_t read_ofs;
for(read_ofs=0;read_ofs<m->filesize;){
unsigned char buffer[4096];

View File

@ -553,10 +553,10 @@ schedule_fetch(struct rhizome_fetch_slot *slot)
case RHIZOME_PAYLOAD_STATUS_STORED:
RETURN(IMPORTED);
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
RETURN(DONOTWANT);
case RHIZOME_PAYLOAD_STATUS_NEW:
break;
goto status_ok;
case RHIZOME_PAYLOAD_STATUS_ERROR:
RETURN(WHY("error writing new payload"));
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
@ -565,9 +565,11 @@ schedule_fetch(struct rhizome_fetch_slot *slot)
RETURN(WHY("payload hash does not match"));
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
RETURN(WHY("payload cannot be encrypted"));
default:
FATALF("status = %d", status);
// No "default" label, so the compiler will warn if a case is not handled.
}
FATALF("status = %d", status);
status_ok:
;
} else {
strbuf r = strbuf_local(slot->request, sizeof slot->request);
strbuf_sprintf(r, "GET /rhizome/manifestbyprefix/%s HTTP/1.0\r\n\r\n", alloca_tohex(slot->bid.binary, slot->prefix_length));

View File

@ -63,11 +63,93 @@ static int strn_to_list_token(const char *str, uint64_t *rowidp, const char **af
return 1;
}
static int http_request_rhizome_response(struct httpd_request *r, uint16_t result, const char *message, const char *payload_status_message)
{
uint16_t rhizome_result = 0;
switch (r->bundle_status) {
case RHIZOME_BUNDLE_STATUS_NEW:
rhizome_result = 201;
break;
case RHIZOME_BUNDLE_STATUS_SAME:
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
case RHIZOME_BUNDLE_STATUS_OLD:
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
rhizome_result = 200;
break;
case RHIZOME_BUNDLE_STATUS_INVALID:
case RHIZOME_BUNDLE_STATUS_FAKE:
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
case RHIZOME_BUNDLE_STATUS_READONLY:
rhizome_result = 403;
break;
case RHIZOME_BUNDLE_STATUS_ERROR:
rhizome_result = 500;
break;
}
if (rhizome_result) {
r->http.response.result_extra[0].label = "rhizome_bundle_status_code";
r->http.response.result_extra[0].value.type = JSON_INTEGER;
r->http.response.result_extra[0].value.u.integer = r->bundle_status;
const char *status_message = rhizome_bundle_status_message(r->bundle_status);
if (status_message) {
r->http.response.result_extra[1].label = "rhizome_bundle_status_message";
r->http.response.result_extra[1].value.type = JSON_STRING_NULTERM;
r->http.response.result_extra[1].value.u.string.content = status_message;
}
if (rhizome_result > result) {
result = rhizome_result;
message = NULL;
}
}
rhizome_result = 0;
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_NEW:
rhizome_result = 201;
break;
case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
rhizome_result = 200;
break;
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
rhizome_result = 403;
break;
case RHIZOME_PAYLOAD_STATUS_ERROR:
rhizome_result = 500;
break;
}
if (rhizome_result) {
r->http.response.result_extra[2].label = "rhizome_payload_status_code";
r->http.response.result_extra[2].value.type = JSON_INTEGER;
r->http.response.result_extra[2].value.u.integer = r->payload_status;
const char *status_message = payload_status_message ? payload_status_message : rhizome_payload_status_message(r->payload_status);
if (status_message) {
r->http.response.result_extra[3].label = "rhizome_payload_status_message";
r->http.response.result_extra[3].value.type = JSON_STRING_NULTERM;
r->http.response.result_extra[3].value.u.string.content = status_message;
}
if (rhizome_result > result) {
result = rhizome_result;
message = NULL;
}
}
if (result == 0) {
result = 500;
message = NULL;
}
http_request_simple_response(&r->http, result, message ? message : result == 403 ? "Rhizome operation failed" : NULL);
return result;
}
static HTTP_CONTENT_GENERATOR restful_rhizome_bundlelist_json_content;
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 403;
if (*remainder)
@ -242,6 +324,7 @@ static int insert_mime_part_body(struct http_request *, char *, size_t);
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;
if (*remainder)
return 404;
if (!is_rhizome_http_enabled())
@ -300,8 +383,7 @@ static int insert_make_manifest(httpd_request *r)
return 0;
// fall through
case 1:
http_request_simple_response(&r->http, 403, "Malformed manifest");
return 403;
return http_request_rhizome_response(r, 403, "Malformed manifest", NULL);
default:
WHYF("rhizome_manifest_parse() returned %d", n);
break;
@ -355,17 +437,17 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa
rhizome_manifest_set_name_from_path(r->manifest, h->content_disposition.filename);
// Start writing the payload content into the Rhizome store. Note: r->manifest->filesize can be
// RHIZOME_SIZE_UNSET at this point, if the manifest did not contain a 'filesize' field.
r->u.insert.payload_status = rhizome_write_open_manifest(&r->u.insert.write, r->manifest);
r->payload_status = rhizome_write_open_manifest(&r->u.insert.write, r->manifest);
r->u.insert.payload_size = 0;
switch (r->u.insert.payload_status) {
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_ERROR:
WHYF("rhizome_write_open_manifest() returned %d", r->u.insert.payload_status);
WHYF("rhizome_write_open_manifest() returned %d", r->payload_status);
return 500;
case RHIZOME_PAYLOAD_STATUS_STORED:
// TODO: initialise payload hash so it can be compared with stored payload
break;
default:
break;
break; // r->payload_status gets dealt with later
}
}
else
@ -395,7 +477,7 @@ static int insert_mime_part_body(struct http_request *hr, char *buf, size_t len)
}
else if (r->u.insert.current_part == PART_PAYLOAD) {
r->u.insert.payload_size += len;
switch (r->u.insert.payload_status) {
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_NEW:
if (rhizome_write_buffer(&r->u.insert.write, (unsigned char *)buf, len) == -1)
return 500;
@ -445,19 +527,25 @@ static int insert_mime_part_end(struct http_request *hr)
WHY("rhizome_fill_manifest() failed");
return 500;
}
if (r->manifest->is_journal) {
http_request_simple_response(&r->http, 403, "Insert not supported for journals");
return 403;
}
if (r->manifest->is_journal)
return http_request_rhizome_response(r, 403, "Insert not supported for journals", NULL);
assert(r->manifest != NULL);
}
else if (r->u.insert.current_part == PART_PAYLOAD) {
r->u.insert.received_payload = 1;
if (r->u.insert.payload_status == RHIZOME_PAYLOAD_STATUS_NEW)
r->u.insert.payload_status = rhizome_finish_write(&r->u.insert.write);
if (r->u.insert.payload_status == RHIZOME_PAYLOAD_STATUS_ERROR) {
WHYF("rhizome_finish_write() returned status = %d", r->u.insert.payload_status);
return 500;
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_NEW:
r->payload_status = rhizome_finish_write(&r->u.insert.write);
if (r->payload_status == RHIZOME_PAYLOAD_STATUS_ERROR) {
WHYF("rhizome_finish_write() returned status = %d", r->payload_status);
return 500;
}
break;
case RHIZOME_PAYLOAD_STATUS_STORED:
// TODO: finish calculating payload hash and compare it with stored payload
break;
default:
break;
}
} else
FATALF("current_part = %s", alloca_str_toprint(r->u.insert.current_part));
@ -475,40 +563,40 @@ static int restful_rhizome_insert_end(struct http_request *hr)
// Fill in the missing manifest fields and ensure payload and manifest are consistent.
assert(r->manifest != NULL);
assert(r->u.insert.write.file_length != RHIZOME_SIZE_UNSET);
switch (r->u.insert.payload_status) {
case RHIZOME_PAYLOAD_STATUS_ERROR:
return 500;
int status_valid = 0;
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_NEW:
status_valid = 1;
if (r->manifest->filesize == RHIZOME_SIZE_UNSET)
rhizome_manifest_set_filesize(r->manifest, r->u.insert.write.file_length);
// fall through
case RHIZOME_PAYLOAD_STATUS_STORED:
status_valid = 1;
// TODO: check that stored hash matches received payload's hash
// fall through
case RHIZOME_PAYLOAD_STATUS_EMPTY:
status_valid = 1;
assert(r->manifest->filesize != RHIZOME_SIZE_UNSET);
if (r->u.insert.payload_size == r->manifest->filesize)
break;
// fall through
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
r->payload_status = RHIZOME_PAYLOAD_STATUS_WRONG_SIZE;
status_valid = 1;
{
strbuf msg = strbuf_alloca(200);
strbuf_sprintf(msg, "Payload size (%"PRIu64") contradicts manifest (filesize=%"PRIu64")", r->u.insert.payload_size, r->manifest->filesize);
http_request_simple_response(&r->http, 403, strbuf_str(msg));
return 403;
return http_request_rhizome_response(r, 403, NULL, strbuf_str(msg));
}
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
http_request_simple_response(&r->http, 403, "Not enough space");
return 403;
case RHIZOME_PAYLOAD_STATUS_EVICTED:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
http_request_simple_response(&r->http, 403, "Payload hash contradicts manifest");
return 403;
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
http_request_simple_response(&r->http, 403, "Missing bundle secret");
return 403;
default:
FATALF("payload_status = %d", r->u.insert.payload_status);
case RHIZOME_PAYLOAD_STATUS_ERROR:
return http_request_rhizome_response(r, 403, NULL, NULL);
}
if (!status_valid) {
WHYF("r->payload_status = %d", r->payload_status);
return http_request_rhizome_response(r, 500, NULL, NULL);
}
// Finalise the manifest and add it to the store.
if (r->manifest->filesize) {
@ -526,35 +614,36 @@ static int restful_rhizome_insert_end(struct http_request *hr)
return 403;
}
rhizome_manifest *mout = NULL;
int result;
switch (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);
int result = 500;
switch (r->bundle_status) {
case RHIZOME_BUNDLE_STATUS_NEW:
result = 201;
if (mout && mout != r->manifest)
rhizome_manifest_free(mout);
mout = NULL;
result = 201;
break;
case RHIZOME_BUNDLE_STATUS_SAME:
case RHIZOME_BUNDLE_STATUS_OLD:
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
if (mout && mout != r->manifest) {
rhizome_manifest_free(r->manifest);
r->manifest = mout;
}
result = 200;
break;
case RHIZOME_BUNDLE_STATUS_INVALID:
result = 403;
break;
case RHIZOME_BUNDLE_STATUS_FAKE:
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
case RHIZOME_BUNDLE_STATUS_READONLY:
case RHIZOME_BUNDLE_STATUS_ERROR:
default:
result = 500;
break;
if (mout && mout != r->manifest)
rhizome_manifest_free(mout);
return http_request_rhizome_response(r, 0, NULL, NULL);
}
if (mout && mout != r->manifest) {
rhizome_manifest_free(r->manifest);
r->manifest = mout;
}
if (result >= 400)
return result;
if (result == 500)
FATALF("rhizome_manifest_finalise() returned status = %d", r->bundle_status);
rhizome_authenticate_author(r->manifest);
r->http.render_extra_headers = render_manifest_headers;
http_request_response_static(&r->http, result, "rhizome-manifest/text",
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
);
@ -568,6 +657,7 @@ static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
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;
if (!is_rhizome_http_enabled())
return 403;
HTTP_HANDLER *handler = NULL;
@ -598,15 +688,16 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
if (ret == -1) {
rhizome_manifest_free(r->manifest);
r->manifest = NULL;
r->bundle_status = RHIZOME_BUNDLE_STATUS_ERROR;
return 500;
}
if (ret == 0) {
rhizome_authenticate_author(r->manifest);
r->http.render_extra_headers = render_manifest_headers;
r->bundle_status = RHIZOME_BUNDLE_STATUS_SAME;
} else {
r->bundle_status = RHIZOME_BUNDLE_STATUS_NEW;
rhizome_manifest_free(r->manifest);
r->manifest = NULL;
assert(r->http.render_extra_headers == NULL);
}
ret = handler(r, remainder);
return ret;
@ -614,8 +705,10 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
static int restful_rhizome_bid_rhm(httpd_request *r, const char *remainder)
{
if (*remainder || r->manifest == NULL)
if (*remainder)
return 404;
if (r->manifest == NULL)
return http_request_rhizome_response(r, 404, NULL, NULL);
http_request_response_static(&r->http, 200, "rhizome-manifest/text",
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
);
@ -624,8 +717,10 @@ static int restful_rhizome_bid_rhm(httpd_request *r, const char *remainder)
static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder)
{
if (*remainder || r->manifest == NULL)
if (*remainder)
return 404;
if (r->manifest == NULL)
return http_request_rhizome_response(r, 404, NULL, NULL);
if (r->manifest->filesize == 0) {
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);
return 1;
@ -639,8 +734,10 @@ static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder)
static int restful_rhizome_bid_decrypted_bin(httpd_request *r, const char *remainder)
{
if (*remainder || r->manifest == NULL)
if (*remainder)
return 404;
if (r->manifest == NULL)
return http_request_rhizome_response(r, 404, NULL, NULL);
if (r->manifest->filesize == 0) {
// TODO use Content Type from manifest (once it is implemented)
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);
@ -685,22 +782,22 @@ int rhizome_response_content_init_filehash(httpd_request *r, const rhizome_fileh
r->u.read_state.blob_fd = -1;
assert(r->finalise_union == NULL);
r->finalise_union = finalise_union_read_state;
enum rhizome_payload_status status = rhizome_open_read(&r->u.read_state, hash);
switch (status) {
r->payload_status = rhizome_open_read(&r->u.read_state, hash);
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_STORED:
break;
return rhizome_response_content_init_read_state(r);
case RHIZOME_PAYLOAD_STATUS_NEW:
return 404;
return 403;
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
return -1;
default:
FATALF("status = %d", status);
}
return rhizome_response_content_init_read_state(r);
FATALF("rhizome_open_read() returned status = %d", r->payload_status);
}
int rhizome_response_content_init_payload(httpd_request *r, rhizome_manifest *m)
@ -709,22 +806,22 @@ int rhizome_response_content_init_payload(httpd_request *r, rhizome_manifest *m)
r->u.read_state.blob_fd = -1;
assert(r->finalise_union == NULL);
r->finalise_union = finalise_union_read_state;
enum rhizome_payload_status status = rhizome_open_decrypt_read(m, &r->u.read_state);
switch (status) {
r->payload_status = rhizome_open_decrypt_read(m, &r->u.read_state);
switch (r->payload_status) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_STORED:
break;
return rhizome_response_content_init_read_state(r);
case RHIZOME_PAYLOAD_STATUS_NEW:
return 404;
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
return 403;
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
return -1;
default:
FATALF("status = %d", status);
}
return rhizome_response_content_init_read_state(r);
FATALF("rhizome_open_decrypt_read() returned status = %d", r->payload_status);
}
int rhizome_payload_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
@ -758,49 +855,58 @@ int rhizome_payload_content(struct http_request *hr, unsigned char *buf, size_t
static void render_manifest_headers(struct http_request *hr, strbuf sb)
{
httpd_request *r = (httpd_request *) hr;
const char *status_message;
if ((status_message = rhizome_bundle_status_message(r->bundle_status))) {
strbuf_sprintf(sb, "Serval-Rhizome-Result-Bundle-Status-Code: %d\r\n", r->bundle_status);
strbuf_sprintf(sb, "Serval-Rhizome-Result-Bundle-Status-Message: %s\r\n", status_message);
}
if ((status_message = rhizome_payload_status_message(r->payload_status))) {
strbuf_sprintf(sb, "Serval-Rhizome-Result-Payload-Status-Code: %d\r\n", r->payload_status);
strbuf_sprintf(sb, "Serval-Rhizome-Result-Payload-Status-Message: %s\r\n", status_message);
}
rhizome_manifest *m = r->manifest;
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->cryptoSignPublic));
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", m->version);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize);
if (m->filesize != 0)
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) {
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->cryptoSignPublic));
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", m->version);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize);
if (m->filesize != 0)
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->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Secret: %s\r\n", secret);
}
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Rowid: %"PRIu64"\r\n", m->rowid);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Inserttime: %"PRIu64"\r\n", m->inserttime);
}
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));
assert(m->haveSecret);
{
char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1];
rhizome_bytes_to_hex_upper(m->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Secret: %s\r\n", secret);
}
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Rowid: %"PRIu64"\r\n", m->rowid);
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Inserttime: %"PRIu64"\r\n", m->inserttime);
}

View File

@ -296,7 +296,7 @@ static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizo
DEBUGF("Not enough space for %"PRIu64". Used; %"PRIu64" = %"PRIu64" + %"PRIu64" * (%"PRIu64" - %"PRIu64"), Limit; %"PRIu64,
bytes, db_used, external_bytes, db_page_size, db_page_count, db_free_page_count, limit);
return RHIZOME_PAYLOAD_STATUS_UNINITERESTING;
return RHIZOME_PAYLOAD_STATUS_EVICTED;
}
int rhizome_store_cleanup(struct rhizome_cleanup_report *report)
@ -963,21 +963,23 @@ enum rhizome_payload_status rhizome_store_payload_file(rhizome_manifest *m, cons
struct rhizome_write write;
bzero(&write, sizeof(write));
enum rhizome_payload_status status = rhizome_write_open_manifest(&write, m);
int status_ok = 0;
switch (status) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
case RHIZOME_PAYLOAD_STATUS_NEW:
status_ok = 1;
break;
case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
case RHIZOME_PAYLOAD_STATUS_EVICTED:
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
return status;
default:
FATALF("status = %d", status);
}
if (!status_ok)
FATALF("rhizome_write_open_manifest() returned status = %d", status);
if (rhizome_write_file(&write, filepath) == -1) {
rhizome_fail_write(&write);
return RHIZOME_PAYLOAD_STATUS_ERROR;
@ -987,26 +989,24 @@ enum rhizome_payload_status rhizome_store_payload_file(rhizome_manifest *m, cons
case RHIZOME_PAYLOAD_STATUS_EMPTY:
assert(write.file_length == 0);
assert(m->filesize == 0);
break;
return status;
case RHIZOME_PAYLOAD_STATUS_NEW:
assert(m->filesize == write.file_length);
if (m->has_filehash)
assert(cmp_rhizome_filehash_t(&m->filehash, &write.id) == 0);
else
rhizome_manifest_set_filehash(m, &write.id);
break;
return status;
case RHIZOME_PAYLOAD_STATUS_ERROR:
case RHIZOME_PAYLOAD_STATUS_STORED:
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
break;
default:
FATALF("status = %d", status);
case RHIZOME_PAYLOAD_STATUS_EVICTED:
return status;
}
return status;
FATALF("rhizome_finish_write() returned status = %d", status);
}
/* Return RHIZOME_PAYLOAD_STATUS_STORED if file blob found, RHIZOME_PAYLOAD_STATUS_NEW if not found.

View File

@ -28,6 +28,8 @@ rexp_crypt='[01]'
rexp_date='[0-9]\{1,\}'
rexp_rowid='[0-9]\{1,\}'
BID_NONEXISTENT=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
assert_manifest_complete() {
local manifest="$1"
tfw_cat -v "$manifest"
@ -552,3 +554,11 @@ rhizome_add_bundles() {
[ "${ROWID[$n]}" -gt "${ROWID_MAX:-0}" ] && ROWID_MAX=${ROWID[$n]}
done
}
rhizome_delete_payload_blobs() {
local filehash
for filehash; do
assert --message="Rhizome external blob file exists, filehash=$filehash" [ -e "$SERVALINSTANCE_PATH/blob/$filehash" ]
rm -f "$SERVALINSTANCE_PATH/blob/$filehash"
done
}

View File

@ -225,19 +225,19 @@ test_RhizomeNewSince() {
}
assert_http_response_headers() {
local n=$1
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Id: ${BID[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Version: ${VERSION[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Filesize: ${SIZE[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Filehash: ${HASH[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-BK: ${BK[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Date: ${DATE[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Name: \"${NAME[$n]}\"$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Service: file$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Author: ${AUTHOR[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Secret: ${SECRET[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Inserttime: ${INSERTTIME[$n]}$CR\$"
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Rowid: ${ROWID[$n]}$CR\$"
local file="$1"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Id: ${BID[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Version: ${VERSION[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Filesize: ${SIZE[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Filehash: ${HASH[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-BK: ${BK[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Date: ${DATE[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Name: \"${NAME[$n]}\"$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Service: file$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Author: ${AUTHOR[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Secret: ${SECRET[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Inserttime: ${INSERTTIME[$n]}$CR\$"
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Rowid: ${ROWID[$n]}$CR\$"
}
doc_RhizomeManifest="HTTP RESTful fetch Rhizome manifest"
@ -258,10 +258,32 @@ test_RhizomeManifest() {
done
for n in 0 1 2; do
assert diff file$n.manifest bundle$n.rhm
assert_http_response_headers $n
assert_http_response_headers http.headers$n
done
}
doc_RhizomeManifestNonexist="HTTP RESTful fetch non-existent Rhizome manifest"
setup_RhizomeManifestNonexist() {
setup
}
test_RhizomeManifestNonexist() {
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.content \
--dump-header http.headers \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT.rhm"
tfw_cat http.headers http.content
assertStdoutIs 404
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code:"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
assertJq http.content 'contains({"http_status_code": 404})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
}
doc_RhizomePayloadRaw="HTTP RESTful fetch Rhizome raw payload"
setup_RhizomePayloadRaw() {
setup
@ -280,10 +302,65 @@ test_RhizomePayloadRaw() {
done
for n in 0 1 2 3; do
assert cmp raw$n raw.bin$n
assert_http_response_headers $n
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 2$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload already in store.*$CR\$"
assert_http_response_headers http.headers$n
done
}
doc_RhizomePayloadRawNonexistManifest="HTTP RESTful fetch Rhizome raw payload for non-existent manifest"
setup_RhizomePayloadRawNonexistManifest() {
setup
}
test_RhizomePayloadRawNonexistManifest() {
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.content \
--dump-header http.headers \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/raw.bin"
tfw_cat http.headers http.content
assertStdoutIs 404
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code:"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
assertJq http.content 'contains({"http_status_code": 404})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
}
doc_RhizomePayloadRawNonexistPayload="HTTP RESTful fetch non-existent Rhizome raw payload"
setup_RhizomePayloadRawNonexistPayload() {
set_extra_config() {
executeOk_servald config set rhizome.max_blob_size 0
}
setup
rhizome_add_bundles $SIDA 0 0
rhizome_delete_payload_blobs "${HASH[0]}"
}
test_RhizomePayloadRawNonexistPayload() {
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.content \
--dump-header http.headers \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[0]}/raw.bin"
tfw_cat http.headers http.content
assertStdoutIs 403
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
assertJq http.content 'contains({"http_status_code": 403})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 1})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle already in store"
assertJq http.content 'contains({"rhizome_payload_status_code": 1})'
assertJqGrep --ignore-case http.content '.rhizome_payload_status_message' "payload new to store"
}
doc_RhizomePayloadDecrypted="HTTP RESTful fetch Rhizome decrypted payload"
setup_RhizomePayloadDecrypted() {
setup
@ -302,10 +379,91 @@ test_RhizomePayloadDecrypted() {
done
for n in 0 1 2 3; do
assert cmp file$n decrypted.bin$n
assert_http_response_headers $n
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 2$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload already in store.*$CR\$"
assert_http_response_headers http.headers$n
done
}
doc_RhizomePayloadDecryptedForeign="HTTP RESTful cannot fetch foreign Rhizome decrypted payload"
setup_RhizomePayloadDecryptedForeign() {
setup
rhizome_add_bundles --encrypted $SIDA 0 0
set_instance +B
create_single_identity
rhizome_add_bundles --encrypted $SIDB 1 1
executeOk_servald rhizome export manifest "${BID[1]}" file1.manifest
set_instance +A
executeOk_servald rhizome import bundle raw1 file1.manifest
}
test_RhizomePayloadDecryptedForeign() {
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output decrypted.bin$n \
--dump-header http.headers$n \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[1]}/decrypted.bin"
tfw_cat http.headers$n decrypted.bin$n
assertStdoutIs 403
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 5$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*incorrect bundle secret*$CR\$"
}
doc_RhizomePayloadDecryptedNonexistManifest="HTTP RESTful fetch Rhizome decrypted payload for non-existent manifest"
setup_RhizomePayloadDecryptedNonexistManifest() {
setup
}
test_RhizomePayloadDecryptedNonexistManifest() {
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.content \
--dump-header http.headers \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/decrypted.bin"
tfw_cat http.headers http.content
assertStdoutIs 404
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code:"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
assertJq http.content 'contains({"http_status_code": 404})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
}
doc_RhizomePayloadDecryptedNonexistPayload="HTTP RESTful fetch non-existent Rhizome decrypted payload"
setup_RhizomePayloadDecryptedNonexistPayload() {
set_extra_config() {
executeOk_servald config set rhizome.max_blob_size 0
}
setup
rhizome_add_bundles $SIDA 0 0
rhizome_delete_payload_blobs "${HASH[0]}"
}
test_RhizomePayloadDecryptedNonexistPayload() {
executeOk curl \
--silent --show-error --write-out '%{http_code}' \
--output http.content \
--dump-header http.headers \
--basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[0]}/decrypted.bin"
tfw_cat http.headers http.content
assertStdoutIs 403
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
assertJq http.content 'contains({"http_status_code": 403})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 1})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle already in store"
assertJq http.content 'contains({"rhizome_payload_status_code": 1})'
assertJqGrep --ignore-case http.content '.rhizome_payload_status_message' "payload new to store"
}
extract_http_header() {
local __var="$1"
local __headerfile="$2"
@ -419,15 +577,19 @@ test_RhizomeInsert() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output nfile$n.manifest \
--dump-header http.header$n \
--dump-header http.headers$n \
--basic --user harry:potter \
--form "manifest=@nmanifest$n;type=rhizome-manifest/text" \
--form "payload=@nfile$n;filename=\"nfile$n\"" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header$n nfile$n.manifest
tfw_cat http.headers$n nfile$n.manifest
assertExitStatus == 0
if [ -n "${author[$n]}" ]; then
assertStdoutIs 201
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$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\$"
else
assertStdoutIs 403
assertJq nfile$n.manifest 'contains({"http_status_code": 403})'
@ -460,6 +622,10 @@ test_RhizomeInsertAnon() {
tfw_cat http.header ifile2.manifest
assertExitStatus == 0
assertStdoutIs 201
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
executeOk_servald rhizome list
assert_rhizome_list --fromhere=0 --manifest=ifile2.manifest file2
}
@ -482,6 +648,10 @@ test_RhizomeInsertEmpty() {
tfw_cat http.header empty.manifest
assertExitStatus == 0
assertStdoutIs 201
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
extract_manifest_id BID empty.manifest
executeOk_servald rhizome list
assert_rhizome_list empty
@ -504,9 +674,13 @@ test_RhizomeInsertLarge() {
--form "manifest=;type=rhizome-manifest/text" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header file1.manifest
tfw_cat http.header -v file1.manifest
assertExitStatus == 0
assertStdoutIs 201
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
extract_manifest_id BID file1.manifest
executeOk_servald rhizome list
assert_rhizome_list file1
@ -730,7 +904,8 @@ test_RhizomeInsertIncorrectFilesize() {
assertExitStatus == 0
assertStdoutIs 403
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'payload size.*contradicts manifest'
assertJq http.body 'contains({"rhizome_payload_status_code": 3})'
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload size.*contradicts manifest'
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
@ -742,7 +917,9 @@ test_RhizomeInsertIncorrectFilesize() {
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'payload size.*contradicts manifest'
assertJq http.body 'contains({"http_status_code": 403})'
assertJq http.body 'contains({"rhizome_payload_status_code": 3})'
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload size.*contradicts manifest'
executeOk_servald rhizome list
assert_rhizome_list
}
@ -766,7 +943,8 @@ test_RhizomeInsertIncorrectFilehash() {
assertExitStatus == 0
assertStdoutIs 403
assertJq http.body 'contains({"http_status_code": 403})'
assertJqGrep --ignore-case http.body '.http_status_message' 'payload hash.*contradicts manifest'
assertJq http.body 'contains({"rhizome_payload_status_code": 4})'
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload hash.*contradicts manifest'
executeOk_servald rhizome list
assert_rhizome_list
}