Implement HTTP POST /restful/rhizome/insert

Change HTTP request buffer pointers from (const char*) to (char*)
because some Rhizome operations can modify received data in-place, eg,
when decrypting it.
This commit is contained in:
Andrew Bettison 2013-12-30 18:09:56 +10:30
parent 42e6168998
commit ee9c96bb8c
9 changed files with 1005 additions and 111 deletions

View File

@ -538,12 +538,12 @@ static size_t _parse_token_or_quoted_string(struct http_request *r, char *dst, s
static inline int _parse_http_size_t(struct http_request *r, http_size_t *szp)
{
return !_run_out(r) && isdigit(*r->cursor) && str_to_uint64(r->cursor, 10, szp, &r->cursor);
return !_run_out(r) && isdigit(*r->cursor) && str_to_uint64(r->cursor, 10, szp, (const char **)&r->cursor);
}
static inline int _parse_uint32(struct http_request *r, uint32_t *uint32p)
{
return !_run_out(r) && isdigit(*r->cursor) && str_to_uint32(r->cursor, 10, uint32p, &r->cursor);
return !_run_out(r) && isdigit(*r->cursor) && str_to_uint32(r->cursor, 10, uint32p, (const char **)&r->cursor);
}
static unsigned _parse_ranges(struct http_request *r, struct http_range *range, unsigned nrange)
@ -598,7 +598,7 @@ static int _parse_content_type(struct http_request *r, struct mime_content_type
return 0;
}
while (_skip_optional_space(r) && _skip_literal(r, ";") && _skip_optional_space(r)) {
const char *start = r->cursor;
char *start = r->cursor;
if (_skip_literal(r, "charset=")) {
size_t n = _parse_token_or_quoted_string(r, ct->charset, sizeof ct->charset);
if (n == 0)
@ -635,7 +635,7 @@ static int _parse_content_type(struct http_request *r, struct mime_content_type
static size_t _parse_base64(struct http_request *r, char *bin, size_t binsize)
{
return base64_decode((unsigned char *)bin, binsize, r->cursor, r->end - r->cursor, &r->cursor, B64_CONSUME_ALL, is_http_space);
return base64_decode((unsigned char *)bin, binsize, r->cursor, r->end - r->cursor, (const char **)&r->cursor, B64_CONSUME_ALL, is_http_space);
}
static int _parse_authorization_credentials_basic(struct http_request *r, struct http_client_credentials_basic *cred, char *buf, size_t bufsz)
@ -654,7 +654,7 @@ static int _parse_authorization_credentials_basic(struct http_request *r, struct
static int _parse_authorization(struct http_request *r, struct http_client_authorization *auth, size_t header_bytes)
{
const char *start = r->cursor;
char *start = r->cursor;
if (_skip_literal(r, "Basic") && _skip_space(r)) {
size_t bufsz = 5 + header_bytes * 3 / 4; // enough for base64 decoding
char buf[bufsz];
@ -870,7 +870,7 @@ static int http_request_parse_header(struct http_request *r)
return r->handle_headers(r);
return 0;
}
const char *const nextline = r->cursor;
char *const nextline = r->cursor;
_rewind(r);
const char *const sol = r->cursor;
if (_skip_literal_nocase(r, "Content-Length:")) {
@ -1074,7 +1074,7 @@ static int _parse_content_disposition(struct http_request *r, struct mime_conten
return 0;
}
while (_skip_optional_space(r) && _skip_literal(r, ";") && _skip_optional_space(r)) {
const char *start = r->cursor;
char *start = r->cursor;
if (_skip_literal(r, "filename=")) {
size_t n = _parse_token_or_quoted_string(r, cd->filename, sizeof cd->filename);
if (n == 0)
@ -1217,7 +1217,7 @@ static int http_request_parse_body_form_data(struct http_request *r)
case PREAMBLE: {
if (config.debug.httpd)
DEBUGF("PREAMBLE");
const char *start = r->parsed;
char *start = r->parsed;
for (; at_start || _skip_to_crlf(r); at_start = 0) {
const char *end_preamble = r->cursor;
int b;
@ -1254,7 +1254,7 @@ static int http_request_parse_body_form_data(struct http_request *r)
_commit(r);
return 0;
}
const char *sol = r->cursor;
char *const sol = r->cursor;
// A blank line finishes the headers. The CRLF does not form part of the body.
if (_skip_crlf(r)) {
_commit(r);
@ -1367,16 +1367,18 @@ static int http_request_parse_body_form_data(struct http_request *r)
case BODY:
if (config.debug.httpd)
DEBUGF("BODY");
const char *start = r->parsed;
char *start = r->parsed;
while (_skip_to_crlf(r)) {
int b;
const char *end_body = r->cursor;
char *end_body = r->cursor;
_skip_crlf(r);
if ((b = _skip_mime_boundary(r))) {
_rewind_crlf(r);
_commit(r);
assert(end_body >= start);
r->part_body_length += end_body - start;
// Note: the handler function may modify the data in-place (eg, Rhizome does encryption
// that way).
_INVOKE_HANDLER_BUF_LEN(handle_mime_body, start, end_body); // excluding CRLF at end
return http_request_form_data_start_part(r, b);
}
@ -1390,6 +1392,7 @@ static int http_request_parse_body_form_data(struct http_request *r)
_commit(r);
assert(r->parsed >= start);
r->part_body_length += r->parsed - start;
// Note: the handler function may modify the data in-place
_INVOKE_HANDLER_BUF_LEN(handle_mime_body, start, r->parsed);
return 100; // need more data
case EPILOGUE:

View File

@ -133,12 +133,12 @@ struct http_mime_handler {
// filure status code in the range 400-599 or by initiating an HTTP response
// directly (changing the phase from RECEIVE to TRANSMIT). They can return
// zero to indicate that parsing should proceed.
int (*handle_mime_preamble)(struct http_request *, const char *, size_t);
int (*handle_mime_preamble)(struct http_request *, char *, size_t);
int (*handle_mime_part_start)(struct http_request *);
int (*handle_mime_part_header)(struct http_request *, const struct mime_part_headers *);
int (*handle_mime_body)(struct http_request *, const char *, size_t);
int (*handle_mime_body)(struct http_request *, char *, size_t);
int (*handle_mime_part_end)(struct http_request *);
int (*handle_mime_epilogue)(struct http_request *, const char *, size_t);
int (*handle_mime_epilogue)(struct http_request *, char *, size_t);
};
struct http_request;
@ -185,11 +185,11 @@ struct http_request {
HTTP_REQUEST_PARSER *handle_headers; // called after all HTTP headers are parsed
HTTP_REQUEST_PARSER *handle_content_end; // called after all content is received
// The following are used for managing the buffer during RECEIVE phase.
const char *reserved; // end of reserved data in buffer[]
const char *received; // start of received data in buffer[]
const char *end; // end of received data in buffer[]
const char *parsed; // start of unparsed data in buffer[]
const char *cursor; // for parsing
char *reserved; // end of reserved data in buffer[]
char *received; // start of received data in buffer[]
char *end; // end of received data in buffer[]
char *parsed; // start of unparsed data in buffer[]
char *cursor; // for parsing
http_size_t request_content_remaining;
// The following are used for parsing a multipart body.
enum mime_state { START, PREAMBLE, HEADER, BODY, EPILOGUE } form_data_state;

View File

@ -780,26 +780,67 @@ typedef struct rhizome_http_request
*/
unsigned int uuid;
/* For receiving a POST multipart form:
*/
// Which part is currently being received
enum rhizome_direct_mime_part { NONE = 0, MANIFEST, DATA } current_part;
// Temporary file currently current part is being written to
int part_fd;
// Which parts have already been received
bool_t received_manifest;
bool_t received_data;
// Name of data file supplied in part's Content-Disposition header, filename
// parameter (if any)
char data_file_name[MIME_FILENAME_MAXLEN + 1];
/* For responses that pertain to a single manifest.
/* For requests/responses that pertain to a single manifest.
*/
rhizome_manifest *manifest;
/* Finaliser for union contents (below).
*/
void (*finalise_union)(struct rhizome_http_request *);
/* Mutually exclusive response arguments.
*/
union {
/* For receiving Rhizome Direct import request
*/
struct {
// Which part is currently being received
const char *current_part;
// Temporary file currently current part is being written to
int part_fd;
// Which parts have already been received
bool_t received_manifest;
bool_t received_data;
// Name of data file supplied in part's Content-Disposition header, filename
// parameter (if any)
char data_file_name[MIME_FILENAME_MAXLEN + 1];
}
direct_import;
/* For receiving RESTful Rhizome insert request
*/
struct {
// Which part is currently being received
const char *current_part;
// Which parts have already been received
bool_t received_author;
bool_t received_secret;
bool_t received_manifest;
bool_t received_payload;
// For storing the "bundle-author" hex SID as we receive it
char author_hex[SID_STRLEN];
size_t author_hex_len;
sid_t author;
// For storing the "bundle-secret" hex as we receive it
char secret_hex[RHIZOME_BUNDLE_KEY_STRLEN];
size_t secret_hex_len;
rhizome_bk_t bundle_secret;
// The "force-new" parameter
char force_new_text[5]; // enough for "false"
size_t force_new_text_len;
bool_t force_new;
// For storing the manifest text (malloc/realloc) as we receive it
char *manifest_text;
size_t manifest_text_size;
size_t manifest_len;
// For receiving the payload
enum rhizome_payload_status payload_status;
uint64_t payload_size;
struct rhizome_write write;
}
insert;
/* For responses that send part or all of a payload.
*/
struct rhizome_read read_state;
@ -807,14 +848,16 @@ typedef struct rhizome_http_request
/* For responses that list manifests.
*/
struct {
enum { LIST_HEADER = 0, LIST_ROWS, LIST_DONE } phase;
uint64_t rowid_highest;
size_t rowcount;
time_ms_t end_time;
struct rhizome_list_cursor cursor;
} list;
enum { LIST_HEADER = 0, LIST_ROWS, LIST_DONE } phase;
uint64_t rowid_highest;
size_t rowcount;
time_ms_t end_time;
struct rhizome_list_cursor cursor;
}
list;
} u;
} rhizome_http_request;
int rhizome_received_content(const unsigned char *bidprefix,uint64_t version,

View File

@ -1435,8 +1435,7 @@ int rhizome_drop_stored_file(const rhizome_filehash_t *hashp, int maximum_priori
*/
int rhizome_store_manifest(rhizome_manifest *m)
{
if (!m->finalised)
return WHY("Manifest was not finalised");
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)

View File

@ -57,11 +57,11 @@ static void rhizome_direct_clear_temporary_files(rhizome_http_request *r)
static int rhizome_direct_import_end(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (!r->received_manifest) {
if (!r->u.direct_import.received_manifest) {
http_request_simple_response(&r->http, 400, "Missing 'manifest' part");
return 0;
}
if (!r->received_data) {
if (!r->u.direct_import.received_data) {
http_request_simple_response(&r->http, 400, "Missing 'data' part");
return 0;
}
@ -126,7 +126,7 @@ static int rhizome_direct_import_end(struct http_request *hr)
int rhizome_direct_enquiry_end(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (!r->received_data) {
if (!r->u.direct_import.received_data) {
http_request_simple_response(&r->http, 400, "Missing 'data' part");
return 0;
}
@ -189,7 +189,7 @@ static int rhizome_direct_addfile_end(struct http_request *hr)
// If given a file without a manifest, we should only accept if it we are configured to do so, and
// the connection is from localhost. Otherwise people could cause your servald to create
// arbitrary bundles, which would be bad.
if (!r->received_manifest) {
if (!r->u.direct_import.received_manifest) {
char payload_path[512];
if (form_temporary_file_path(r, payload_path, "data") == -1) {
http_request_simple_response(&r->http, 500, "Internal Error: Buffer overrun");
@ -242,7 +242,7 @@ static int rhizome_direct_addfile_end(struct http_request *hr)
if (m->service == NULL)
rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE);
const sid_t *author = is_sid_t_any(config.rhizome.api.addfile.default_author) ? NULL : &config.rhizome.api.addfile.default_author;
if (rhizome_fill_manifest(m, r->data_file_name, author)) {
if (rhizome_fill_manifest(m, r->u.direct_import.data_file_name, author)) {
rhizome_manifest_free(m);
rhizome_direct_clear_temporary_files(r);
http_request_simple_response(&r->http, 500, "Internal Error: Could not fill manifest");
@ -285,48 +285,48 @@ static int rhizome_direct_addfile_end(struct http_request *hr)
}
}
static char PART_MANIFEST[] = "manifest";
static char PART_DATA[] = "data";
static int rhizome_direct_process_mime_start(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
assert(r->current_part == NONE);
assert(r->part_fd == -1);
assert(r->u.direct_import.current_part == NULL);
assert(r->u.direct_import.part_fd == -1);
return 0;
}
static int rhizome_direct_process_mime_end(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (r->part_fd != -1) {
if (close(r->part_fd) == -1) {
WHYF_perror("close(%d)", r->part_fd);
if (r->u.direct_import.part_fd != -1) {
if (close(r->u.direct_import.part_fd) == -1) {
WHYF_perror("close(%d)", r->u.direct_import.part_fd);
http_request_simple_response(&r->http, 500, "Internal Error: Close temporary file failed");
return 500;
}
r->part_fd = -1;
r->u.direct_import.part_fd = -1;
}
switch (r->current_part) {
case MANIFEST:
r->received_manifest = 1;
break;
case DATA:
r->received_data = 1;
break;
case NONE:
break;
}
r->current_part = NONE;
if (r->u.direct_import.current_part == PART_MANIFEST)
r->u.direct_import.received_manifest = 1;
else if (r->u.direct_import.current_part == PART_DATA)
r->u.direct_import.received_data = 1;
r->u.direct_import.current_part = NULL;
return 0;
}
static int rhizome_direct_process_mime_part_header(struct http_request *hr, const struct mime_part_headers *h)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (strcmp(h->content_disposition.name, "data") == 0) {
r->current_part = DATA;
strncpy(r->data_file_name, h->content_disposition.filename, sizeof r->data_file_name)[sizeof r->data_file_name - 1] = '\0';
if (strcmp(h->content_disposition.name, PART_DATA) == 0) {
r->u.direct_import.current_part = PART_DATA;
strncpy(r->u.direct_import.data_file_name,
h->content_disposition.filename,
sizeof r->u.direct_import.data_file_name)
[sizeof r->u.direct_import.data_file_name - 1] = '\0';
}
else if (strcmp(h->content_disposition.name, "manifest") == 0) {
r->current_part = MANIFEST;
else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) {
r->u.direct_import.current_part = PART_MANIFEST;
} else
return 0;
char path[512];
@ -334,7 +334,7 @@ static int rhizome_direct_process_mime_part_header(struct http_request *hr, cons
http_request_simple_response(&r->http, 500, "Internal Error: Buffer overrun");
return 0;
}
if ((r->part_fd = open(path, O_WRONLY | O_CREAT, 0666)) == -1) {
if ((r->u.direct_import.part_fd = open(path, O_WRONLY | O_CREAT, 0666)) == -1) {
WHYF_perror("open(%s,O_WRONLY|O_CREAT,0666)", alloca_str_toprint(path));
http_request_simple_response(&r->http, 500, "Internal Error: Create temporary file failed");
return 0;
@ -342,11 +342,11 @@ static int rhizome_direct_process_mime_part_header(struct http_request *hr, cons
return 0;
}
static int rhizome_direct_process_mime_body(struct http_request *hr, const char *buf, size_t len)
static int rhizome_direct_process_mime_body(struct http_request *hr, char *buf, size_t len)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (r->part_fd != -1) {
if (write_all(r->part_fd, buf, len) == -1) {
if (r->u.direct_import.part_fd != -1) {
if (write_all(r->u.direct_import.part_fd, buf, len) == -1) {
http_request_simple_response(&r->http, 500, "Internal Error: Write temporary file failed");
return 500;
}
@ -365,9 +365,9 @@ int rhizome_direct_import(rhizome_http_request *r, const char *remainder)
r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header;
r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body;
r->http.handle_content_end = rhizome_direct_import_end;
r->current_part = NONE;
r->part_fd = -1;
r->data_file_name[0] = '\0';
r->u.direct_import.current_part = NULL;
r->u.direct_import.part_fd = -1;
r->u.direct_import.data_file_name[0] = '\0';
return 1;
}
@ -382,9 +382,9 @@ int rhizome_direct_enquiry(rhizome_http_request *r, const char *remainder)
r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header;
r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body;
r->http.handle_content_end = rhizome_direct_enquiry_end;
r->current_part = NONE;
r->part_fd = -1;
r->data_file_name[0] = '\0';
r->u.direct_import.current_part = NULL;
r->u.direct_import.part_fd = -1;
r->u.direct_import.data_file_name[0] = '\0';
return 1;
}
@ -414,9 +414,9 @@ int rhizome_direct_addfile(rhizome_http_request *r, const char *remainder)
r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header;
r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body;
r->http.handle_content_end = rhizome_direct_addfile_end;
r->current_part = NONE;
r->part_fd = -1;
r->data_file_name[0] = '\0';
r->u.direct_import.current_part = NULL;
r->u.direct_import.part_fd = -1;
r->u.direct_import.data_file_name[0] = '\0';
return 1;
}

View File

@ -30,8 +30,10 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "overlay_address.h"
#include "conf.h"
#include "str.h"
#include "strbuf.h"
#include "strbuf_helpers.h"
#include "rhizome.h"
#include "dataformats.h"
#include "http_server.h"
#define RHIZOME_SERVER_MAX_LIVE_REQUESTS 32
@ -45,6 +47,7 @@ struct http_handler{
static HTTP_HANDLER restful_rhizome_bundlelist_json;
static HTTP_HANDLER restful_rhizome_newsince;
static HTTP_HANDLER restful_rhizome_insert;
static HTTP_HANDLER restful_rhizome_;
static HTTP_HANDLER rhizome_status_page;
@ -62,6 +65,7 @@ extern HTTP_HANDLER rhizome_direct_dispatch;
struct http_handler paths[]={
{"/restful/rhizome/bundlelist.json", restful_rhizome_bundlelist_json},
{"/restful/rhizome/newsince/", restful_rhizome_newsince},
{"/restful/rhizome/insert", restful_rhizome_insert},
{"/restful/rhizome/", restful_rhizome_},
{"/rhizome/status", rhizome_status_page},
{"/rhizome/file/", rhizome_file_page},
@ -96,6 +100,8 @@ static int rhizome_dispatch(struct http_request *hr)
return 404;
}
static HTTP_RENDERER render_manifest_headers;
struct sched_ent server_alarm;
struct profile_total server_stats = {
.name = "rhizome_server_poll",
@ -233,10 +239,32 @@ success:
}
static void rhizome_server_finalise_http_request(struct http_request *_r)
static void finalise_union_read_state(rhizome_http_request *r)
{
rhizome_http_request *r = (rhizome_http_request *) _r;
rhizome_read_close(&r->u.read_state);
}
static void finalise_union_rhizome_insert(rhizome_http_request *r)
{
if (r->u.insert.manifest_text) {
free(r->u.insert.manifest_text);
r->u.insert.manifest_text = NULL;
}
if (r->u.insert.write.blob_fd != -1)
rhizome_fail_write(&r->u.insert.write);
}
static void rhizome_server_finalise_http_request(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (r->manifest) {
rhizome_manifest_free(r->manifest);
r->manifest = NULL;
}
if (r->finalise_union) {
r->finalise_union(r);
r->finalise_union = NULL;
}
request_count--;
}
@ -276,9 +304,6 @@ void rhizome_server_poll(struct sched_ent *alarm)
} else {
request_count++;
request->uuid = rhizome_http_request_uuid_counter++;
request->data_file_name[0] = '\0';
request->u.read_state.blob_fd = -1;
request->u.read_state.blob_rowid = 0;
if (peerip)
request->http.client_sockaddr_in = *peerip;
request->http.handle_headers = rhizome_dispatch;
@ -559,13 +584,379 @@ static int restful_rhizome_bundlelist_json_content(struct http_request *hr, unsi
return ret;
}
static HTTP_REQUEST_PARSER restful_rhizome_insert_end;
static int insert_mime_part_start(struct http_request *);
static int insert_mime_part_end(struct http_request *);
static int insert_mime_part_header(struct http_request *, const struct mime_part_headers *);
static int insert_mime_part_body(struct http_request *, char *, size_t);
static int restful_rhizome_insert(rhizome_http_request *r, const char *remainder)
{
if (*remainder)
return 404;
if (!is_rhizome_http_enabled())
return 403;
if (r->http.verb != HTTP_VERB_POST)
return 405;
int ret = authorize(&r->http);
if (ret)
return ret;
// Parse the request body as multipart/form-data.
assert(r->u.insert.current_part == NULL);
assert(!r->u.insert.received_author);
assert(!r->u.insert.received_secret);
assert(!r->u.insert.received_manifest);
assert(!r->u.insert.received_payload);
bzero(&r->u.insert.write, sizeof r->u.insert.write);
r->u.insert.write.blob_fd = -1;
r->finalise_union = finalise_union_rhizome_insert;
r->http.form_data.handle_mime_part_start = insert_mime_part_start;
r->http.form_data.handle_mime_part_end = insert_mime_part_end;
r->http.form_data.handle_mime_part_header = insert_mime_part_header;
r->http.form_data.handle_mime_body = insert_mime_part_body;
// Perform the insert once the body has arrived.
r->http.handle_content_end = restful_rhizome_insert_end;
return 1;
}
static char PART_MANIFEST[] = "manifest";
static char PART_PAYLOAD[] = "payload";
static char PART_AUTHOR[] = "bundle-author";
static char PART_SECRET[] = "bundle-secret";
static int insert_mime_part_start(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
assert(r->u.insert.current_part == NULL);
return 0;
}
static int http_response_form_part(rhizome_http_request *r, const char *what, const char *partname, const char *text, size_t textlen)
{
if (config.debug.rhizome)
DEBUGF("%s \"%s\" form part %s", what, partname, text ? alloca_toprint(-1, text, textlen) : "");
strbuf msg = strbuf_alloca(100);
strbuf_sprintf(msg, "%s \"%s\" form part", what, partname);
http_request_simple_response(&r->http, 403, strbuf_str(msg));
return 403;
}
static int insert_make_manifest(rhizome_http_request *r)
{
if (!r->u.insert.received_manifest)
return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0);
if ((r->manifest = rhizome_new_manifest())) {
if (r->u.insert.manifest_len == 0)
return 0;
assert(r->u.insert.manifest_len <= sizeof r->manifest->manifestdata);
memcpy(r->manifest->manifestdata, r->u.insert.manifest_text, r->u.insert.manifest_len);
r->manifest->manifest_all_bytes = r->u.insert.manifest_len;
int n = rhizome_manifest_parse(r->manifest);
switch (n) {
case -1:
break;
case 0:
if (!r->manifest->malformed)
return 0;
// fall through
case 1:
http_request_simple_response(&r->http, 403, "Malformed manifest");
return 403;
default:
WHYF("rhizome_manifest_parse() returned %d", n);
break;
}
}
return 500;
}
static int insert_mime_part_header(struct http_request *hr, const struct mime_part_headers *h)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (strcmp(h->content_disposition.name, PART_AUTHOR) == 0) {
if (r->u.insert.received_author)
return http_response_form_part(r, "Duplicate", PART_AUTHOR, NULL, 0);
r->u.insert.current_part = PART_AUTHOR;
}
else if (strcmp(h->content_disposition.name, PART_SECRET) == 0) {
if (r->u.insert.received_secret)
return http_response_form_part(r, "Duplicate", PART_SECRET, NULL, 0);
r->u.insert.current_part = PART_SECRET;
}
else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) {
// Reject a request if it has a repeated manifest part.
if (r->u.insert.received_manifest)
return http_response_form_part(r, "Duplicate", PART_MANIFEST, NULL, 0);
assert(r->u.insert.manifest_text == NULL);
assert(r->u.insert.manifest_text_size == 0);
assert(r->u.insert.manifest_len == 0);
if ( strcmp(h->content_type.type, "rhizome-manifest") != 0
|| strcmp(h->content_type.subtype, "text") != 0
)
return http_response_form_part(r, "Unsupported Content-Type in", PART_MANIFEST, NULL, 0);
r->u.insert.current_part = PART_MANIFEST;
}
else if (strcmp(h->content_disposition.name, PART_PAYLOAD) == 0) {
// Reject a request if it has a repeated payload part.
if (r->u.insert.received_payload)
return http_response_form_part(r, "Duplicate", PART_PAYLOAD, NULL, 0);
// Reject a request if it has a missing manifest part preceding the payload part.
if (!r->u.insert.received_manifest)
return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0);
assert(r->manifest != NULL);
r->u.insert.current_part = PART_PAYLOAD;
// If the manifest does not contain a 'name' field, then assign it from the payload filename.
if ( strcasecmp(RHIZOME_SERVICE_FILE, r->manifest->service) == 0
&& r->manifest->name == NULL
&& *h->content_disposition.filename
)
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->u.insert.payload_size = 0;
switch (r->u.insert.payload_status) {
case RHIZOME_PAYLOAD_STATUS_ERROR:
WHYF("rhizome_write_open_manifest() returned %d", r->u.insert.payload_status);
return 500;
case RHIZOME_PAYLOAD_STATUS_STORED:
// TODO: initialise payload hash so it can be compared with stored payload
break;
default:
break;
}
}
else
return http_response_form_part(r, "Unsupported", h->content_disposition.name, NULL, 0);
return 0;
}
static int accumulate_text(rhizome_http_request *r, const char *partname, char *textbuf, size_t textsiz, size_t *textlenp, const char *buf, size_t len)
{
if (len) {
size_t newlen = *textlenp + len;
if (newlen > textsiz) {
if (config.debug.rhizome)
DEBUGF("Form part \"%s\" too long, %zu bytes overflows maximum %zu by %zu",
partname, newlen, textsiz, (size_t)(newlen - textsiz)
);
strbuf msg = strbuf_alloca(100);
strbuf_sprintf(msg, "Overflow in \"%s\" form part", partname);
http_request_simple_response(&r->http, 403, strbuf_str(msg));
return 0;
}
memcpy(textbuf + *textlenp, buf, len);
*textlenp = newlen;
}
return 1;
}
static int insert_mime_part_body(struct http_request *hr, char *buf, size_t len)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (r->u.insert.current_part == PART_AUTHOR) {
accumulate_text(r, PART_AUTHOR,
r->u.insert.author_hex,
sizeof r->u.insert.author_hex,
&r->u.insert.author_hex_len,
buf, len);
}
else if (r->u.insert.current_part == PART_SECRET) {
accumulate_text(r, PART_SECRET,
r->u.insert.secret_hex,
sizeof r->u.insert.secret_hex,
&r->u.insert.secret_hex_len,
buf, len);
}
else if (r->u.insert.current_part == PART_MANIFEST) {
if (len == 0)
return 0;
size_t newlen = r->u.insert.manifest_len + len;
if (newlen > MAX_MANIFEST_BYTES) {
if (config.debug.rhizome)
DEBUGF("manifest too large, %zu bytes overflows maximum %zu by %zu",
newlen, MAX_MANIFEST_BYTES, (size_t)(newlen - MAX_MANIFEST_BYTES)
);
http_request_simple_response(&r->http, 403, "Manifest size overflow");
return 403;
}
if (newlen > r->u.insert.manifest_text_size) {
if ((r->u.insert.manifest_text = erealloc(r->u.insert.manifest_text, newlen)) == NULL)
return 500;
r->u.insert.manifest_text_size = newlen;
}
memcpy(r->u.insert.manifest_text + r->u.insert.manifest_len, buf, len);
r->u.insert.manifest_len = newlen;
}
else if (r->u.insert.current_part == PART_PAYLOAD) {
r->u.insert.payload_size += len;
switch (r->u.insert.payload_status) {
case RHIZOME_PAYLOAD_STATUS_NEW:
if (rhizome_write_buffer(&r->u.insert.write, (unsigned char *)buf, len) == -1)
return 500;
break;
case RHIZOME_PAYLOAD_STATUS_STORED:
// TODO: calculate payload hash so it can be compared with stored payload
break;
default:
break;
}
} else
FATALF("current_part = %s", alloca_str_toprint(r->u.insert.current_part));
return 0;
}
static int insert_mime_part_end(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (r->u.insert.current_part == PART_AUTHOR) {
if ( r->u.insert.author_hex_len != sizeof r->u.insert.author_hex
|| strn_to_sid_t(&r->u.insert.author, r->u.insert.author_hex, NULL) == -1
)
return http_response_form_part(r, "Invalid", PART_AUTHOR, r->u.insert.author_hex, r->u.insert.author_hex_len);
r->u.insert.received_author = 1;
if (config.debug.rhizome)
DEBUGF("received %s = %s", PART_AUTHOR, alloca_tohex_sid_t(r->u.insert.author));
}
else if (r->u.insert.current_part == PART_SECRET) {
if ( r->u.insert.secret_hex_len != sizeof r->u.insert.secret_hex
|| strn_to_rhizome_bk_t(&r->u.insert.bundle_secret, r->u.insert.secret_hex, NULL) == -1
)
return http_response_form_part(r, "Invalid", PART_SECRET, r->u.insert.secret_hex, r->u.insert.secret_hex_len);
r->u.insert.received_secret = 1;
if (config.debug.rhizome)
DEBUGF("received %s = %s", PART_SECRET, alloca_tohex_rhizome_bk_t(r->u.insert.bundle_secret));
}
else if (r->u.insert.current_part == PART_MANIFEST) {
r->u.insert.received_manifest = 1;
int result = insert_make_manifest(r);
if (result)
return result;
if (r->manifest->has_id && r->u.insert.received_secret)
rhizome_apply_bundle_secret(r->manifest, &r->u.insert.bundle_secret);
if (r->manifest->service == NULL)
rhizome_manifest_set_service(r->manifest, RHIZOME_SERVICE_FILE);
if (rhizome_fill_manifest(r->manifest, NULL, r->u.insert.received_author ? &r->u.insert.author: NULL) == -1) {
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;
}
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;
}
} else
FATALF("current_part = %s", alloca_str_toprint(r->u.insert.current_part));
r->u.insert.current_part = NULL;
return 0;
}
static int restful_rhizome_insert_end(struct http_request *hr)
{
rhizome_http_request *r = (rhizome_http_request *) hr;
if (!r->u.insert.received_manifest)
return http_response_form_part(r, "Missing", PART_MANIFEST, NULL, 0);
if (!r->u.insert.received_payload)
return http_response_form_part(r, "Missing", PART_PAYLOAD, NULL, 0);
// 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;
case RHIZOME_PAYLOAD_STATUS_NEW:
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:
// TODO: check that stored hash matches received payload's hash
// fall through
case RHIZOME_PAYLOAD_STATUS_EMPTY:
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:
{
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;
}
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);
}
// Finalise the manifest and add it to the store.
if (r->manifest->filesize) {
if (!r->manifest->has_filehash)
rhizome_manifest_set_filehash(r->manifest, &r->u.insert.write.id);
else
assert(cmp_rhizome_filehash_t(&r->u.insert.write.id, &r->manifest->filehash) == 0);
}
if (!rhizome_manifest_validate(r->manifest) || r->manifest->malformed) {
http_request_simple_response(&r->http, 403, "Manifest is malformed");
return 403;
}
if (!r->manifest->haveSecret) {
http_request_simple_response(&r->http, 403, "Missing bundle secret");
return 403;
}
rhizome_manifest *mout = NULL;
int result;
switch (rhizome_manifest_finalise(r->manifest, &mout, !r->u.insert.force_new)) {
case RHIZOME_BUNDLE_STATUS_NEW:
result = 201;
if (mout && mout != r->manifest)
rhizome_manifest_free(mout);
mout = NULL;
break;
case RHIZOME_BUNDLE_STATUS_SAME:
case RHIZOME_BUNDLE_STATUS_OLD:
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
result = 200;
break;
case RHIZOME_BUNDLE_STATUS_INVALID:
result = 403;
break;
case RHIZOME_BUNDLE_STATUS_ERROR:
default:
result = 500;
break;
}
if (mout && mout != r->manifest) {
rhizome_manifest_free(r->manifest);
r->manifest = mout;
}
if (result >= 400)
return result;
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
);
return 0;
}
static int rhizome_response_content_init_filehash(rhizome_http_request *r, const rhizome_filehash_t *hash);
static int rhizome_response_content_init_payload(rhizome_http_request *r, rhizome_manifest *);
static HTTP_CONTENT_GENERATOR rhizome_payload_content;
static HTTP_RENDERER render_manifest_headers;
static HTTP_HANDLER restful_rhizome_bid_rhm;
static HTTP_HANDLER restful_rhizome_bid_raw_bin;
static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
@ -596,22 +987,19 @@ static int restful_rhizome_(rhizome_http_request *r, const char *remainder)
int ret = authorize(&r->http);
if (ret)
return ret;
rhizome_manifest *m = rhizome_new_manifest();
ret = rhizome_retrieve_manifest(&bid, m);
if (ret == -1) {
rhizome_manifest_free(m);
if ((r->manifest = rhizome_new_manifest()) == NULL)
return 500;
ret = rhizome_retrieve_manifest(&bid, r->manifest);
if (ret == -1)
return 500;
}
if (ret == 0) {
rhizome_authenticate_author(m);
r->manifest = m;
rhizome_authenticate_author(r->manifest);
r->http.render_extra_headers = render_manifest_headers;
} else {
assert(r->manifest == NULL);
assert(r->http.render_extra_headers == NULL);
}
ret = handler(r, remainder);
rhizome_manifest_free(m);
return ret;
}
@ -619,7 +1007,7 @@ static int restful_rhizome_bid_rhm(rhizome_http_request *r, const char *remainde
{
if (*remainder || r->manifest == NULL)
return 404;
http_request_response_static(&r->http, 200, "x-servalproject/rhizome-manifest-text",
http_request_response_static(&r->http, 200, "rhizome-manifest/text",
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
);
return 1;
@ -745,6 +1133,9 @@ static int rhizome_response_content_init_read_state(rhizome_http_request *r)
static int rhizome_response_content_init_filehash(rhizome_http_request *r, const rhizome_filehash_t *hash)
{
bzero(&r->u.read_state, sizeof r->u.read_state);
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) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
@ -766,6 +1157,9 @@ static int rhizome_response_content_init_filehash(rhizome_http_request *r, const
static int rhizome_response_content_init_payload(rhizome_http_request *r, rhizome_manifest *m)
{
bzero(&r->u.read_state, sizeof r->u.read_state);
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) {
case RHIZOME_PAYLOAD_STATUS_EMPTY:
@ -846,16 +1240,15 @@ static int manifest_by_prefix_page(rhizome_http_request *r, const char *remainde
unsigned prefix_len = strn_fromhex(prefix.binary, sizeof prefix.binary, remainder, &endp);
if (endp == NULL || *endp != '\0' || prefix_len < 1)
return 404; // not found
rhizome_manifest *m = rhizome_new_manifest();
int ret = rhizome_retrieve_manifest_by_prefix(prefix.binary, prefix_len, m);
if ((r->manifest = rhizome_new_manifest()) == NULL)
return 500;
int ret = rhizome_retrieve_manifest_by_prefix(prefix.binary, prefix_len, r->manifest);
if (ret == -1)
return 500;
if (ret == 0) {
http_request_response_static(&r->http, 200, "application/binary", (const char *)m->manifestdata, m->manifest_all_bytes);
rhizome_manifest_free(m);
http_request_response_static(&r->http, 200, "application/binary", (const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes);
return 1;
}
rhizome_manifest_free(m);
return 404;
}

View File

@ -691,7 +691,8 @@ static enum rhizome_bundle_status rhizome_write_derive_key(rhizome_manifest *m,
return RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL;
if (config.debug.rhizome)
DEBUGF("Encrypting payload contents for %s, %"PRIu64, alloca_tohex_rhizome_bid_t(m->cryptoSignPublic), m->version);
DEBUGF("Encrypting payload contents for bid=%s, version=%"PRIu64,
alloca_tohex_rhizome_bid_t(m->cryptoSignPublic), m->version);
write->crypt=1;
if (m->is_journal && m->tail > 0)

View File

@ -24,7 +24,7 @@ rexp_bundlesecret="$rexp_bundlekey"
rexp_filehash='[0-9a-fA-F]\{128\}'
rexp_filesize='[0-9]\{1,\}'
rexp_version='[0-9]\{1,\}'
rexp_crypt='[0-9]\{1,\}'
rexp_crypt='[01]'
rexp_date='[0-9]\{1,\}'
rexp_rowid='[0-9]\{1,\}'

View File

@ -1,6 +1,6 @@
#!/bin/bash
# Tests for Serval rhizome operations.
# Tests for Serval DNA HTTP RESTful interface
#
# Copyright 2013 Serval Project, Inc.
#
@ -26,6 +26,7 @@ shopt -s extglob
setup() {
CR=' '
VT=' '
setup_curl 7
setup_jq 1.2
setup_servald
@ -35,8 +36,11 @@ setup() {
set rhizome.api.restful.users.harry.password potter \
set rhizome.api.restful.users.ron.password weasley \
set rhizome.api.restful.users.hermione.password grainger
create_single_identity
echo "$SIDA1" >sids
if [ -z "$IDENTITY_COUNT" ]; then
create_single_identity
else
create_identities $IDENTITY_COUNT
fi
start_servald_instances +A
wait_until rhizome_http_server_started +A
get_rhizome_server_port PORTA +A
@ -56,6 +60,8 @@ set_rhizome_config() {
executeOk_servald config \
set debug.httpd on \
set debug.rhizome_httpd on \
set debug.rhizome_manifest on \
set debug.externalblobs on \
set debug.rhizome on \
set debug.verbose on \
set log.console.level debug
@ -121,6 +127,7 @@ add_bundles() {
extract_stdout_author AUTHOR[$n]
extract_stdout_secret SECRET[$n]
extract_stdout_inserttime INSERTTIME[$n]
NAME[$n]=file$n
if $encrypted; then
extract_stdout_crypt CRYPT[$n]
assert [ "${CRYPT[$n]}" = 1 ]
@ -324,7 +331,7 @@ assert_http_response_headers() {
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: \"file$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\$"
@ -398,9 +405,457 @@ test_RhizomePayloadDecrypted() {
done
}
doc_RhizomeInsert="Insert new Rhizome bundle"
extract_http_header() {
local __var="$1"
local __headerfile="$2"
local __header="$3"
local __rexp="$4"
local __value=$($SED -n -e "/^$__header:[ $VT]*$__rexp$CR\$/s/^$__header:[ $VT]*\(.*\)$CR\$/\1/p" "$__headerfile")
assert --message="$__headerfile contains valid '$__header' header" \
--dump-on-fail="$__headerfile" \
[ -n "$__value" ]
[ -n "$__var" ] && eval $__var=\"\$__value\"
}
http_unquote_string() {
local __var="$1"
local __unq="$(eval echo '"${'$__var'}"' | sed -e 's/^"//' -e 's/"$//' -e 's/\\\(.\)/\1/g')"
eval $__var=\"\$__unq\"
}
doc_RhizomeInsert="HTTP RESTful insert new Rhizome bundles"
setup_RhizomeInsert() {
IDENTITY_COUNT=3
SIDA4=
setup
for n in 1 2 3 4; do
create_file file$n $((1000 + $n))
create_file nfile$n $((1100 + $n))
payload_filename[$n]=
eval author[$n]=\$SIDA$n
service[$n]=file
done
name[1]=blarg
echo "name=blarg" >manifest1
name[2]=file2
echo "crypt=1" >manifest2
name[3]=kibble
payload_filename[3]=kibble
>manifest3
name[4]=
service[4]=wah
echo -e "service=wah\ncrypt=0" >manifest4
}
test_RhizomeInsert() {
:
for n in 1 2 3 4; do
authorargs=()
[ -n "${author[$n]}" ] && authorargs=(--form "bundle-author=${author[$n]}")
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output file$n.manifest \
--dump-header http.header$n \
--basic --user harry:potter \
"${authorargs[@]}" \
--form "manifest=@manifest$n;type=rhizome-manifest/text" \
--form "payload=@file$n${payload_filename[$n]:+;filename=\"${payload_filename[$n]}\"}" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header$n file$n.manifest
assertExitStatus == 0
assertStdoutIs 201
extract_http_header BID[$n] http.header$n Serval-Rhizome-Bundle-Id "$rexp_manifestid"
extract_http_header VERSION[$n] http.header$n Serval-Rhizome-Bundle-Version "$rexp_version"
extract_http_header SIZE[$n] http.header$n Serval-Rhizome-Bundle-Filesize "$rexp_filesize"
extract_http_header HASH[$n] http.header$n Serval-Rhizome-Bundle-Filehash "$rexp_filehash"
extract_http_header DATE[$n] http.header$n Serval-Rhizome-Bundle-Date "$rexp_date"
extract_http_header ROWID[$n] http.header$n Serval-Rhizome-Bundle-Rowid "$rexp_rowid"
extract_http_header INSERTTIME[$n] http.header$n Serval-Rhizome-Bundle-Inserttime "$rexp_date"
extract_http_header SECRET[$n] http.header$n Serval-Rhizome-Bundle-Secret "$rexp_bundlesecret"
extract_http_header SERVICE[$n] http.header$n Serval-Rhizome-Bundle-Service ".*"
assert [ ${SIZE[$n]} -eq $((1000 + $n)) ]
assert [ "${SERVICE[$n]}" = "${service[$n]}" ]
extract_manifest_id BID file$n.manifest
extract_manifest_version VERSION file$n.manifest
extract_manifest_filesize SIZE file$n.manifest
extract_manifest_filehash HASH file$n.manifest
extract_manifest_date DATE file$n.manifest
extract_manifest_service SERVICE file$n.manifest
assert [ "$BID" = "${BID[$n]}" ]
assert [ "$VERSION" = "${VERSION[$n]}" ]
assert [ "$SIZE" = "${SIZE[$n]}" ]
assert [ "$HASH" = "${HASH[$n]}" ]
assert [ "$DATE" = "${DATE[$n]}" ]
assert [ "$SERVICE" = "${SERVICE[$n]}" ]
if [ -n "${name[$n]}" ]; then
extract_http_header NAME[$n] http.header$n Serval-Rhizome-Bundle-Name ".*"
http_unquote_string NAME[$n]
assert [ "${NAME[$n]}" = "${name[$n]}" ]
extract_manifest_name NAME file$n.manifest
assert [ "$NAME" = "${NAME[$n]}" ]
fi
if [ -n "${author[$n]}" ]; then
extract_http_header AUTHOR[$n] http.header$n Serval-Rhizome-Bundle-Author "$rexp_sid"
assert [ "${AUTHOR[$n]}" = "${author[$n]}" ]
extract_http_header BK[$n] http.header$n Serval-Rhizome-Bundle-BK "$rexp_bundlekey"
extract_manifest_BK BK file$n.manifest
assert [ "$BK" = "${BK[$n]}" ]
fi
done
executeOk_servald rhizome list
assert_rhizome_list \
--fromhere=1 \
--author=${author[1]} file1 \
--author=${author[2]} file2 \
--author=${author[3]} file3 \
--fromhere=0 \
--author=${author[4]} file4
for n in 1 2 3 4; do
executeOk_servald rhizome extract bundle ${BID[$n]} xfile$n.manifest xfile$n
assert diff xfile$n.manifest file$n.manifest
assert diff file$n xfile$n
done
for n in 1 2 3 4; do
$SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d' xfile$n.manifest >nmanifest$n
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output nfile$n.manifest \
--dump-header http.header$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
assertExitStatus == 0
if [ -n "${author[$n]}" ]; then
assertStdoutIs 201
else
assertStdoutIs 403
assertGrep --ignore-case nfile$n.manifest "missing bundle secret"
fi
done
}
doc_RhizomeInsertAnon="HTTP RESTful update anonymous Rhizome bundle"
setup_RhizomeInsertAnon() {
setup
create_file file1 1001
executeOk_servald rhizome add file '' file1 file1.manifest
extract_stdout_secret SECRET
assertGrep --matches=0 file1.manifest '^BK='
$SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d' file1.manifest >file2.manifest
create_file file2 1002
}
test_RhizomeInsertAnon() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output ifile2.manifest \
--dump-header http.header \
--basic --user harry:potter \
--form "bundle-secret=$SECRET" \
--form "manifest=@file2.manifest;type=rhizome-manifest/text" \
--form "payload=@file2" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header ifile2.manifest
assertExitStatus == 0
assertStdoutIs 201
executeOk_servald rhizome list
assert_rhizome_list --fromhere=0 --manifest=ifile2.manifest file2
}
doc_RhizomeInsertEmpty="HTTP RESTful insert empty Rhizome bundle"
setup_RhizomeInsertEmpty() {
setup
>empty
assert [ ! -s empty ]
}
test_RhizomeInsertEmpty() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output empty.manifest \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=;type=rhizome-manifest/text" \
--form "payload=@empty;filename=\"lucky\"" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header empty.manifest
assertExitStatus == 0
assertStdoutIs 201
extract_manifest_id BID empty.manifest
executeOk_servald rhizome list
assert_rhizome_list empty
executeOk_servald rhizome extract bundle $BID xempty.manifest xempty
assert [ ! -e xempty ]
assert diff xempty.manifest empty.manifest
}
doc_RhizomeInsertLarge="HTTP RESTful insert 50 MiB Rhizome bundle"
setup_RhizomeInsertLarge() {
setup
create_file file1 50m
}
test_RhizomeInsertLarge() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output file1.manifest \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=;type=rhizome-manifest/text" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header file1.manifest
assertExitStatus == 0
assertStdoutIs 201
extract_manifest_id BID file1.manifest
executeOk_servald rhizome list
assert_rhizome_list file1
executeOk_servald rhizome extract bundle $BID xfile1.manifest xfile1
assert diff xfile1.manifest file1.manifest
assert cmp file1 xfile1
}
doc_RhizomeInsertMissingManifest="HTTP RESTful insert missing 'manifest' form part"
setup_RhizomeInsertMissingManifest() {
setup
echo 'File one' >file1
}
test_RhizomeInsertMissingManifest() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'missing.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertIncorrectManifestType="HTTP RESTful insert incorrect 'manifest' content type"
setup_RhizomeInsertIncorrectManifestType() {
setup
echo 'File one' >file1
}
test_RhizomeInsertIncorrectManifestType() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=;type=rhizome-manifest/something" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'unsupported content-type.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertDuplicateManifest="HTTP RESTful insert duplicate 'manifest' form part"
setup_RhizomeInsertDuplicateManifest() {
setup
echo 'File one' >file1
echo 'name=wah' >file1.manifest
echo 'name=bee' >file2.manifest
}
test_RhizomeInsertDuplicateManifest() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
--form "manifest=@file2.manifest;type=rhizome-manifest/text" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'duplicate.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertJournal="HTTP RESTful insert does not support journals"
setup_RhizomeInsertJournal() {
setup
echo 'File one' >file1
echo 'tail=0' >file1.manifest
}
test_RhizomeInsertJournal() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'not supported.*journal'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertMissingPayload="HTTP RESTful insert missing 'payload' form part"
setup_RhizomeInsertMissingPayload() {
setup
echo 'File one' >file1
echo 'name=wah' >file1.manifest
}
test_RhizomeInsertMissingPayload() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'missing.*payload.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertDuplicatePayload="HTTP RESTful insert duplicate 'payload' form part"
setup_RhizomeInsertDuplicatePayload() {
setup
echo 'File one' >file1
echo 'File two' >file2
echo 'name=wah' >file1.manifest
}
test_RhizomeInsertDuplicatePayload() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
--form "payload=@file1" \
--form "payload=@file2" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'duplicate.*payload.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertPartOrder="HTTP RESTful insert 'payload' form part before 'manifest'"
setup_RhizomeInsertPartOrder() {
setup
echo 'File one' >file1
echo 'name=wah' >file1.manifest
}
test_RhizomeInsertPartOrder() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "payload=@file1" \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'missing.*manifest.*form.*part'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertPartUnsupported="HTTP RESTful insert unsupported form part"
setup_RhizomeInsertPartUnsupported() {
setup
echo 'File one' >file1
echo 'name=wah' >file1.manifest
}
test_RhizomeInsertPartUnsupported() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
--form "payload=@file1" \
--form "happyhappy=joyjoy" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'unsupported.*form.*part'
assertGrep http.body 'happyhappy'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertIncorrectFilesize="HTTP RESTful insert with incorrect filesize"
setup_RhizomeInsertIncorrectFilesize() {
setup
echo 'File one' >file1
echo 'filesize=6' >file1.manifest
echo 'File two' >file2
echo 'filesize=100' >file2.manifest
}
test_RhizomeInsertIncorrectFilesize() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'payload size.*contradicts manifest'
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file2.manifest;type=rhizome-manifest/text" \
--form "payload=@file2" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'payload size.*contradicts manifest'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_RhizomeInsertIncorrectFilehash="HTTP RESTful insert with incorrect filehash"
setup_RhizomeInsertIncorrectFilehash() {
setup
echo 'File one' >file1
echo 'filehash=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' >file1.manifest
}
test_RhizomeInsertIncorrectFilehash() {
execute curl \
--silent --show-error --write-out '%{http_code}' \
--output http.body \
--dump-header http.header \
--basic --user harry:potter \
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
--form "payload=@file1" \
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
tfw_cat http.header http.body
assertExitStatus == 0
assertStdoutIs 403
assertGrep --ignore-case http.body 'payload hash.*contradicts manifest'
executeOk_servald rhizome list
assert_rhizome_list
}
doc_MeshmsListConversations="HTTP RESTful list MeshMS conversations as JSON"