mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-18 12:56:29 +00:00
1247 lines
48 KiB
C
1247 lines
48 KiB
C
/*
|
|
Serval DNA Rhizome HTTP RESTful interface
|
|
Copyright (C) 2013-2015 Serval Project Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "serval.h"
|
|
#include "rhizome.h"
|
|
#include "conf.h"
|
|
#include "httpd.h"
|
|
#include "str.h"
|
|
#include "base64.h"
|
|
#include "strbuf_helpers.h"
|
|
#include "numeric_str.h"
|
|
#include "debug.h"
|
|
|
|
DEFINE_FEATURE(http_rest_rhizome);
|
|
|
|
DECLARE_HANDLER("/restful/rhizome/bundlelist.json", restful_rhizome_bundlelist_json);
|
|
DECLARE_HANDLER("/restful/rhizome/newsince/", restful_rhizome_newsince);
|
|
DECLARE_HANDLER("/restful/rhizome/insert", restful_rhizome_insert);
|
|
DECLARE_HANDLER("/restful/rhizome/import", restful_rhizome_import);
|
|
DECLARE_HANDLER("/restful/rhizome/append", restful_rhizome_append);
|
|
DECLARE_HANDLER("/restful/rhizome/", restful_rhizome_);
|
|
|
|
static HTTP_RENDERER render_status_and_manifest_headers;
|
|
static HTTP_RENDERER render_status_and_import_headers;
|
|
|
|
static void on_rhizome_bundle_added(httpd_request *r, rhizome_manifest *m);
|
|
|
|
static void finalise_union_read_state(httpd_request *r)
|
|
{
|
|
rhizome_read_close(&r->u.read_state);
|
|
}
|
|
|
|
static void finalise_union_rhizome_insert(httpd_request *r)
|
|
{
|
|
form_buf_malloc_release(&r->u.insert.manifest);
|
|
if (r->u.insert.write.blob_fd != -1)
|
|
rhizome_fail_write(&r->u.insert.write);
|
|
}
|
|
|
|
static void finalise_union_rhizome_list(httpd_request *r)
|
|
{
|
|
if (r->u.rhlist.cursor.service)
|
|
free((void*)r->u.rhlist.cursor.service);
|
|
r->u.rhlist.cursor.service=NULL;
|
|
if (r->u.rhlist.cursor.name)
|
|
free((void*)r->u.rhlist.cursor.name);
|
|
r->u.rhlist.cursor.name=NULL;
|
|
}
|
|
|
|
#define LIST_TOKEN_STRLEN (BASE64_ENCODED_LEN(sizeof(serval_uuid_t) + 8))
|
|
#define alloca_list_token(rowid) list_token_to_str(alloca(LIST_TOKEN_STRLEN + 1), (rowid))
|
|
|
|
static char *list_token_to_str(char *buf, uint64_t rowid)
|
|
{
|
|
struct iovec iov[2];
|
|
iov[0].iov_base = rhizome_database.uuid.u.binary;
|
|
iov[0].iov_len = sizeof rhizome_database.uuid.u.binary;
|
|
iov[1].iov_base = &rowid;
|
|
iov[1].iov_len = sizeof rowid;
|
|
size_t n = base64url_encodev(buf, iov, 2);
|
|
assert(n == LIST_TOKEN_STRLEN);
|
|
buf[n] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
static int strn_to_list_token(const char *str, uint64_t *rowidp, const char **afterp)
|
|
{
|
|
unsigned char token[sizeof rhizome_database.uuid.u.binary + sizeof *rowidp];
|
|
if (base64url_decode(token, sizeof token, str, 0, afterp, 0, NULL) == sizeof token
|
|
&& cmp_serval_uuid_t(&rhizome_database.uuid, (serval_uuid_t *) &token) == 0
|
|
&& **afterp=='/'){
|
|
memcpy(rowidp, token + sizeof rhizome_database.uuid.u.binary, sizeof *rowidp);
|
|
(*afterp)++;
|
|
}else{
|
|
// don't skip the token
|
|
*afterp=str;
|
|
*rowidp=1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int http_request_rhizome_response(struct httpd_request *r, uint16_t http_status, const char *http_reason)
|
|
{
|
|
uint16_t rhizome_http_status = 0;
|
|
switch (r->bundle_result.status) {
|
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
|
rhizome_http_status = 200; // OK
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
rhizome_http_status = 201; // Created
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
|
case RHIZOME_BUNDLE_STATUS_OLD:
|
|
rhizome_http_status = 202; // Accepted
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_FAKE:
|
|
case RHIZOME_BUNDLE_STATUS_READONLY:
|
|
rhizome_http_status = 419; // Authentication Timeout
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_INVALID:
|
|
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
|
|
case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG:
|
|
rhizome_http_status = 422; // Unprocessable Entity
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_BUSY:
|
|
rhizome_http_status = 423; // Locked
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
rhizome_http_status = 500;
|
|
break;
|
|
}
|
|
if (rhizome_http_status) {
|
|
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_result.status;
|
|
const char *status_message = rhizome_bundle_result_message(r->bundle_result);
|
|
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_http_status > http_status) {
|
|
http_status = rhizome_http_status;
|
|
if (!http_reason)
|
|
http_reason = status_message;
|
|
}
|
|
}
|
|
rhizome_http_status = 0;
|
|
switch (r->payload_status) {
|
|
case RHIZOME_PAYLOAD_STATUS_STORED:
|
|
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
|
rhizome_http_status = 200;
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_NEW:
|
|
rhizome_http_status = 201;
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
|
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
|
rhizome_http_status = 202; // Accepted
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
|
rhizome_http_status = 419; // Authentication Timeout
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
|
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
|
rhizome_http_status = 422; // Unprocessable Entity
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_BUSY:
|
|
rhizome_http_status = 423; // Locked
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
|
rhizome_http_status = 500;
|
|
break;
|
|
}
|
|
if (rhizome_http_status) {
|
|
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 = 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_http_status > http_status) {
|
|
http_status = rhizome_http_status;
|
|
if (!http_reason)
|
|
http_reason = status_message;
|
|
}
|
|
}
|
|
if (http_status == 0) {
|
|
http_status = 500;
|
|
http_reason = "Invalid result";
|
|
}
|
|
http_request_simple_response(&r->http, http_status, http_reason);
|
|
return http_status;
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR restful_rhizome_bundlelist_json_content;
|
|
|
|
static int restful_open_cursor(httpd_request *r)
|
|
{
|
|
assert(r->finalise_union == NULL);
|
|
r->finalise_union = finalise_union_rhizome_list;
|
|
r->u.rhlist.phase = LIST_HEADER;
|
|
r->u.rhlist.rowcount = 0;
|
|
|
|
const char *service = http_request_get_query_param(&r->http, "service");
|
|
if (service && *service){
|
|
r->u.rhlist.cursor.service = str_edup(service);
|
|
// TODO fail?
|
|
}
|
|
|
|
const char *name = http_request_get_query_param(&r->http, "name");
|
|
if (name && *name){
|
|
r->u.rhlist.cursor.name = str_edup(name);
|
|
// TODO fail?
|
|
}
|
|
|
|
const char *sender = http_request_get_query_param(&r->http, "sender");
|
|
if (sender && *sender){
|
|
if (str_to_sid_t(&r->u.rhlist.cursor.sender, sender) != -1)
|
|
r->u.rhlist.cursor.is_sender_set = 1;
|
|
// TODO fail?
|
|
}
|
|
|
|
const char *recipient = http_request_get_query_param(&r->http, "recipient");
|
|
if (recipient && *recipient){
|
|
if (str_to_sid_t(&r->u.rhlist.cursor.recipient, recipient) != -1)
|
|
r->u.rhlist.cursor.is_recipient_set = 1;
|
|
// TODO fail?
|
|
}
|
|
|
|
int ret = rhizome_list_open(&r->u.rhlist.cursor);
|
|
if (ret == -1)
|
|
return http_request_rhizome_response(r, 500, "Failed to open list");
|
|
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_JSON, restful_rhizome_bundlelist_json_content);
|
|
return 1;
|
|
}
|
|
|
|
static int restful_rhizome_bundlelist_json(httpd_request *r, const char *remainder)
|
|
{
|
|
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
|
|
if (!is_rhizome_http_enabled())
|
|
return 404;
|
|
int ret = authorize_restful(&r->http);
|
|
if (ret)
|
|
return ret;
|
|
if (*remainder)
|
|
return 404;
|
|
if (r->http.verb != HTTP_VERB_GET)
|
|
return 405;
|
|
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
|
|
return restful_open_cursor(r);
|
|
}
|
|
|
|
static HTTP_CONTENT_GENERATOR_STRBUF_CHUNKER restful_rhizome_bundlelist_json_content_chunk;
|
|
|
|
static int restful_rhizome_bundlelist_json_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
int ret = generate_http_content_from_strbuf_chunks(hr, (char *)buf, bufsz, result, restful_rhizome_bundlelist_json_content_chunk);
|
|
rhizome_list_release(&r->u.rhlist.cursor);
|
|
return ret;
|
|
}
|
|
|
|
static int restful_rhizome_newsince(httpd_request *r, const char *remainder)
|
|
{
|
|
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
|
|
if (!is_rhizome_http_enabled())
|
|
return 404;
|
|
int ret = authorize_restful(&r->http);
|
|
if (ret)
|
|
return ret;
|
|
uint64_t rowid;
|
|
const char *end = NULL;
|
|
if (!strn_to_list_token(remainder, &rowid, &end) || strcmp(end, "bundlelist.json") != 0)
|
|
return 404;
|
|
if (r->http.verb != HTTP_VERB_GET)
|
|
return 405;
|
|
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
|
|
r->u.rhlist.cursor.rowid_since = rowid;
|
|
r->u.rhlist.cursor.oldest_first = 1;
|
|
r->u.rhlist.end_time = gettime_ms() + config.api.restful.newsince_timeout * 1000;
|
|
r->trigger_rhizome_bundle_added = on_rhizome_bundle_added;
|
|
return restful_open_cursor(r);
|
|
}
|
|
|
|
static void on_rhizome_bundle_added(httpd_request *r, rhizome_manifest *UNUSED(m))
|
|
{
|
|
http_request_resume_response(&r->http);
|
|
}
|
|
|
|
static int restful_rhizome_bundlelist_json_content_chunk(struct http_request *hr, strbuf b)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
const char *headers[] = {
|
|
".token",
|
|
"_id",
|
|
"service",
|
|
"id",
|
|
"version",
|
|
"date",
|
|
".inserttime",
|
|
".author",
|
|
".fromhere",
|
|
"filesize",
|
|
"filehash",
|
|
"sender",
|
|
"recipient",
|
|
"name"
|
|
};
|
|
switch (r->u.rhlist.phase) {
|
|
case LIST_HEADER:
|
|
strbuf_puts(b, "{\n\"header\":[");
|
|
unsigned i;
|
|
for (i = 0; i != NELS(headers); ++i) {
|
|
if (i)
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, headers[i]);
|
|
}
|
|
strbuf_puts(b, "],\n\"rows\":[");
|
|
if (!strbuf_overrun(b))
|
|
r->u.rhlist.phase = LIST_ROWS;
|
|
return 1;
|
|
case LIST_FIRST:
|
|
case LIST_ROWS:
|
|
{
|
|
int ret = rhizome_list_next(&r->u.rhlist.cursor);
|
|
if (ret == -1)
|
|
return -1;
|
|
if (ret == 0) {
|
|
time_ms_t now;
|
|
if (r->u.rhlist.cursor.oldest_first == 0 || (now = gettime_ms()) >= r->u.rhlist.end_time) {
|
|
r->u.rhlist.phase = LIST_END;
|
|
return 1;
|
|
}
|
|
http_request_pause_response(&r->http, r->u.rhlist.end_time);
|
|
return 0;
|
|
}
|
|
rhizome_manifest *m = r->u.rhlist.cursor.manifest;
|
|
assert(m->filesize != RHIZOME_SIZE_UNSET);
|
|
rhizome_lookup_author(m);
|
|
if (r->u.rhlist.rowcount != 0)
|
|
strbuf_putc(b, ',');
|
|
strbuf_puts(b, "\n[");
|
|
if (m->rowid > r->u.rhlist.rowid_highest) {
|
|
strbuf_json_string(b, alloca_list_token(m->rowid));
|
|
r->u.rhlist.rowid_highest = m->rowid;
|
|
} else
|
|
strbuf_json_null(b);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, m->rowid);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, m->service);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, m->keypair.public_key.binary, sizeof m->keypair.public_key.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, m->version);
|
|
strbuf_putc(b, ',');
|
|
if (m->has_date)
|
|
strbuf_sprintf(b, "%"PRItime_ms_t, m->date);
|
|
else
|
|
strbuf_json_null(b);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRItime_ms_t",", m->inserttime);
|
|
// The 'fromhere' flag indicates if the author is a known (unlocked) identity in the local
|
|
// keyring. The values are 0 (no), 1 (yes), 2 (yes and cryptographically verified). In the
|
|
// implementation below, the 0 value (no) is redundant, because it only occurs when the
|
|
// 'author' column is null, but in future the author SID might be reported for non-local
|
|
// authors, so clients should only use 'fromhere != 0', never 'author != null', to detect
|
|
// local authorship.
|
|
int fromhere = 0;
|
|
switch (m->authorship) {
|
|
case AUTHOR_AUTHENTIC:
|
|
fromhere = 2;
|
|
strbuf_json_hex(b, m->author.binary, sizeof m->author.binary);
|
|
break;
|
|
case AUTHOR_LOCAL:
|
|
fromhere = 1;
|
|
strbuf_json_hex(b, m->author.binary, sizeof m->author.binary);
|
|
break;
|
|
case AUTHOR_REMOTE:
|
|
strbuf_json_hex(b, m->author.binary, sizeof m->author.binary);
|
|
break;
|
|
default:
|
|
strbuf_json_null(b);
|
|
break;
|
|
}
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%d", fromhere);
|
|
strbuf_putc(b, ',');
|
|
strbuf_sprintf(b, "%"PRIu64, m->filesize);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, m->filesize ? m->filehash.binary : NULL, sizeof m->filehash.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, m->has_sender ? m->sender.binary : NULL, sizeof m->sender.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_hex(b, m->has_recipient ? m->recipient.binary : NULL, sizeof m->recipient.binary);
|
|
strbuf_putc(b, ',');
|
|
strbuf_json_string(b, m->name);
|
|
strbuf_puts(b, "]");
|
|
if (!strbuf_overrun(b)) {
|
|
rhizome_list_commit(&r->u.rhlist.cursor);
|
|
++r->u.rhlist.rowcount;
|
|
}
|
|
}
|
|
return 1;
|
|
case LIST_END:
|
|
strbuf_puts(b, "\n]\n}\n");
|
|
if (!strbuf_overrun(b))
|
|
r->u.rhlist.phase = LIST_DONE;
|
|
// fall through...
|
|
case LIST_DONE:
|
|
return 0;
|
|
}
|
|
abort();
|
|
}
|
|
|
|
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(httpd_request *r, const char *remainder)
|
|
{
|
|
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
|
|
r->http.render_extra_headers = render_status_and_manifest_headers;
|
|
if (!is_rhizome_http_enabled())
|
|
return 404;
|
|
int ret = authorize_restful(&r->http);
|
|
if (ret)
|
|
return ret;
|
|
if (*remainder)
|
|
return 404;
|
|
if (r->http.verb != HTTP_VERB_POST)
|
|
return 405;
|
|
// 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_bundleid);
|
|
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 int restful_rhizome_import(httpd_request *r, const char *remainder)
|
|
{
|
|
// Do this first to check for request enabled, authorization, and POST.
|
|
r->u.insert.importing = 1;
|
|
int ret;
|
|
if ((ret = restful_rhizome_insert(r, remainder)) != 1)
|
|
return ret;
|
|
r->http.render_extra_headers = render_status_and_import_headers;
|
|
|
|
// If the client provides the Bundle ID and version on the command line, we can potentially reject
|
|
// the request immediately, to save the client sending the full manifest and payload.
|
|
#define PARAM_ID "id"
|
|
#define PARAM_VERSION "version"
|
|
const char *q_id = http_request_get_query_param(&r->http, PARAM_ID);
|
|
const char *q_version = http_request_get_query_param(&r->http, PARAM_VERSION);
|
|
if (q_id && !q_version)
|
|
return http_request_rhizome_response(r, 400, "Missing \"" PARAM_VERSION "\" parameter");
|
|
if (!q_id && q_version)
|
|
return http_request_rhizome_response(r, 400, "Missing \"" PARAM_ID "\" parameter");
|
|
if (q_id && str_to_rhizome_bid_t(&r->bid, q_id) == -1)
|
|
return http_request_rhizome_response(r, 400, "Invalid \"" PARAM_ID "\" parameter");
|
|
if (q_version && !str_to_uint64(q_version, 10, &r->ui64, NULL))
|
|
return http_request_rhizome_response(r, 400, "Invalid \"" PARAM_VERSION "\" parameter");
|
|
#undef PARAM_ID
|
|
#undef PARAM_VERSION
|
|
if (q_id && q_version) {
|
|
r->u.insert.verify_id_version = 1; // must agree with manifest
|
|
r->u.insert.payload_size = RHIZOME_SIZE_UNSET;
|
|
r->bundle_result.status = rhizome_is_interesting(&r->bid, r->ui64, &r->u.insert.payload_size);
|
|
switch (r->bundle_result.status) {
|
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
|
r->payload_status = r->u.insert.payload_size ? RHIZOME_PAYLOAD_STATUS_STORED
|
|
: RHIZOME_PAYLOAD_STATUS_EMPTY;
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
r->payload_status = RHIZOME_PAYLOAD_STATUS_ERROR;
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
case RHIZOME_BUNDLE_STATUS_BUSY:
|
|
case RHIZOME_BUNDLE_STATUS_OLD:
|
|
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
|
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
|
|
case RHIZOME_BUNDLE_STATUS_INVALID:
|
|
case RHIZOME_BUNDLE_STATUS_FAKE:
|
|
case RHIZOME_BUNDLE_STATUS_READONLY:
|
|
case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int restful_rhizome_append(httpd_request *r, const char *remainder)
|
|
{
|
|
r->u.insert.appending = 1;
|
|
return restful_rhizome_insert(r, remainder);
|
|
}
|
|
|
|
static char PART_MANIFEST[] = "manifest";
|
|
static char PART_PAYLOAD[] = "payload";
|
|
static char PART_BUNDLEID[] = "bundle-id";
|
|
static char PART_AUTHOR[] = "bundle-author";
|
|
static char PART_SECRET[] = "bundle-secret";
|
|
|
|
static int insert_mime_part_start(struct http_request *hr)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
assert(r->u.insert.current_part == NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int insert_make_manifest(httpd_request *r)
|
|
{
|
|
if (!r->u.insert.received_manifest)
|
|
return http_response_form_part(r, 400, "Missing", PART_MANIFEST, NULL, 0);
|
|
if ((r->manifest = rhizome_new_manifest()) == NULL)
|
|
return http_request_rhizome_response(r, 429, "Manifest table full"); // Too Many Requests
|
|
assert(r->u.insert.manifest.length <= sizeof r->manifest->manifestdata);
|
|
memcpy(r->manifest->manifestdata, r->u.insert.manifest.buffer, r->u.insert.manifest.length);
|
|
r->manifest->manifest_all_bytes = r->u.insert.manifest.length;
|
|
int n = rhizome_manifest_parse(r->manifest);
|
|
switch (n) {
|
|
case 0:
|
|
if (r->manifest->malformed) {
|
|
r->bundle_result = rhizome_bundle_result_sprintf(RHIZOME_BUNDLE_STATUS_INVALID, "Malformed manifest: %s", r->manifest->malformed);
|
|
break;
|
|
}
|
|
|
|
if (r->u.insert.importing) {
|
|
const char *invalid_reason;
|
|
if ((invalid_reason = rhizome_manifest_validate_reason(r->manifest))) {
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, invalid_reason);
|
|
break;
|
|
}
|
|
assert(r->manifest->has_id);
|
|
assert(r->manifest->version != 0);
|
|
if (r->u.insert.verify_id_version && (
|
|
cmp_rhizome_bid_t(&r->manifest->keypair.public_key, &r->bid) != 0 ||
|
|
r->manifest->version != r->ui64)
|
|
) {
|
|
r->bundle_result = rhizome_bundle_result_static(
|
|
RHIZOME_BUNDLE_STATUS_INVALID,
|
|
"Manifest does not match id/version query parameters"
|
|
);
|
|
break;
|
|
}
|
|
if (!rhizome_manifest_verify(r->manifest)) {
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_FAKE, NULL);
|
|
break;
|
|
}
|
|
r->bundle_result.status = rhizome_manifest_check_stored(r->manifest, NULL);
|
|
break;
|
|
}
|
|
|
|
rhizome_manifest *mout = NULL;
|
|
r->bundle_result = rhizome_manifest_add_file(r->u.insert.appending, r->manifest, &mout,
|
|
r->u.insert.received_bundleid ? &r->bid : NULL,
|
|
r->u.insert.received_secret ? &r->u.insert.bundle_secret : NULL,
|
|
r->u.insert.received_author ? &r->u.insert.author : NULL,
|
|
NULL, 0, NULL);
|
|
if (mout && mout != r->manifest) {
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = mout;
|
|
}
|
|
break;
|
|
case 1:
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, NULL);
|
|
break;
|
|
default:
|
|
WHYF("rhizome_manifest_parse() returned %d", n);
|
|
FALLTHROUGH; // fall through
|
|
case -1:
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_ERROR, "Internal error while parsing manifest");
|
|
break;
|
|
}
|
|
|
|
switch (r->bundle_result.status) {
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_OLD:
|
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
|
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
|
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
|
|
case RHIZOME_BUNDLE_STATUS_INVALID:
|
|
case RHIZOME_BUNDLE_STATUS_BUSY:
|
|
case RHIZOME_BUNDLE_STATUS_FAKE:
|
|
case RHIZOME_BUNDLE_STATUS_READONLY:
|
|
case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG:
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
}
|
|
assert(r->manifest != NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int insert_mime_part_header(struct http_request *hr, const struct mime_part_headers *h)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
if (!h->content_disposition.type[0])
|
|
return http_response_content_disposition(r, 415, "Missing", h->content_disposition.type);
|
|
if (strcmp(h->content_disposition.type, "form-data") != 0)
|
|
return http_response_content_disposition(r, 415, "Unsupported", h->content_disposition.type);
|
|
|
|
if (!r->u.insert.importing && strcmp(h->content_disposition.name, PART_AUTHOR) == 0) {
|
|
if (r->u.insert.received_author)
|
|
return http_response_form_part(r, 400, "Duplicate", PART_AUTHOR, NULL, 0);
|
|
// Reject a request if this parameter comes after the manifest part.
|
|
if (r->u.insert.received_manifest || r->u.insert.importing)
|
|
return http_response_form_part(r, 400, "Spurious", PART_AUTHOR, NULL, 0);
|
|
if (!h->content_type.type[0])
|
|
; // missing Content-Type defaults to CONTENT_TYPE_SID_HEX
|
|
// TODO deprecate this default and insist that Content-Type be supplied
|
|
else if (!mime_content_types_are_equal(&h->content_type, &CONTENT_TYPE_SID_HEX))
|
|
return http_response_form_part(r, 415, "Unsupported Content-Type in", PART_AUTHOR, NULL, 0);
|
|
r->u.insert.current_part = PART_AUTHOR;
|
|
assert(r->u.insert.author_hex_len == 0);
|
|
}
|
|
else if (!r->u.insert.importing && strcmp(h->content_disposition.name, PART_SECRET) == 0) {
|
|
if (r->u.insert.received_secret)
|
|
return http_response_form_part(r, 400, "Duplicate", PART_SECRET, NULL, 0);
|
|
// Reject a request if this parameter comes after the manifest part.
|
|
if (r->u.insert.received_manifest || r->u.insert.importing)
|
|
return http_response_form_part(r, 400, "Spurious", PART_SECRET, NULL, 0);
|
|
if (!h->content_type.type[0])
|
|
; // missing Content-Type defaults to CONTENT_TYPE_RHIZOME_BUNDLE_SECRET
|
|
// TODO deprecate this default and insist that Content-Type be supplied
|
|
else if (!mime_content_types_are_equal(&h->content_type, &CONTENT_TYPE_RHIZOME_BUNDLE_SECRET_HEX))
|
|
return http_response_form_part(r, 415, "Unsupported Content-Type in", PART_SECRET, NULL, 0);
|
|
r->u.insert.current_part = PART_SECRET;
|
|
assert(r->u.insert.secret_text_len == 0);
|
|
}
|
|
else if (!r->u.insert.importing && strcmp(h->content_disposition.name, PART_BUNDLEID) == 0) {
|
|
if (r->u.insert.received_bundleid)
|
|
return http_response_form_part(r, 400, "Duplicate", PART_BUNDLEID, NULL, 0);
|
|
// Reject a request if this parameter comes after the manifest part.
|
|
if (r->u.insert.received_manifest || r->u.insert.importing)
|
|
return http_response_form_part(r, 400, "Spurious", PART_BUNDLEID, NULL, 0);
|
|
if (!h->content_type.type[0])
|
|
; // missing Content-Type defaults to CONTENT_TYPE_RHIZOME_BUNDLE_ID
|
|
// TODO deprecate this default and insist that Content-Type be supplied
|
|
else if (!mime_content_types_are_equal(&h->content_type, &CONTENT_TYPE_RHIZOME_BUNDLE_ID_HEX))
|
|
return http_response_form_part(r, 415, "Unsupported Content-Type in", PART_BUNDLEID, NULL, 0);
|
|
r->u.insert.current_part = PART_BUNDLEID;
|
|
assert(r->u.insert.bid_text_len == 0);
|
|
}
|
|
else if (strcmp(h->content_disposition.name, PART_MANIFEST) == 0) {
|
|
// Reject a request if it has a repeated manifest part.
|
|
if (r->u.insert.received_manifest)
|
|
return http_response_form_part(r, 400, "Duplicate", PART_MANIFEST, NULL, 0);
|
|
form_buf_malloc_init(&r->u.insert.manifest, MAX_MANIFEST_BYTES);
|
|
if (!mime_content_types_are_equal(&h->content_type, &CONTENT_TYPE_RHIZOME_MANIFEST))
|
|
return http_response_form_part(r, 415, "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, 400, "Duplicate", PART_PAYLOAD, NULL, 0);
|
|
// Reject a request if it has a missing manifest part preceding the payload part.
|
|
if (!r->u.insert.received_manifest)
|
|
return http_response_form_part(r, 400, "Missing", PART_MANIFEST, NULL, 0);
|
|
assert(r->manifest != NULL);
|
|
if (r->u.insert.importing && (r->manifest->filesize == 0 || !r->manifest->has_filehash))
|
|
return http_response_form_part(r, 400, "Spurious", PART_PAYLOAD, NULL, 0);
|
|
|
|
// TODO enforce correct content type
|
|
r->u.insert.current_part = PART_PAYLOAD;
|
|
|
|
if (r->u.insert.importing){
|
|
r->payload_status = rhizome_open_write(&r->u.insert.write, &r->manifest->filehash, r->manifest->filesize);
|
|
}else{
|
|
// 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.
|
|
if (r->u.insert.appending) {
|
|
r->payload_status = rhizome_write_open_journal(&r->u.insert.write, r->manifest, 0, RHIZOME_SIZE_UNSET);
|
|
} else {
|
|
// Note: r->manifest->filesize can be RHIZOME_SIZE_UNSET at this point, if the manifest did
|
|
// not contain a 'filesize' field.
|
|
r->payload_status = rhizome_write_open_manifest(&r->u.insert.write, r->manifest);
|
|
}
|
|
}
|
|
// complete early so the client doesn't have to send the payload
|
|
if (r->payload_status != RHIZOME_PAYLOAD_STATUS_NEW)
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
|
|
r->u.insert.payload_size = 0;
|
|
}
|
|
else
|
|
return http_response_form_part(r, 400, "Unsupported", h->content_disposition.name, NULL, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int insert_mime_part_body(struct http_request *hr, char *buf, size_t len)
|
|
{
|
|
httpd_request *r = (httpd_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_text,
|
|
sizeof r->u.insert.secret_text,
|
|
&r->u.insert.secret_text_len,
|
|
buf, len);
|
|
}
|
|
else if (r->u.insert.current_part == PART_BUNDLEID) {
|
|
accumulate_text(r, PART_BUNDLEID,
|
|
r->u.insert.bid_text,
|
|
sizeof r->u.insert.bid_text,
|
|
&r->u.insert.bid_text_len,
|
|
buf, len);
|
|
}
|
|
else if (r->u.insert.current_part == PART_MANIFEST) {
|
|
form_buf_malloc_accumulate(r, PART_MANIFEST, &r->u.insert.manifest, buf, len);
|
|
}
|
|
else if (r->u.insert.current_part == PART_PAYLOAD) {
|
|
r->u.insert.payload_size += len;
|
|
switch (r->payload_status) {
|
|
case RHIZOME_PAYLOAD_STATUS_NEW:
|
|
if (rhizome_write_buffer(&r->u.insert.write, (unsigned char *)buf, len) == -1)
|
|
return http_request_rhizome_response(r, 500, "Error in payload write");
|
|
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)
|
|
{
|
|
httpd_request *r = (httpd_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, sizeof r->u.insert.author_hex) == -1
|
|
)
|
|
return http_response_form_part(r, 400, "Invalid", PART_AUTHOR, r->u.insert.author_hex, r->u.insert.author_hex_len);
|
|
r->u.insert.received_author = 1;
|
|
DEBUGF(rhizome, "received %s = %s", PART_AUTHOR, alloca_tohex_sid_t(r->u.insert.author));
|
|
}
|
|
else if (r->u.insert.current_part == PART_SECRET) {
|
|
if (strn_to_rhizome_bsk_t(&r->u.insert.bundle_secret, r->u.insert.secret_text, r->u.insert.secret_text_len) == -1)
|
|
return http_response_form_part(r, 400, "Invalid", PART_SECRET, r->u.insert.secret_text, r->u.insert.secret_text_len);
|
|
r->u.insert.received_secret = 1;
|
|
DEBUGF(rhizome, "received %s = %s", PART_SECRET, alloca_tohex_rhizome_bk_t(r->u.insert.bundle_secret));
|
|
}
|
|
else if (r->u.insert.current_part == PART_BUNDLEID) {
|
|
if (strn_to_rhizome_bid_t(&r->bid, r->u.insert.bid_text, r->u.insert.bid_text_len) == -1)
|
|
return http_response_form_part(r, 400, "Invalid", PART_BUNDLEID, r->u.insert.secret_text, r->u.insert.secret_text_len);
|
|
r->u.insert.received_bundleid = 1;
|
|
DEBUGF(rhizome, "received %s = %s", PART_BUNDLEID, alloca_tohex_rhizome_bid_t(r->bid));
|
|
}
|
|
else if (r->u.insert.current_part == PART_MANIFEST) {
|
|
r->u.insert.received_manifest = 1;
|
|
DEBUGF(rhizome, "received %s = %s", PART_MANIFEST, alloca_toprint(-1, r->u.insert.manifest.buffer, r->u.insert.manifest.length));
|
|
int result = insert_make_manifest(r);
|
|
if (result)
|
|
return result;
|
|
}
|
|
else if (r->u.insert.current_part == PART_PAYLOAD) {
|
|
r->u.insert.received_payload = 1;
|
|
DEBUGF(rhizome, "received %s, %zd bytes", PART_PAYLOAD, r->u.insert.payload_size);
|
|
r->payload_status = rhizome_finish_write(&r->u.insert.write);
|
|
} 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)
|
|
{
|
|
httpd_request *r = (httpd_request *) hr;
|
|
if (!r->u.insert.received_manifest)
|
|
return http_response_form_part(r, 400, "Missing", PART_MANIFEST, NULL, 0);
|
|
if (!r->u.insert.received_payload)
|
|
return http_response_form_part(r, 400, "Missing", PART_PAYLOAD, NULL, 0);
|
|
// Fill in the missing manifest fields and ensure payload and manifest are consistent.
|
|
assert(r->manifest != NULL);
|
|
DEBUGF(rhizome, "r->payload_status=%d %s", r->payload_status, rhizome_payload_status_message(r->payload_status));
|
|
assert(r->u.insert.write.file_length != RHIZOME_SIZE_UNSET);
|
|
if (r->u.insert.appending) {
|
|
// For journal appends, the user cannot supply a 'filesize' field. This will have been caught
|
|
// by previous logic. The manifest should also have a 'filesize' field by now. The new payload
|
|
// size should be the sum of 'filesize' and the appended portion.
|
|
assert(r->manifest->is_journal);
|
|
assert(r->manifest->filesize != RHIZOME_SIZE_UNSET);
|
|
DEBUGF(rhizome, "file_length=%"PRIu64" filesize=%"PRIu64" payload_size=%"PRIu64,
|
|
r->u.insert.write.file_length,
|
|
r->manifest->filesize,
|
|
r->u.insert.payload_size);
|
|
if (r->u.insert.write.file_length != r->manifest->filesize + r->u.insert.payload_size)
|
|
r->payload_status = RHIZOME_PAYLOAD_STATUS_WRONG_SIZE;
|
|
} else {
|
|
// The Rhizome CLI 'add file' operation allows the user to supply a 'filesize' field which is
|
|
// smaller than the supplied file, for convenience, to allow only the first part of a file to be
|
|
// added as a payload. But the RESTful interface doesn't allow that.
|
|
assert(!r->manifest->is_journal);
|
|
if (r->manifest->filesize != RHIZOME_SIZE_UNSET && r->u.insert.payload_size != r->manifest->filesize)
|
|
r->payload_status = RHIZOME_PAYLOAD_STATUS_WRONG_SIZE;
|
|
}
|
|
r->payload_status = rhizome_finish_store(&r->u.insert.write, r->manifest, r->payload_status);
|
|
int status_valid = 0;
|
|
switch (r->payload_status) {
|
|
case RHIZOME_PAYLOAD_STATUS_NEW:
|
|
case RHIZOME_PAYLOAD_STATUS_STORED:
|
|
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
|
status_valid = 1;
|
|
break;
|
|
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
|
r->bundle_result = rhizome_bundle_result_sprintf(RHIZOME_BUNDLE_STATUS_INCONSISTENT,
|
|
"Payload size (%"PRIu64") contradicts manifest (filesize=%"PRIu64")",
|
|
r->u.insert.payload_size, r->manifest->filesize);
|
|
return http_request_rhizome_response(r, 0, "Inconsistent filesize");
|
|
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
|
r->bundle_result = rhizome_bundle_result_sprintf(RHIZOME_BUNDLE_STATUS_INCONSISTENT,
|
|
"Payload hash (%s) contradicts manifest (filehash=%s)",
|
|
alloca_tohex_rhizome_filehash_t(r->u.insert.write.id),
|
|
alloca_tohex_rhizome_filehash_t(r->manifest->filehash));
|
|
return http_request_rhizome_response(r, 0, "Inconsistent filehash");
|
|
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
|
r->bundle_result = rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_READONLY);
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
|
r->bundle_result = rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_NO_ROOM);
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
|
r->bundle_result = rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_NO_ROOM);
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_PAYLOAD_STATUS_BUSY:
|
|
r->bundle_result = rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_BUSY);
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
|
return http_request_rhizome_response(r, 500, "Payload store error");
|
|
}
|
|
if (!status_valid)
|
|
FATALF("rhizome_finish_store() returned status = %d", r->payload_status);
|
|
|
|
rhizome_manifest *mout = NULL;
|
|
if (r->u.insert.importing){
|
|
r->bundle_result = rhizome_bundle_result(rhizome_add_manifest_to_store(r->manifest, &mout));
|
|
}else{
|
|
// Finalise the manifest and add it to the store.
|
|
const char *invalid_reason = rhizome_manifest_validate_reason(r->manifest);
|
|
if (invalid_reason) {
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, invalid_reason);
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
}
|
|
if (r->manifest->malformed) {
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, r->manifest->malformed);
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
}
|
|
if (!r->manifest->haveSecret) {
|
|
r->bundle_result = rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_READONLY, "Missing bundle secret");
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
}
|
|
rhizome_bundle_result_free(&r->bundle_result);
|
|
r->bundle_result = rhizome_manifest_finalise(r->manifest, &mout, !r->u.insert.force_new);
|
|
}
|
|
int http_status = 500;
|
|
switch (r->bundle_result.status) {
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
if (mout && mout != r->manifest)
|
|
rhizome_manifest_free(mout);
|
|
http_status = 201;
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_OLD:
|
|
if (mout && mout != r->manifest) {
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = mout;
|
|
}
|
|
http_status = 202;
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
|
if (mout && mout != r->manifest) {
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = mout;
|
|
}
|
|
http_status = 200;
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
case RHIZOME_BUNDLE_STATUS_INVALID:
|
|
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_BUSY:
|
|
case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG:
|
|
if (mout && mout != r->manifest)
|
|
rhizome_manifest_free(mout);
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = NULL;
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
}
|
|
if (http_status == 500)
|
|
FATALF("rhizome_manifest_finalise() returned status=%d", r->bundle_result.status);
|
|
if (r->u.insert.importing){
|
|
return http_request_rhizome_response(r, http_status, NULL);
|
|
}else{
|
|
rhizome_authenticate_author(r->manifest);
|
|
http_request_response_static(&r->http, http_status, &CONTENT_TYPE_RHIZOME_MANIFEST,
|
|
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
|
|
);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static HTTP_HANDLER restful_rhizome_bid_rhm;
|
|
static HTTP_HANDLER restful_rhizome_bid_raw_bin;
|
|
static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
|
|
|
|
static int restful_rhizome_(httpd_request *r, const char *remainder)
|
|
{
|
|
r->http.response.header.content_type = &CONTENT_TYPE_JSON;
|
|
r->http.render_extra_headers = render_status_and_manifest_headers;
|
|
if (!is_rhizome_http_enabled())
|
|
return 404;
|
|
int ret = authorize_restful(&r->http);
|
|
if (ret)
|
|
return ret;
|
|
HTTP_HANDLER *handler = NULL;
|
|
rhizome_bid_t bid;
|
|
const char *end;
|
|
if (parse_rhizome_bid_t(&bid, remainder, -1, &end) != -1) {
|
|
if (strcmp(end, ".rhm") == 0) {
|
|
handler = restful_rhizome_bid_rhm;
|
|
remainder = "";
|
|
} else if (strcmp(end, "/raw.bin") == 0) {
|
|
handler = restful_rhizome_bid_raw_bin;
|
|
remainder = "";
|
|
} else if (strcmp(end, "/decrypted.bin") == 0) {
|
|
handler = restful_rhizome_bid_decrypted_bin;
|
|
remainder = "";
|
|
}
|
|
}
|
|
if (handler == NULL)
|
|
return 404;
|
|
if (r->http.verb != HTTP_VERB_GET)
|
|
return 405;
|
|
if ((r->manifest = rhizome_new_manifest()) == NULL)
|
|
return http_request_rhizome_response(r, 429, "Manifest table full"); // Too Many Requests
|
|
r->bundle_result.status = rhizome_retrieve_manifest(&bid, r->manifest);
|
|
switch(r->bundle_result.status){
|
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
|
rhizome_authenticate_author(r->manifest);
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = NULL;
|
|
break;
|
|
case RHIZOME_BUNDLE_STATUS_BUSY:
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = NULL;
|
|
return http_request_rhizome_response(r, 0, NULL);
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
rhizome_manifest_free(r->manifest);
|
|
r->manifest = NULL;
|
|
return http_request_rhizome_response(r, 500, "Manifest retrieve error");
|
|
default: // should not return others
|
|
FATALF("rhizome_retrieve_manifest() returned status = %d", r->bundle_result.status);
|
|
}
|
|
return handler(r, remainder);
|
|
}
|
|
|
|
static int restful_rhizome_bid_rhm(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
if (r->manifest == NULL)
|
|
return http_request_rhizome_response(r, 404, "Bundle not found"); // Not Found
|
|
http_request_response_static(&r->http, 200, &CONTENT_TYPE_RHIZOME_MANIFEST,
|
|
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
if (r->manifest == NULL)
|
|
return http_request_rhizome_response(r, 404, "Bundle not found"); // Not Found
|
|
if (r->manifest->filesize == 0) {
|
|
http_request_response_static(&r->http, 200, &CONTENT_TYPE_BLOB, "", 0);
|
|
return 1;
|
|
}
|
|
int ret = rhizome_response_content_init_filehash(r, &r->manifest->filehash);
|
|
if (ret)
|
|
return ret;
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_BLOB, rhizome_payload_content);
|
|
return 1;
|
|
}
|
|
|
|
static int restful_rhizome_bid_decrypted_bin(httpd_request *r, const char *remainder)
|
|
{
|
|
if (*remainder)
|
|
return 404;
|
|
if (r->manifest == NULL)
|
|
return http_request_rhizome_response(r, 404, "Bundle not found"); // Not Found
|
|
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);
|
|
return 1;
|
|
}
|
|
int ret = rhizome_response_content_init_payload(r, r->manifest);
|
|
if (ret)
|
|
return ret;
|
|
// TODO use Content Type from manifest (once it is implemented)
|
|
http_request_response_generated(&r->http, 200, &CONTENT_TYPE_BLOB, rhizome_payload_content);
|
|
return 1;
|
|
}
|
|
|
|
static int rhizome_response_content_init_read_state(httpd_request *r)
|
|
{
|
|
if (r->u.read_state.length == RHIZOME_SIZE_UNSET && rhizome_read(&r->u.read_state, NULL, 0)) {
|
|
rhizome_read_close(&r->u.read_state);
|
|
return http_request_rhizome_response(r, 404, "Payload not found");
|
|
}
|
|
assert(r->u.read_state.length != RHIZOME_SIZE_UNSET);
|
|
int ret = http_response_init_content_range(r, r->u.read_state.length);
|
|
if (ret==0)
|
|
r->u.read_state.offset = r->http.response.header.content_range_start;
|
|
return ret;
|
|
}
|
|
|
|
int rhizome_response_content_init_filehash(httpd_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;
|
|
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:
|
|
return rhizome_response_content_init_read_state(r);
|
|
case RHIZOME_PAYLOAD_STATUS_NEW:
|
|
return http_request_rhizome_response(r, 404, "Payload not found");
|
|
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
|
case RHIZOME_PAYLOAD_STATUS_BUSY:
|
|
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 http_request_rhizome_response(r, 500, "Payload read error");
|
|
}
|
|
FATALF("rhizome_open_read() returned status = %d", r->payload_status);
|
|
}
|
|
|
|
int rhizome_response_content_init_payload(httpd_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;
|
|
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:
|
|
return rhizome_response_content_init_read_state(r);
|
|
case RHIZOME_PAYLOAD_STATUS_NEW:
|
|
return http_request_rhizome_response(r, 404, "Payload not found");
|
|
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
|
return http_request_rhizome_response(r, 419, "Payload decryption error"); // Authentication Timeout
|
|
case RHIZOME_PAYLOAD_STATUS_BUSY:
|
|
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
|
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
|
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
|
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
|
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
|
return http_request_rhizome_response(r, 500, "Payload read error");
|
|
}
|
|
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)
|
|
{
|
|
// Only read multiples of 4k from disk.
|
|
const size_t blocksz = 1 << 12;
|
|
// Ask for a large buffer for all future reads.
|
|
const size_t preferred_bufsz = 16 * blocksz;
|
|
// Reads the next part of the payload into the supplied buffer.
|
|
httpd_request *r = (httpd_request *) hr;
|
|
assert(r->u.read_state.length != RHIZOME_SIZE_UNSET);
|
|
assert(r->u.read_state.offset < r->u.read_state.length);
|
|
uint64_t remain = r->u.read_state.length - r->u.read_state.offset;
|
|
size_t readlen = bufsz;
|
|
if (remain <= bufsz)
|
|
readlen = remain;
|
|
else
|
|
readlen &= ~(blocksz - 1);
|
|
if (readlen > 0) {
|
|
ssize_t n = rhizome_read(&r->u.read_state, buf, readlen);
|
|
if (n == -1)
|
|
return -1;
|
|
result->generated = (size_t) n;
|
|
}
|
|
assert(r->u.read_state.offset <= r->u.read_state.length);
|
|
remain = r->u.read_state.length - r->u.read_state.offset;
|
|
result->need = remain < preferred_bufsz ? remain : preferred_bufsz;
|
|
return remain ? 1 : 0;
|
|
}
|
|
|
|
static void render_status_headers(const struct httpd_request *r, strbuf sb)
|
|
{
|
|
switch (r->bundle_result.status) {
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
case RHIZOME_BUNDLE_STATUS_SAME:
|
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
|
case RHIZOME_BUNDLE_STATUS_OLD:
|
|
case RHIZOME_BUNDLE_STATUS_INVALID:
|
|
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_BUSY:
|
|
case RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG:
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Result-Bundle-Status-Code: %d\r\n", r->bundle_result.status);
|
|
strbuf_puts(sb, "Serval-Rhizome-Result-Bundle-Status-Message: ");
|
|
strbuf_json_string(sb, rhizome_bundle_result_message_nonnull(r->bundle_result));
|
|
strbuf_puts(sb, "\r\n");
|
|
break;
|
|
}
|
|
const char *status_message = rhizome_payload_status_message(r->payload_status);
|
|
if (status_message) {
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Result-Payload-Status-Code: %d\r\n", r->payload_status);
|
|
strbuf_puts(sb, "Serval-Rhizome-Result-Payload-Status-Message: ");
|
|
strbuf_json_string(sb, status_message);
|
|
strbuf_puts(sb, "\r\n");
|
|
}
|
|
}
|
|
|
|
static void render_manifest_headers(const rhizome_manifest *m, strbuf sb)
|
|
{
|
|
assert(m != NULL);
|
|
if (m->has_id)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->keypair.public_key));
|
|
if (m->version)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", m->version);
|
|
if (m->filesize != RHIZOME_SIZE_UNSET)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize);
|
|
if (m->has_filehash)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filehash: %s\r\n", alloca_tohex_rhizome_filehash_t(m->filehash));
|
|
if (m->has_sender)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Sender: %s\r\n", alloca_tohex_sid_t(m->sender));
|
|
if (m->has_recipient)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Recipient: %s\r\n", alloca_tohex_sid_t(m->recipient));
|
|
if (m->has_bundle_key)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-BK: %s\r\n", alloca_tohex_rhizome_bk_t(m->bundle_key));
|
|
switch (m->payloadEncryption) {
|
|
case PAYLOAD_CRYPT_UNKNOWN:
|
|
break;
|
|
case PAYLOAD_CLEAR:
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 0\r\n");
|
|
break;
|
|
case PAYLOAD_ENCRYPTED:
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 1\r\n");
|
|
break;
|
|
}
|
|
if (m->is_journal)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Tail: %"PRIu64"\r\n", m->tail);
|
|
if (m->has_date)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Date: %"PRIu64"\r\n", m->date);
|
|
if (m->name) {
|
|
strbuf_puts(sb, "Serval-Rhizome-Bundle-Name: ");
|
|
strbuf_append_quoted_string(sb, m->name);
|
|
strbuf_puts(sb, "\r\n");
|
|
}
|
|
if (m->service)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Service: %s\r\n", m->service);
|
|
assert(m->authorship != AUTHOR_LOCAL);
|
|
if (m->authorship == AUTHOR_AUTHENTIC)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Author: %s\r\n", alloca_tohex_sid_t(m->author));
|
|
if (m->haveSecret) {
|
|
char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1];
|
|
rhizome_bytes_to_hex_upper(m->keypair.binary, secret, RHIZOME_BUNDLE_KEY_BYTES);
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Secret: %s\r\n", secret);
|
|
}
|
|
if (m->rowid)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Rowid: %"PRIu64"\r\n", m->rowid);
|
|
if (m->inserttime)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Inserttime: %"PRIu64"\r\n", m->inserttime);
|
|
}
|
|
|
|
static void render_import_headers(const struct httpd_request *r, strbuf sb)
|
|
{
|
|
if (r->u.insert.verify_id_version) {
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(r->bid));
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", r->ui64);
|
|
}
|
|
if (r->u.insert.payload_size != RHIZOME_SIZE_UNSET)
|
|
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", r->u.insert.payload_size);
|
|
}
|
|
|
|
static void render_status_and_manifest_headers(const struct http_request *hr, strbuf sb)
|
|
{
|
|
const httpd_request *r = (httpd_request *) hr;
|
|
render_status_headers(r, sb);
|
|
if (r->manifest) {
|
|
render_manifest_headers(r->manifest, sb);
|
|
}
|
|
}
|
|
|
|
static void render_status_and_import_headers(const struct http_request *hr, strbuf sb)
|
|
{
|
|
const httpd_request *r = (httpd_request *) hr;
|
|
render_status_headers(r, sb);
|
|
if (r->manifest) {
|
|
render_manifest_headers(r->manifest, sb);
|
|
} else {
|
|
render_import_headers(r, sb);
|
|
}
|
|
}
|