mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-18 20:57:56 +00:00
Merge latest Rhizome Java API into development
This commit is contained in:
commit
606f087dd5
@ -65,7 +65,7 @@ DEFS= @DEFS@
|
||||
|
||||
.PHONY: all test clean
|
||||
|
||||
all: servald libmonitorclient.so libmonitorclient.a test
|
||||
all: servald libserval.so libmonitorclient.so libmonitorclient.a test
|
||||
|
||||
test: tfw_createfile directory_service fakeradio config_test simulator
|
||||
|
||||
@ -198,8 +198,6 @@ copyright:
|
||||
findPATH = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))
|
||||
COPYRIGHT_TOOL := $(call findPATH,sp-copyright-tool)
|
||||
|
||||
# This does not build on 64 bit elf platforms as NaCL isn't built with -fPIC
|
||||
# DOC 20120615
|
||||
libserval.so: $(SERVALD_OBJS) $(OBJSDIR_TOOLS)/version.o
|
||||
@echo LINK $@
|
||||
@$(CC) -Wall -shared -o $@ $(SERVALD_OBJS) $(OBJSDIR_TOOLS)/version.o $(LDFLAGS)
|
||||
|
@ -1761,9 +1761,9 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
|
||||
status = RHIZOME_BUNDLE_STATUS_DONOTWANT;
|
||||
WHY("Insufficient space to store payload");
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
status = RHIZOME_BUNDLE_STATUS_NO_ROOM;
|
||||
INFO("Insufficient space to store payload");
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
status = RHIZOME_BUNDLE_STATUS_ERROR;
|
||||
@ -1773,12 +1773,12 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
|
||||
status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
status = RHIZOME_BUNDLE_STATUS_FAKE;
|
||||
status = RHIZOME_BUNDLE_STATUS_READONLY;
|
||||
break;
|
||||
default:
|
||||
FATALF("pstatus = %d", pstatus);
|
||||
}
|
||||
rhizome_manifest *mout = m;
|
||||
rhizome_manifest *mout = NULL;
|
||||
if (status == RHIZOME_BUNDLE_STATUS_NEW) {
|
||||
if (!rhizome_manifest_validate(m) || m->malformed)
|
||||
status = RHIZOME_BUNDLE_STATUS_INVALID;
|
||||
@ -1790,6 +1790,7 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
|
||||
}
|
||||
}
|
||||
}
|
||||
int status_valid = 0;
|
||||
switch (status) {
|
||||
case RHIZOME_BUNDLE_STATUS_NEW:
|
||||
if (mout && mout != m)
|
||||
@ -1799,25 +1800,30 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co
|
||||
case RHIZOME_BUNDLE_STATUS_SAME:
|
||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
||||
case RHIZOME_BUNDLE_STATUS_OLD:
|
||||
assert(mout != NULL);
|
||||
cli_put_manifest(context, mout);
|
||||
if ( manifestpath && *manifestpath
|
||||
&& rhizome_write_manifest_file(mout, manifestpath, 0) == -1
|
||||
)
|
||||
WHYF("Could not write manifest to %s", alloca_str_toprint(manifestpath));
|
||||
status_valid = 1;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_READONLY:
|
||||
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
|
||||
case RHIZOME_BUNDLE_STATUS_ERROR:
|
||||
case RHIZOME_BUNDLE_STATUS_INVALID:
|
||||
case RHIZOME_BUNDLE_STATUS_FAKE:
|
||||
case RHIZOME_BUNDLE_STATUS_DONOTWANT:
|
||||
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
||||
status_valid = 1;
|
||||
break;
|
||||
default:
|
||||
FATALF("status=%d", status);
|
||||
// Do not use a default: label! With no default, if a new value is added to the enum, then the
|
||||
// compiler will issue a warning on switch statements that do not cover all the values, which is
|
||||
// a valuable tool for the developer.
|
||||
}
|
||||
if (mout && mout != m) {
|
||||
if (!status_valid)
|
||||
FATALF("status=%d", status);
|
||||
if (mout && mout != m)
|
||||
rhizome_manifest_free(mout);
|
||||
m = NULL;
|
||||
}
|
||||
rhizome_manifest_free(m);
|
||||
keyring_free(keyring);
|
||||
keyring = NULL;
|
||||
|
@ -384,15 +384,6 @@ STRUCT(rhizome_direct)
|
||||
SUB_STRUCT(peerlist, peer,)
|
||||
END_STRUCT
|
||||
|
||||
STRUCT(user)
|
||||
STRING(50, password, "", str,, "Authentication password")
|
||||
END_STRUCT
|
||||
|
||||
ARRAY(userlist,)
|
||||
KEY_STRING(25, str)
|
||||
VALUE_SUB_STRUCT(user)
|
||||
END_ARRAY(10)
|
||||
|
||||
STRUCT(rhizome_api_addfile)
|
||||
STRING(64, uri_path, "", absolute_path,, "URI path for HTTP add-file request")
|
||||
ATOM(struct in_addr, allow_host, hton_in_addr(INADDR_LOOPBACK), in_addr,, "IP address of host allowed to make HTTP add-file request")
|
||||
@ -401,15 +392,8 @@ ATOM(sid_t, default_author, SID_ANY, sid,, "Author of ad
|
||||
ATOM(rhizome_bk_t, bundle_secret_key, RHIZOME_BK_NONE, rhizome_bk,, "Secret key of add-file bundle to try if sender not given")
|
||||
END_STRUCT
|
||||
|
||||
STRUCT(rhizome_api_restful)
|
||||
SUB_STRUCT(userlist, users,)
|
||||
ATOM(uint32_t, newsince_timeout, 60, uint32_time_interval,, "Time to block while reporting new bundles")
|
||||
ATOM(uint32_t, newsince_poll_ms, 2000, uint32_nonzero,, "Database poll interval while blocked reporting new bundles")
|
||||
END_STRUCT
|
||||
|
||||
STRUCT(rhizome_api)
|
||||
SUB_STRUCT(rhizome_api_addfile, addfile,)
|
||||
SUB_STRUCT(rhizome_api_restful, restful,)
|
||||
END_STRUCT
|
||||
|
||||
STRUCT(rhizome_http)
|
||||
@ -490,6 +474,25 @@ KEY_ATOM(unsigned, uint)
|
||||
VALUE_NODE_STRUCT(network_interface, network_interface)
|
||||
END_ARRAY(10)
|
||||
|
||||
STRUCT(user)
|
||||
STRING(50, password, "", str,, "Authentication password")
|
||||
END_STRUCT
|
||||
|
||||
ARRAY(userlist,)
|
||||
KEY_STRING(25, str)
|
||||
VALUE_SUB_STRUCT(user)
|
||||
END_ARRAY(10)
|
||||
|
||||
STRUCT(api_restful)
|
||||
SUB_STRUCT(userlist, users,)
|
||||
ATOM(uint32_t, newsince_timeout, 60, uint32_time_interval,, "Time to block while reporting new bundles")
|
||||
ATOM(uint32_t, newsince_poll_ms, 2000, uint32_nonzero,, "Database poll interval while blocked reporting new bundles")
|
||||
END_STRUCT
|
||||
|
||||
STRUCT(api)
|
||||
SUB_STRUCT(api_restful, restful,)
|
||||
END_STRUCT
|
||||
|
||||
// The top level.
|
||||
STRUCT(main)
|
||||
NODE_STRUCT(interface_list, interfaces, interface_list,)
|
||||
@ -504,4 +507,5 @@ SUB_STRUCT(rhizome, rhizome,)
|
||||
SUB_STRUCT(directory, directory,)
|
||||
SUB_STRUCT(olsr, olsr,)
|
||||
SUB_STRUCT(host_list, hosts,)
|
||||
SUB_STRUCT(api, api,)
|
||||
END_STRUCT
|
||||
|
@ -339,6 +339,14 @@ static inline void _commit(struct http_request *r)
|
||||
r->parsed = r->cursor;
|
||||
}
|
||||
|
||||
static inline int _skip_any(struct http_request *r)
|
||||
{
|
||||
if (_run_out(r))
|
||||
return 0;
|
||||
++r->cursor;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline void _skip_all(struct http_request *r)
|
||||
{
|
||||
r->cursor = r->end;
|
||||
@ -626,6 +634,17 @@ static int _parse_content_type(struct http_request *r, struct mime_content_type
|
||||
continue;
|
||||
}
|
||||
r->cursor = start;
|
||||
if (_skip_literal(r, "format=")) {
|
||||
size_t n = _parse_token_or_quoted_string(r, ct->format, sizeof ct->format);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
if (n >= sizeof ct->format) {
|
||||
WARNF("HTTP Content-Type format truncated: %s", alloca_str_toprint(ct->format));
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
r->cursor = start;
|
||||
struct substring param;
|
||||
if (_skip_token(r, ¶m) && _skip_literal(r, "=") && _parse_token_or_quoted_string(r, NULL, 0)) {
|
||||
if (r->debug_flag && *r->debug_flag)
|
||||
@ -1246,16 +1265,21 @@ static int http_request_parse_body_form_data(struct http_request *r)
|
||||
if (config.debug.http_server)
|
||||
DEBUGF("PREAMBLE");
|
||||
char *start = r->parsed;
|
||||
for (; at_start || _skip_to_crlf(r); at_start = 0) {
|
||||
const char *end_preamble = r->cursor;
|
||||
while (at_start || _skip_to_crlf(r)) {
|
||||
char *end_preamble = r->cursor;
|
||||
int b;
|
||||
if ((b = _skip_mime_boundary(r))) {
|
||||
if ((at_start || _skip_crlf(r)) && (b = _skip_mime_boundary(r))) {
|
||||
assert(end_preamble >= r->parsed);
|
||||
_INVOKE_HANDLER_BUF_LEN(handle_mime_preamble, r->parsed, end_preamble);
|
||||
_INVOKE_HANDLER_BUF_LEN(handle_mime_preamble, start, end_preamble);
|
||||
_rewind_crlf(r);
|
||||
_commit(r);
|
||||
return http_request_form_data_start_part(r, b);
|
||||
}
|
||||
if (!at_start) {
|
||||
r->cursor = end_preamble;
|
||||
_skip_any(r);
|
||||
}
|
||||
at_start = 0;
|
||||
}
|
||||
if (_end_of_content(r)) {
|
||||
if (r->debug_flag && *r->debug_flag)
|
||||
@ -1321,11 +1345,10 @@ static int http_request_parse_body_form_data(struct http_request *r)
|
||||
char labelstr[labellen + 1];
|
||||
strncpy(labelstr, label.start, labellen)[labellen] = '\0';
|
||||
str_tolower_inplace(labelstr);
|
||||
const char *value = r->cursor;
|
||||
if (strcmp(labelstr, "content-length") == 0) {
|
||||
if (r->part_header.content_length != CONTENT_LENGTH_UNKNOWN) {
|
||||
if (r->debug_flag && *r->debug_flag)
|
||||
DEBUGF("Skipping duplicate HTTP multipart header Content-Length: %s", alloca_toprint(50, sol, r->end - sol));
|
||||
DEBUGF("Skipping duplicate HTTP multipart header %s", alloca_toprint(50, sol, r->end - sol));
|
||||
return 400;
|
||||
}
|
||||
http_size_t length;
|
||||
@ -1341,7 +1364,7 @@ static int http_request_parse_body_form_data(struct http_request *r)
|
||||
else if (strcmp(labelstr, "content-type") == 0) {
|
||||
if (r->part_header.content_type.type[0]) {
|
||||
if (r->debug_flag && *r->debug_flag)
|
||||
DEBUGF("Skipping duplicate HTTP multipart header Content-Type: %s", alloca_toprint(50, sol, r->end - sol));
|
||||
DEBUGF("Skipping duplicate HTTP multipart header %s", alloca_toprint(50, sol, r->end - sol));
|
||||
return 400;
|
||||
}
|
||||
if (_parse_content_type(r, &r->part_header.content_type) && _skip_optional_space(r) && _skip_crlf(r)) {
|
||||
@ -1355,7 +1378,7 @@ static int http_request_parse_body_form_data(struct http_request *r)
|
||||
else if (strcmp(labelstr, "content-disposition") == 0) {
|
||||
if (r->part_header.content_disposition.type[0]) {
|
||||
if (r->debug_flag && *r->debug_flag)
|
||||
DEBUGF("Skipping duplicate HTTP multipart header Content-Disposition: %s", alloca_toprint(50, sol, r->end - sol));
|
||||
DEBUGF("Skipping duplicate HTTP multipart header %s", alloca_toprint(50, sol, r->end - sol));
|
||||
return 400;
|
||||
}
|
||||
if (_parse_content_disposition(r, &r->part_header.content_disposition) && _skip_optional_space(r) && _skip_crlf(r)) {
|
||||
@ -1369,7 +1392,7 @@ static int http_request_parse_body_form_data(struct http_request *r)
|
||||
else if (_skip_to_crlf(r)) {
|
||||
_commit(r);
|
||||
if (r->debug_flag && *r->debug_flag)
|
||||
DEBUGF("Skip HTTP multipart header: %s: %s", alloca_str_toprint(labelstr), alloca_toprint(-1, value, value - r->cursor));
|
||||
DEBUGF("Skip HTTP multipart header: %s", alloca_toprint(50, sol, r->parsed - sol));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -1798,6 +1821,7 @@ static const char *httpResultString(int response_code)
|
||||
switch (response_code) {
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 204: return "No Content";
|
||||
case 206: return "Partial Content";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
@ -1824,12 +1848,14 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char
|
||||
) {
|
||||
hr->header.content_type = CONTENT_TYPE_TEXT;
|
||||
strbuf_sprintf(sb, "%03u %s", hr->result_code, message);
|
||||
if (hr->result_extra_label) {
|
||||
strbuf_puts(sb, "\r\n");
|
||||
strbuf_puts(sb, hr->result_extra_label);
|
||||
strbuf_puts(sb, "=");
|
||||
strbuf_json_atom_as_text(sb, &hr->result_extra_value);
|
||||
}
|
||||
unsigned i;
|
||||
for (i = 0; i < NELS(hr->result_extra); ++i)
|
||||
if (hr->result_extra[i].label) {
|
||||
strbuf_puts(sb, "\r\n");
|
||||
strbuf_puts(sb, hr->result_extra[i].label);
|
||||
strbuf_puts(sb, "=");
|
||||
strbuf_json_atom_as_text(sb, &hr->result_extra[i].value);
|
||||
}
|
||||
strbuf_puts(sb, "\r\n");
|
||||
}
|
||||
else if ( hr->header.content_type == CONTENT_TYPE_JSON
|
||||
@ -1838,24 +1864,28 @@ static strbuf strbuf_status_body(strbuf sb, struct http_response *hr, const char
|
||||
hr->header.content_type = CONTENT_TYPE_JSON;
|
||||
strbuf_sprintf(sb, "{\n \"http_status_code\": %u,\n \"http_status_message\": ", hr->result_code);
|
||||
strbuf_json_string(sb, message);
|
||||
if (hr->result_extra_label) {
|
||||
strbuf_puts(sb, ",\n ");
|
||||
strbuf_json_string(sb, hr->result_extra_label);
|
||||
strbuf_puts(sb, ": ");
|
||||
strbuf_json_atom_as_html(sb, &hr->result_extra_value);
|
||||
}
|
||||
unsigned i;
|
||||
for (i = 0; i < NELS(hr->result_extra); ++i)
|
||||
if (hr->result_extra[i].label) {
|
||||
strbuf_puts(sb, ",\n ");
|
||||
strbuf_json_string(sb, hr->result_extra[i].label);
|
||||
strbuf_puts(sb, ": ");
|
||||
strbuf_json_atom(sb, &hr->result_extra[i].value);
|
||||
}
|
||||
strbuf_puts(sb, "\n}");
|
||||
}
|
||||
else {
|
||||
hr->header.content_type = CONTENT_TYPE_HTML;
|
||||
strbuf_sprintf(sb, "<html>\n<h1>%03u %s</h1>", hr->result_code, message);
|
||||
if (hr->result_extra_label) {
|
||||
strbuf_puts(sb, "\n<dl><dt>");
|
||||
strbuf_html_escape(sb, hr->result_extra_label, strlen(hr->result_extra_label));
|
||||
strbuf_puts(sb, "</dt><dd>");
|
||||
strbuf_json_atom_as_html(sb, &hr->result_extra_value);
|
||||
strbuf_puts(sb, "</dd></dl>");
|
||||
}
|
||||
unsigned i;
|
||||
for (i = 0; i < NELS(hr->result_extra); ++i)
|
||||
if (hr->result_extra[i].label) {
|
||||
strbuf_puts(sb, "\n<dl><dt>");
|
||||
strbuf_html_escape(sb, hr->result_extra[i].label, strlen(hr->result_extra[i].label));
|
||||
strbuf_puts(sb, "</dt><dd>");
|
||||
strbuf_json_atom_as_html(sb, &hr->result_extra[i].value);
|
||||
strbuf_puts(sb, "</dd></dl>");
|
||||
}
|
||||
strbuf_puts(sb, "\n</html>");
|
||||
}
|
||||
return sb;
|
||||
|
@ -68,6 +68,7 @@ struct mime_content_type {
|
||||
char subtype[64];
|
||||
char multipart_boundary[71];
|
||||
char charset[31];
|
||||
char format[31];
|
||||
};
|
||||
|
||||
struct http_client_authorization {
|
||||
@ -111,8 +112,10 @@ typedef int (HTTP_CONTENT_GENERATOR)(struct http_request *, unsigned char *, siz
|
||||
|
||||
struct http_response {
|
||||
uint16_t result_code;
|
||||
const char *result_extra_label;
|
||||
struct json_atom result_extra_value;
|
||||
struct {
|
||||
const char *label;
|
||||
struct json_atom value;
|
||||
} result_extra[4];
|
||||
struct http_response_headers header;
|
||||
const char *content;
|
||||
HTTP_CONTENT_GENERATOR *content_generator; // callback to produce more content
|
||||
|
16
httpd.c
16
httpd.c
@ -276,6 +276,8 @@ void httpd_server_poll(struct sched_ent *alarm)
|
||||
} else {
|
||||
++httpd_request_count;
|
||||
request->uuid = http_request_uuid_counter++;
|
||||
request->payload_status = INVALID_RHIZOME_PAYLOAD_STATUS;
|
||||
request->bundle_status = INVALID_RHIZOME_BUNDLE_STATUS;
|
||||
if (peerip)
|
||||
request->http.client_sockaddr_in = *peerip;
|
||||
request->http.handle_headers = httpd_dispatch;
|
||||
@ -328,27 +330,27 @@ static int is_from_loopback(const struct http_request *r)
|
||||
/* Return 1 if the given authorization credentials are acceptable.
|
||||
* Return 0 if not.
|
||||
*/
|
||||
static int is_authorized(const struct http_client_authorization *auth)
|
||||
static int is_authorized_restful(const struct http_client_authorization *auth)
|
||||
{
|
||||
if (auth->scheme != BASIC)
|
||||
return 0;
|
||||
unsigned i;
|
||||
for (i = 0; i != config.rhizome.api.restful.users.ac; ++i) {
|
||||
if ( strcmp(config.rhizome.api.restful.users.av[i].key, auth->credentials.basic.user) == 0
|
||||
&& strcmp(config.rhizome.api.restful.users.av[i].value.password, auth->credentials.basic.password) == 0
|
||||
for (i = 0; i != config.api.restful.users.ac; ++i) {
|
||||
if ( strcmp(config.api.restful.users.av[i].key, auth->credentials.basic.user) == 0
|
||||
&& strcmp(config.api.restful.users.av[i].value.password, auth->credentials.basic.password) == 0
|
||||
)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int authorize(struct http_request *r)
|
||||
int authorize_restful(struct http_request *r)
|
||||
{
|
||||
if (!is_from_loopback(r))
|
||||
return 403;
|
||||
if (!is_authorized(&r->request_header.authorization)) {
|
||||
if (!is_authorized_restful(&r->request_header.authorization)) {
|
||||
r->response.header.www_authenticate.scheme = BASIC;
|
||||
r->response.header.www_authenticate.realm = "Serval Rhizome";
|
||||
r->response.header.www_authenticate.realm = "Serval RESTful API";
|
||||
return 401;
|
||||
}
|
||||
return 0;
|
||||
|
5
httpd.h
5
httpd.h
@ -60,6 +60,8 @@ typedef struct httpd_request
|
||||
/* For requests/responses that pertain to a single manifest.
|
||||
*/
|
||||
rhizome_manifest *manifest;
|
||||
enum rhizome_payload_status payload_status;
|
||||
enum rhizome_bundle_status bundle_status;
|
||||
|
||||
/* For requests/responses that contain one or two SIDs.
|
||||
*/
|
||||
@ -123,7 +125,6 @@ typedef struct httpd_request
|
||||
// For storing the manifest text (malloc/realloc) as we receive it
|
||||
struct form_buf_malloc manifest;
|
||||
// For receiving the payload
|
||||
enum rhizome_payload_status payload_status;
|
||||
uint64_t payload_size;
|
||||
struct rhizome_write write;
|
||||
}
|
||||
@ -191,7 +192,7 @@ int httpd_server_start(uint16_t port_low, uint16_t port_high);
|
||||
typedef int HTTP_HANDLER(httpd_request *r, const char *remainder);
|
||||
|
||||
int is_http_header_complete(const char *buf, size_t len, size_t read_since_last_call);
|
||||
int authorize(struct http_request *r);
|
||||
int authorize_restful(struct http_request *r);
|
||||
int http_response_content_type(httpd_request *r, const char *what, const struct mime_content_type *ct);
|
||||
int http_response_content_disposition(httpd_request *r, const char *what, const char *type);
|
||||
int http_response_form_part(httpd_request *r, const char *what, const char *partname, const char *text, size_t textlen);
|
||||
|
113
java/org/servalproject/json/JSONTableScanner.java
Normal file
113
java/org/servalproject/json/JSONTableScanner.java
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.json;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.io.IOException;
|
||||
import java.util.Vector;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class JSONTableScanner {
|
||||
|
||||
private static class Column {
|
||||
public String label;
|
||||
public Class type;
|
||||
public JSONTokeniser.Narrow opts;
|
||||
}
|
||||
|
||||
HashMap<String,Column> columnMap;
|
||||
Column[] columns;
|
||||
|
||||
public JSONTableScanner()
|
||||
{
|
||||
columnMap = new HashMap<String,Column>();
|
||||
}
|
||||
|
||||
public JSONTableScanner addColumn(String label, Class type)
|
||||
{
|
||||
return addColumn(label, type, JSONTokeniser.Narrow.NO_NULL);
|
||||
}
|
||||
|
||||
public JSONTableScanner addColumn(String label, Class type, JSONTokeniser.Narrow opts)
|
||||
{
|
||||
assert !columnMap.containsKey(label);
|
||||
Column col = new Column();
|
||||
col.label = label;
|
||||
col.type = type;
|
||||
col.opts = opts;
|
||||
columnMap.put(label, col);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void consumeHeaderArray(JSONTokeniser json) throws IOException, JSONInputException
|
||||
{
|
||||
Vector<String> headers = new Vector<String>();
|
||||
json.consumeArray(headers, String.class);
|
||||
if (headers.size() < 1)
|
||||
throw new JSONInputException("malformed JSON table, empty headers array");
|
||||
columns = new Column[headers.size()];
|
||||
HashSet<String> headerSet = new HashSet<String>(columnMap.size());
|
||||
for (int i = 0; i < headers.size(); ++i) {
|
||||
String header = headers.get(i);
|
||||
if (columnMap.containsKey(header)) {
|
||||
if (headerSet.contains(header))
|
||||
throw new JSONInputException("malformed JSON table, duplicate column header: \"" + header + "\"");
|
||||
headerSet.add(header);
|
||||
columns[i] = columnMap.get(header);
|
||||
}
|
||||
}
|
||||
for (String header: columnMap.keySet())
|
||||
if (!headerSet.contains(header))
|
||||
throw new JSONInputException("malformed JSON table, missing column header: \"" + header + "\"");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String,Object> consumeRowArray(JSONTokeniser json) throws IOException, JSONInputException
|
||||
{
|
||||
Object[] row = new Object[columns.length];
|
||||
json.consumeArray(row, JSONTokeniser.Narrow.ALLOW_NULL);
|
||||
HashMap<String,Object> rowMap = new HashMap<String,Object>(row.length);
|
||||
for (int i = 0; i < row.length; ++i) {
|
||||
Column col = columns[i];
|
||||
if (col != null) {
|
||||
Object value;
|
||||
if (JSONTokeniser.supportsNarrowTo(col.type))
|
||||
value = JSONTokeniser.narrow(row[i], col.type, col.opts);
|
||||
else {
|
||||
value = JSONTokeniser.narrow(row[i], col.opts);
|
||||
try {
|
||||
value = value == null ? null : col.type.getConstructor(value.getClass()).newInstance(value);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new JSONInputException("invalid column value: " + col.label + "=\"" + value + "\"", e.getTargetException());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new JSONInputException("invalid column value: " + col.label + "=\"" + value + "\"", e);
|
||||
}
|
||||
}
|
||||
rowMap.put(col.label, value);
|
||||
}
|
||||
}
|
||||
return rowMap;
|
||||
}
|
||||
}
|
@ -54,6 +54,10 @@ public class JSONTokeniser {
|
||||
|
||||
public static class UnexpectedException extends JSONInputException
|
||||
{
|
||||
public UnexpectedException(String got) {
|
||||
super("unexpected " + got);
|
||||
}
|
||||
|
||||
public UnexpectedException(String got, Class expecting) {
|
||||
super("unexpected " + got + ", expecting " + expecting.getName());
|
||||
}
|
||||
@ -78,6 +82,10 @@ public class JSONTokeniser {
|
||||
|
||||
public static class UnexpectedTokenException extends UnexpectedException
|
||||
{
|
||||
public UnexpectedTokenException(Object got) {
|
||||
super(jsonTokenDescription(got));
|
||||
}
|
||||
|
||||
public UnexpectedTokenException(Object got, Class expecting) {
|
||||
super(jsonTokenDescription(got), expecting);
|
||||
}
|
||||
@ -115,6 +123,11 @@ public class JSONTokeniser {
|
||||
return n;
|
||||
}
|
||||
|
||||
public static void unexpected(Object tok) throws UnexpectedTokenException
|
||||
{
|
||||
throw new UnexpectedTokenException(tok);
|
||||
}
|
||||
|
||||
public static void match(Object tok, Token exactly) throws SyntaxException
|
||||
{
|
||||
if (tok != exactly)
|
||||
@ -131,6 +144,20 @@ public class JSONTokeniser {
|
||||
ALLOW_NULL
|
||||
};
|
||||
|
||||
public static boolean supportsNarrowTo(Class cls) {
|
||||
return cls == Boolean.class
|
||||
|| cls == Integer.class
|
||||
|| cls == Long.class
|
||||
|| cls == Float.class
|
||||
|| cls == Double.class
|
||||
|| cls == String.class;
|
||||
}
|
||||
|
||||
public static Object narrow(Object tok, Narrow opts) throws UnexpectedException
|
||||
{
|
||||
return narrow(tok, Object.class, opts);
|
||||
}
|
||||
|
||||
public static <T> T narrow(Object tok, Class<T> cls) throws UnexpectedException
|
||||
{
|
||||
return narrow(tok, cls, Narrow.NO_NULL);
|
||||
|
44
java/org/servalproject/servaldna/BundleSecret.java
Normal file
44
java/org/servalproject/servaldna/BundleSecret.java
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class BundleSecret extends AbstractId {
|
||||
|
||||
@Override
|
||||
public int getBinarySize() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
public BundleSecret(String hex) throws InvalidHexException {
|
||||
super(hex);
|
||||
}
|
||||
|
||||
public BundleSecret(ByteBuffer b) throws InvalidBinaryException {
|
||||
super(b);
|
||||
}
|
||||
|
||||
public BundleSecret(byte[] binary) throws InvalidBinaryException {
|
||||
super(binary);
|
||||
}
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ import org.servalproject.servaldna.meshms.MeshMSConversationList;
|
||||
import org.servalproject.servaldna.meshms.MeshMSException;
|
||||
import org.servalproject.servaldna.meshms.MeshMSMessageList;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URL;
|
||||
@ -32,8 +33,23 @@ import java.net.URLConnection;
|
||||
import java.net.HttpURLConnection;
|
||||
import org.servalproject.codec.Base64;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDCommand;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeCommon;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeIncompleteManifest;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeBundleList;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeManifestBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomePayloadBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeInsertBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeInvalidManifestException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeFakeManifestException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeInconsistencyException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeEncryptionException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeReadOnlyException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeDecryptionException;
|
||||
import org.servalproject.servaldna.meshms.MeshMSCommon;
|
||||
import org.servalproject.servaldna.meshms.MeshMSConversationList;
|
||||
import org.servalproject.servaldna.meshms.MeshMSMessageList;
|
||||
@ -58,6 +74,52 @@ public class ServalDClient implements ServalDHttpConnectionFactory
|
||||
this.restfulPassword = restfulPassword;
|
||||
}
|
||||
|
||||
public RhizomeBundleList rhizomeListBundles() throws ServalDInterfaceException, IOException
|
||||
{
|
||||
RhizomeBundleList list = new RhizomeBundleList(this);
|
||||
list.connect();
|
||||
return list;
|
||||
}
|
||||
|
||||
public RhizomeManifestBundle rhizomeManifest(BundleId bid) throws ServalDInterfaceException, IOException
|
||||
{
|
||||
return RhizomeCommon.rhizomeManifest(this, bid);
|
||||
}
|
||||
|
||||
public RhizomePayloadRawBundle rhizomePayloadRaw(BundleId bid) throws ServalDInterfaceException, IOException
|
||||
{
|
||||
return RhizomeCommon.rhizomePayloadRaw(this, bid);
|
||||
}
|
||||
|
||||
public RhizomePayloadBundle rhizomePayload(BundleId bid) throws ServalDInterfaceException, IOException, RhizomeDecryptionException
|
||||
{
|
||||
return RhizomeCommon.rhizomePayload(this, bid);
|
||||
}
|
||||
|
||||
public RhizomeInsertBundle rhizomeInsert(SubscriberId author, RhizomeIncompleteManifest manifest, BundleSecret secret)
|
||||
throws ServalDInterfaceException,
|
||||
IOException,
|
||||
RhizomeInvalidManifestException,
|
||||
RhizomeFakeManifestException,
|
||||
RhizomeInconsistencyException,
|
||||
RhizomeReadOnlyException,
|
||||
RhizomeEncryptionException
|
||||
{
|
||||
return RhizomeCommon.rhizomeInsert(this, author, manifest, secret);
|
||||
}
|
||||
|
||||
public RhizomeInsertBundle rhizomeInsert(SubscriberId author, RhizomeIncompleteManifest manifest, BundleSecret secret, InputStream payloadStream, String fileName)
|
||||
throws ServalDInterfaceException,
|
||||
IOException,
|
||||
RhizomeInvalidManifestException,
|
||||
RhizomeFakeManifestException,
|
||||
RhizomeInconsistencyException,
|
||||
RhizomeReadOnlyException,
|
||||
RhizomeEncryptionException
|
||||
{
|
||||
return RhizomeCommon.rhizomeInsert(this, author, manifest, secret, payloadStream, fileName);
|
||||
}
|
||||
|
||||
public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException, MeshMSException
|
||||
{
|
||||
MeshMSConversationList list = new MeshMSConversationList(this, sid);
|
||||
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna;
|
||||
|
||||
/**
|
||||
* Thrown when the Serval DNA interface is used to perform an operation that is not yet implemented,
|
||||
* but will be provided in future.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class ServalDNotImplementedException extends ServalDInterfaceException
|
||||
{
|
||||
public ServalDNotImplementedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -103,11 +103,11 @@ public class ServerControl {
|
||||
* RESTful interface. The authorisation must then be supplied to the restful client
|
||||
* object before requests can be made.
|
||||
*/
|
||||
String restfulPassword = ServalDCommand.getConfigItem("rhizome.api.restful.users." + restfulUsername + ".password");
|
||||
String restfulPassword = ServalDCommand.getConfigItem("api.restful.users." + restfulUsername + ".password");
|
||||
if (restfulPassword == null) {
|
||||
restfulPassword = new BigInteger(130, new SecureRandom()).toString(32);
|
||||
ServalDCommand.configActions(
|
||||
ServalDCommand.ConfigAction.set, "rhizome.api.restful.users." + restfulUsername + ".password", restfulPassword,
|
||||
ServalDCommand.ConfigAction.set, "api.restful.users." + restfulUsername + ".password", restfulPassword,
|
||||
ServalDCommand.ConfigAction.sync
|
||||
);
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public class MeshMSCommon
|
||||
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII"));
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
throw new ServalDInterfaceException("unexpected MeshMS status = " + status.meshms_status + ", \"" + status.message + "\"");
|
||||
throw new ServalDInterfaceException("unexpected MeshMS status = " + status.meshms_status_code + ", \"" + status.meshms_status_message + "\"");
|
||||
}
|
||||
for (int code: expected_response_codes) {
|
||||
if (conn.getResponseCode() == code) {
|
||||
@ -61,8 +61,10 @@ public class MeshMSCommon
|
||||
}
|
||||
|
||||
private static class Status {
|
||||
public MeshMSStatus meshms_status;
|
||||
public String message;
|
||||
public int http_status_code;
|
||||
public String http_status_message;
|
||||
public MeshMSStatus meshms_status_code;
|
||||
public String meshms_status_message;
|
||||
}
|
||||
|
||||
protected static Status decodeRestfulStatus(JSONTokeniser json) throws IOException, ServalDInterfaceException
|
||||
@ -72,15 +74,19 @@ public class MeshMSCommon
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("http_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
json.consume(Integer.class);
|
||||
status.http_status_code = json.consume(Integer.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
status.message = json.consume("http_status_message");
|
||||
json.consume("http_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
String message = json.consume(String.class);
|
||||
status.http_status_message = json.consume(String.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
json.consume("meshms_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.meshms_status = MeshMSStatus.fromCode(json.consume(Integer.class));
|
||||
status.meshms_status_code = MeshMSStatus.fromCode(json.consume(Integer.class));
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
json.consume("meshms_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.meshms_status_message = json.consume(String.class);
|
||||
json.consume(JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
return status;
|
||||
@ -92,7 +98,7 @@ public class MeshMSCommon
|
||||
|
||||
protected static void throwRestfulResponseExceptions(Status status, URL url) throws MeshMSException, ServalDFailureException
|
||||
{
|
||||
switch (status.meshms_status) {
|
||||
switch (status.meshms_status_code) {
|
||||
case OK:
|
||||
case UPDATED:
|
||||
break;
|
||||
@ -101,7 +107,7 @@ public class MeshMSCommon
|
||||
case PROTOCOL_FAULT:
|
||||
throw new MeshMSProtocolFaultException(url);
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received meshms_status=ERROR(-1) from " + url);
|
||||
throw new ServalDFailureException("received meshms_status_code=ERROR(-1) from " + url);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +131,7 @@ public class MeshMSCommon
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, HttpURLConnection.HTTP_CREATED);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status;
|
||||
return status.meshms_status_code;
|
||||
}
|
||||
|
||||
public static MeshMSStatus markAllConversationsRead(ServalDHttpConnectionFactory connector, SubscriberId sid1) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
@ -137,7 +143,7 @@ public class MeshMSCommon
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, expected_response_codes);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status;
|
||||
return status.meshms_status_code;
|
||||
}
|
||||
|
||||
public static MeshMSStatus markAllMessagesRead(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
@ -149,7 +155,7 @@ public class MeshMSCommon
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, expected_response_codes);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status;
|
||||
return status.meshms_status_code;
|
||||
}
|
||||
|
||||
public static MeshMSStatus advanceReadOffset(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2, long offset) throws IOException, ServalDInterfaceException, MeshMSException
|
||||
@ -161,7 +167,7 @@ public class MeshMSCommon
|
||||
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, expected_response_codes);
|
||||
Status status = decodeRestfulStatus(json);
|
||||
throwRestfulResponseExceptions(status, conn.getURL());
|
||||
return status.meshms_status;
|
||||
return status.meshms_status_code;
|
||||
}
|
||||
|
||||
}
|
||||
|
141
java/org/servalproject/servaldna/rhizome/RhizomeBundleList.java
Normal file
141
java/org/servalproject/servaldna/rhizome/RhizomeBundleList.java
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.json.JSONTableScanner;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RhizomeBundleList {
|
||||
|
||||
private ServalDHttpConnectionFactory httpConnector;
|
||||
private HttpURLConnection httpConnection;
|
||||
private JSONTokeniser json;
|
||||
private JSONTableScanner table;
|
||||
int rowCount;
|
||||
|
||||
public RhizomeBundleList(ServalDHttpConnectionFactory connector)
|
||||
{
|
||||
this.httpConnector = connector;
|
||||
this.table = new JSONTableScanner()
|
||||
.addColumn("_id", Integer.class)
|
||||
.addColumn(".token", String.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("service", String.class)
|
||||
.addColumn("id", BundleId.class)
|
||||
.addColumn("version", Long.class)
|
||||
.addColumn("date", Long.class)
|
||||
.addColumn(".inserttime", Long.class)
|
||||
.addColumn(".author", SubscriberId.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn(".fromhere", Integer.class)
|
||||
.addColumn("filesize", Long.class)
|
||||
.addColumn("filehash", FileHash.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("sender", SubscriberId.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("recipient", SubscriberId.class, JSONTokeniser.Narrow.ALLOW_NULL)
|
||||
.addColumn("name", String.class);
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
return this.json != null;
|
||||
}
|
||||
|
||||
public void connect() throws IOException, ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
rowCount = 0;
|
||||
httpConnection = httpConnector.newServalDHttpConnection("/restful/rhizome/bundlelist.json");
|
||||
httpConnection.connect();
|
||||
json = RhizomeCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("header");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
table.consumeHeaderArray(json);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
json.consume("rows");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
json.consume(JSONTokeniser.Token.START_ARRAY);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public RhizomeListBundle nextBundle() throws ServalDInterfaceException, IOException
|
||||
{
|
||||
try {
|
||||
Object tok = json.nextToken();
|
||||
if (tok == JSONTokeniser.Token.END_ARRAY) {
|
||||
json.consume(JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
return null;
|
||||
}
|
||||
if (rowCount != 0)
|
||||
JSONTokeniser.match(tok, JSONTokeniser.Token.COMMA);
|
||||
else
|
||||
json.pushToken(tok);
|
||||
Map<String,Object> row = table.consumeRowArray(json);
|
||||
return new RhizomeListBundle(
|
||||
new RhizomeManifest((BundleId)row.get("id"),
|
||||
(long)row.get("version"),
|
||||
(long)row.get("filesize"),
|
||||
(FileHash)row.get("filehash"),
|
||||
(SubscriberId)row.get("sender"),
|
||||
(SubscriberId)row.get("recipient"),
|
||||
null, // BK
|
||||
null, // crypt
|
||||
null, // tail
|
||||
(long)row.get("date"),
|
||||
(String)row.get("service"),
|
||||
(String)row.get("name")),
|
||||
rowCount++,
|
||||
(int)row.get("_id"),
|
||||
(String)row.get(".token"),
|
||||
(long)row.get(".inserttime"),
|
||||
(SubscriberId)row.get(".author"),
|
||||
(int)row.get(".fromhere")
|
||||
);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException
|
||||
{
|
||||
httpConnection = null;
|
||||
if (json != null) {
|
||||
json.close();
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
/* This enum is a direct isomorphism from the C "enum rhizome_bundle_status" defined in rhizome.h.
|
||||
*/
|
||||
public enum RhizomeBundleStatus {
|
||||
ERROR(-1), // internal error
|
||||
NEW(0), // bundle is newer than store
|
||||
SAME(1), // same version already in store
|
||||
DUPLICATE(2), // equivalent bundle already in store
|
||||
OLD(3), // newer version already in store
|
||||
INVALID(4), // manifest is invalid
|
||||
FAKE(5), // manifest signature not valid
|
||||
INCONSISTENT(6), // manifest filesize/filehash does not match supplied payload
|
||||
NO_ROOM(7), // doesn't fit; store may contain more important bundles
|
||||
READONLY(8) // cannot modify manifest; secret unknown
|
||||
;
|
||||
|
||||
final public int code;
|
||||
|
||||
private RhizomeBundleStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public static RhizomeBundleStatus fromCode(int code) throws InvalidException
|
||||
{
|
||||
RhizomeBundleStatus status = null;
|
||||
switch (code) {
|
||||
case -1: status = ERROR; break;
|
||||
case 0: status = NEW; break;
|
||||
case 1: status = SAME; break;
|
||||
case 2: status = DUPLICATE; break;
|
||||
case 3: status = OLD; break;
|
||||
case 4: status = INVALID; break;
|
||||
case 5: status = FAKE; break;
|
||||
case 6: status = INCONSISTENT; break;
|
||||
case 7: status = NO_ROOM; break;
|
||||
case 8: status = READONLY; break;
|
||||
default: throw new InvalidException(code);
|
||||
}
|
||||
assert status.code == code;
|
||||
return status;
|
||||
}
|
||||
|
||||
public static class InvalidException extends ServalDInterfaceException
|
||||
{
|
||||
public InvalidException(int code) {
|
||||
super("invalid Rhizome bundle status code = " + code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
667
java/org/servalproject/servaldna/rhizome/RhizomeCommon.java
Normal file
667
java/org/servalproject/servaldna/rhizome/RhizomeCommon.java
Normal file
@ -0,0 +1,667 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.lang.StringBuilder;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import org.servalproject.json.JSONTokeniser;
|
||||
import org.servalproject.json.JSONInputException;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDFailureException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
|
||||
public class RhizomeCommon
|
||||
{
|
||||
|
||||
private static class Status {
|
||||
InputStream input_stream;
|
||||
public int http_status_code;
|
||||
public String http_status_message;
|
||||
RhizomeBundleStatus bundle_status_code;
|
||||
String bundle_status_message;
|
||||
RhizomePayloadStatus payload_status_code;
|
||||
String payload_status_message;
|
||||
}
|
||||
|
||||
private static void dumpStatus(Status status, PrintStream out)
|
||||
{
|
||||
out.println("input_stream=" + status.input_stream);
|
||||
out.println("http_status_code=" + status.http_status_code);
|
||||
out.println("http_status_message=" + status.http_status_message);
|
||||
out.println("bundle_status_code=" + status.bundle_status_code);
|
||||
out.println("bundle_status_message=" + status.bundle_status_message);
|
||||
out.println("payload_status_code=" + status.payload_status_code);
|
||||
out.println("payload_status_message=" + status.payload_status_message);
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static Status receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = new Status();
|
||||
status.http_status_code = conn.getResponseCode();
|
||||
status.http_status_message = conn.getResponseMessage();
|
||||
for (int code: expected_response_codes) {
|
||||
if (status.http_status_code == code) {
|
||||
status.input_stream = conn.getInputStream();
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (!conn.getContentType().equals("application/json"))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
|
||||
if (status.http_status_code >= 300) {
|
||||
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII"));
|
||||
decodeRestfulStatus(status, json);
|
||||
}
|
||||
if (status.http_status_code == HttpURLConnection.HTTP_FORBIDDEN)
|
||||
return status;
|
||||
if (status.http_status_code == HttpURLConnection.HTTP_NOT_IMPLEMENTED)
|
||||
throw new ServalDNotImplementedException(status.http_status_message);
|
||||
throw new ServalDInterfaceException("unexpected HTTP response: " + status.http_status_code + " " + status.http_status_message);
|
||||
}
|
||||
|
||||
protected static ServalDInterfaceException unexpectedResponse(HttpURLConnection conn, Status status)
|
||||
{
|
||||
return new ServalDInterfaceException(
|
||||
"unexpected Rhizome failure, " + quoteString(status.http_status_message)
|
||||
+ (status.bundle_status_code == null ? "" : ", " + status.bundle_status_code)
|
||||
+ (status.bundle_status_message == null ? "" : " " + quoteString(status.bundle_status_message))
|
||||
+ (status.payload_status_code == null ? "" : ", " + status.payload_status_code)
|
||||
+ (status.payload_status_message == null ? "" : " " + quoteString(status.payload_status_message))
|
||||
+ " from " + conn.getURL()
|
||||
);
|
||||
}
|
||||
|
||||
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
int[] expected_response_codes = { expected_response_code };
|
||||
return receiveRestfulResponse(conn, expected_response_codes);
|
||||
}
|
||||
|
||||
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
Status status = receiveResponse(conn, expected_response_codes);
|
||||
if (!conn.getContentType().equals("application/json"))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
|
||||
return new JSONTokeniser(new InputStreamReader(status.input_stream, "US-ASCII"));
|
||||
}
|
||||
|
||||
protected static void decodeHeaderBundleStatus(Status status, HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
status.bundle_status_code = header(conn, "Serval-Rhizome-Result-Bundle-Status-Code", RhizomeBundleStatus.class);
|
||||
status.bundle_status_message = headerString(conn, "Serval-Rhizome-Result-Bundle-Status-Message");
|
||||
}
|
||||
|
||||
protected static void decodeHeaderPayloadStatus(Status status, HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
status.payload_status_code = header(conn, "Serval-Rhizome-Result-Payload-Status-Code", RhizomePayloadStatus.class);
|
||||
status.payload_status_message = headerString(conn, "Serval-Rhizome-Result-Payload-Status-Message");
|
||||
}
|
||||
|
||||
protected static void decodeRestfulStatus(Status status, JSONTokeniser json) throws IOException, ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
json.consume(JSONTokeniser.Token.START_OBJECT);
|
||||
json.consume("http_status_code");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
int hs = json.consume(Integer.class);
|
||||
json.consume(JSONTokeniser.Token.COMMA);
|
||||
if (status.http_status_code == 0)
|
||||
status.http_status_code = json.consume(Integer.class);
|
||||
else if (hs != status.http_status_code)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", http_status_code=" + hs
|
||||
+ " but HTTP response code is " + status.http_status_code);
|
||||
json.consume("http_status_message");
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
status.http_status_message = json.consume(String.class);
|
||||
Object tok = json.nextToken();
|
||||
while (tok == JSONTokeniser.Token.COMMA) {
|
||||
String label = json.consume(String.class);
|
||||
json.consume(JSONTokeniser.Token.COLON);
|
||||
if (label.equals("rhizome_bundle_status_code")) {
|
||||
RhizomeBundleStatus bs = RhizomeBundleStatus.fromCode(json.consume(Integer.class));
|
||||
if (status.bundle_status_code == null)
|
||||
status.bundle_status_code = bs;
|
||||
else if (status.bundle_status_code != bs)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_bundle_status_code=" + bs.code
|
||||
+ " but Serval-Rhizome-Result-Bundle-Status-Code: " + status.bundle_status_code.code);
|
||||
}
|
||||
else if (label.equals("rhizome_bundle_status_message")) {
|
||||
String message = json.consume(String.class);
|
||||
if (status.bundle_status_message == null)
|
||||
status.bundle_status_message = message;
|
||||
else if (!status.bundle_status_message.equals(message))
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_bundle_status_message=" + message
|
||||
+ " but Serval-Rhizome-Result-Bundle-Status-Message: " + status.bundle_status_message);
|
||||
}
|
||||
else if (label.equals("rhizome_payload_status_code")) {
|
||||
RhizomePayloadStatus bs = RhizomePayloadStatus.fromCode(json.consume(Integer.class));
|
||||
if (status.payload_status_code == null)
|
||||
status.payload_status_code = bs;
|
||||
else if (status.payload_status_code != bs)
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_payload_status_code=" + bs.code
|
||||
+ " but Serval-Rhizome-Result-Payload-Status-Code: " + status.payload_status_code.code);
|
||||
}
|
||||
else if (label.equals("rhizome_payload_status_message")) {
|
||||
String message = json.consume(String.class);
|
||||
if (status.payload_status_message == null)
|
||||
status.payload_status_message = message;
|
||||
else if (!status.payload_status_message.equals(message))
|
||||
throw new ServalDInterfaceException("JSON/header conflict"
|
||||
+ ", rhizome_payload_status_message=" + message
|
||||
+ " but Serval-Rhizome-Result-Payload-Status-Code: " + status.payload_status_message);
|
||||
}
|
||||
else
|
||||
json.unexpected(label);
|
||||
tok = json.nextToken();
|
||||
}
|
||||
json.match(tok, JSONTokeniser.Token.END_OBJECT);
|
||||
json.consume(JSONTokeniser.Token.EOF);
|
||||
}
|
||||
catch (JSONInputException e) {
|
||||
throw new ServalDInterfaceException("malformed JSON status response", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid)
|
||||
throws IOException, ServalDInterfaceException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + ".rhm");
|
||||
conn.connect();
|
||||
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
try {
|
||||
dumpHeaders(conn, System.err);
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
dumpStatus(status, System.err);
|
||||
switch (status.bundle_status_code) {
|
||||
case NEW:
|
||||
return null;
|
||||
case SAME:
|
||||
if (!conn.getContentType().equals("rhizome-manifest/text"))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
|
||||
RhizomeManifest manifest = RhizomeManifest.fromTextFormat(status.input_stream);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
return new RhizomeManifestBundle(manifest, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
|
||||
}
|
||||
}
|
||||
catch (RhizomeManifestParseException e) {
|
||||
throw new ServalDInterfaceException("malformed manifest from daemon", e);
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
}
|
||||
throw unexpectedResponse(conn, status);
|
||||
}
|
||||
|
||||
public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid)
|
||||
throws IOException, ServalDInterfaceException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/raw.bin");
|
||||
conn.connect();
|
||||
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
try {
|
||||
dumpHeaders(conn, System.err);
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
dumpStatus(status, System.err);
|
||||
switch (status.bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
|
||||
case NEW: // No manifest
|
||||
return null;
|
||||
case SAME:
|
||||
decodeHeaderPayloadStatus(status, conn);
|
||||
switch (status.payload_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_payload_status_code=ERROR(-1) from " + conn.getURL());
|
||||
case NEW:
|
||||
// The manifest is known but the payload is unavailable, so return a bundle
|
||||
// object with a null input stream.
|
||||
// FALL THROUGH
|
||||
case EMPTY:
|
||||
if (status.input_stream != null) {
|
||||
status.input_stream.close();
|
||||
status.input_stream = null;
|
||||
}
|
||||
// FALL THROUGH
|
||||
case STORED: {
|
||||
if (status.input_stream != null && !conn.getContentType().equals("application/octet-stream"))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
|
||||
RhizomeManifest manifest = manifestFromHeaders(conn);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
RhizomePayloadRawBundle ret = new RhizomePayloadRawBundle(manifest, status.input_stream, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
status.input_stream = null; // don't close when we return
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
}
|
||||
throw unexpectedResponse(conn, status);
|
||||
}
|
||||
|
||||
public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid)
|
||||
throws IOException, ServalDInterfaceException, RhizomeDecryptionException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/decrypted.bin");
|
||||
conn.connect();
|
||||
Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
|
||||
try {
|
||||
dumpHeaders(conn, System.err);
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
dumpStatus(status, System.err);
|
||||
switch (status.bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
|
||||
case NEW: // No manifest
|
||||
return null;
|
||||
case SAME:
|
||||
decodeHeaderPayloadStatus(status, conn);
|
||||
switch (status.payload_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received rhizome_payload_status_code=ERROR(-1) from " + conn.getURL());
|
||||
case CRYPTO_FAIL:
|
||||
throw new RhizomeDecryptionException(conn.getURL());
|
||||
case NEW:
|
||||
// The manifest is known but the payload is unavailable, so return a bundle
|
||||
// object with a null input stream.
|
||||
// FALL THROUGH
|
||||
case EMPTY:
|
||||
if (status.input_stream != null) {
|
||||
status.input_stream.close();
|
||||
status.input_stream = null;
|
||||
}
|
||||
// FALL THROUGH
|
||||
case STORED: {
|
||||
if (status.input_stream != null && !conn.getContentType().equals("application/octet-stream"))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
|
||||
RhizomeManifest manifest = manifestFromHeaders(conn);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
RhizomePayloadBundle ret = new RhizomePayloadBundle(manifest, status.input_stream, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
status.input_stream = null; // don't close when we return
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
}
|
||||
throw unexpectedResponse(conn, status);
|
||||
}
|
||||
|
||||
public static RhizomeInsertBundle rhizomeInsert(ServalDHttpConnectionFactory connector,
|
||||
SubscriberId author,
|
||||
RhizomeIncompleteManifest manifest,
|
||||
BundleSecret secret)
|
||||
throws ServalDInterfaceException,
|
||||
IOException,
|
||||
RhizomeInvalidManifestException,
|
||||
RhizomeFakeManifestException,
|
||||
RhizomeInconsistencyException,
|
||||
RhizomeReadOnlyException,
|
||||
RhizomeEncryptionException
|
||||
{
|
||||
return rhizomeInsert(connector, author, manifest, secret, null, null);
|
||||
}
|
||||
|
||||
public static RhizomeInsertBundle rhizomeInsert(ServalDHttpConnectionFactory connector,
|
||||
SubscriberId author,
|
||||
RhizomeIncompleteManifest manifest,
|
||||
BundleSecret secret,
|
||||
InputStream payloadStream,
|
||||
String fileName)
|
||||
throws ServalDInterfaceException,
|
||||
IOException,
|
||||
RhizomeInvalidManifestException,
|
||||
RhizomeFakeManifestException,
|
||||
RhizomeInconsistencyException,
|
||||
RhizomeReadOnlyException,
|
||||
RhizomeEncryptionException
|
||||
{
|
||||
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/insert");
|
||||
String boundary = Long.toHexString(System.currentTimeMillis());
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
conn.connect();
|
||||
OutputStream ost = conn.getOutputStream();
|
||||
PrintStream wr = new PrintStream(ost, false, "US-ASCII");
|
||||
wr.print(new Object(){}.getClass().getEnclosingClass().getName());
|
||||
if (author != null) {
|
||||
wr.print("\r\n--" + boundary + "\r\n");
|
||||
wr.print("Content-Disposition: form-data; name=\"bundle-author\"\r\n");
|
||||
wr.print("Content-Type: serval-mesh/sid\r\n");
|
||||
wr.print("Content-Transfer-Encoding: hex\r\n");
|
||||
wr.print("\r\n");
|
||||
wr.print(author.toHex());
|
||||
}
|
||||
if (secret != null) {
|
||||
wr.print("\r\n--" + boundary + "\r\n");
|
||||
wr.print("Content-Disposition: form-data; name=\"bundle-secret\"\r\n");
|
||||
wr.print("Content-Type: rhizome/bundle-secret\r\n");
|
||||
wr.print("Content-Transfer-Encoding: hex\r\n");
|
||||
wr.print("\r\n");
|
||||
wr.print(secret.toHex());
|
||||
}
|
||||
wr.print("\r\n--" + boundary + "\r\n");
|
||||
wr.print("Content-Disposition: form-data; name=\"manifest\"\r\n");
|
||||
wr.print("Content-Type: rhizome/manifest; format=\"text+binarysig\"\r\n");
|
||||
wr.print("Content-Transfer-Encoding: binary\r\n");
|
||||
wr.print("\r\n");
|
||||
wr.flush();
|
||||
manifest.toTextFormat(ost);
|
||||
if (payloadStream != null) {
|
||||
wr.print("\r\n--" + boundary + "\r\n");
|
||||
wr.print("Content-Disposition: form-data; name=\"payload\"");
|
||||
if (fileName != null) {
|
||||
wr.print("; filename=");
|
||||
wr.print(quoteString(fileName));
|
||||
}
|
||||
wr.print("\r\n");
|
||||
wr.print("Content-Type: application/octet-stream\r\n");
|
||||
wr.print("Content-Transfer-Encoding: binary\r\n");
|
||||
wr.print("\r\n");
|
||||
wr.flush();
|
||||
byte[] buffer = new byte[4096];
|
||||
int n;
|
||||
while ((n = payloadStream.read(buffer)) > 0)
|
||||
ost.write(buffer, 0, n);
|
||||
}
|
||||
wr.print("\r\n--" + boundary + "--\r\n");
|
||||
wr.close();
|
||||
int[] expected_response_codes = { HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED };
|
||||
Status status = RhizomeCommon.receiveResponse(conn, expected_response_codes);
|
||||
try {
|
||||
dumpHeaders(conn, System.err);
|
||||
decodeHeaderPayloadStatus(status, conn);
|
||||
switch (status.payload_status_code) {
|
||||
case ERROR:
|
||||
dumpStatus(status, System.err);
|
||||
throw new ServalDFailureException("received Rhizome payload_status=ERROR " + quoteString(status.payload_status_message) + " from " + conn.getURL());
|
||||
case EMPTY:
|
||||
case NEW:
|
||||
case STORED:
|
||||
decodeHeaderBundleStatus(status, conn);
|
||||
dumpStatus(status, System.err);
|
||||
switch (status.bundle_status_code) {
|
||||
case ERROR:
|
||||
throw new ServalDFailureException("received Rhizome bundle_status=ERROR " + quoteString(status.bundle_status_message) + " from " + conn.getURL());
|
||||
case NEW:
|
||||
case SAME:
|
||||
case DUPLICATE:
|
||||
case OLD:
|
||||
case NO_ROOM: {
|
||||
if (!conn.getContentType().equals("rhizome-manifest/text"))
|
||||
throw new ServalDInterfaceException("unexpected HTTP Content-Type " + conn.getContentType() + " from " + conn.getURL());
|
||||
RhizomeManifest returned_manifest = RhizomeManifest.fromTextFormat(status.input_stream);
|
||||
BundleExtra extra = bundleExtraFromHeaders(conn);
|
||||
return new RhizomeInsertBundle(status.bundle_status_code, returned_manifest, extra.rowId, extra.insertTime, extra.author, extra.secret);
|
||||
}
|
||||
case INVALID:
|
||||
throw new RhizomeInvalidManifestException(status.bundle_status_message, conn.getURL());
|
||||
case FAKE:
|
||||
throw new RhizomeFakeManifestException(status.bundle_status_message, conn.getURL());
|
||||
case INCONSISTENT:
|
||||
throw new RhizomeInconsistencyException(status.bundle_status_message, conn.getURL());
|
||||
case READONLY:
|
||||
throw new RhizomeReadOnlyException(status.bundle_status_message, conn.getURL());
|
||||
}
|
||||
break;
|
||||
case TOO_BIG:
|
||||
case EVICTED:
|
||||
dumpStatus(status, System.err);
|
||||
return null;
|
||||
case WRONG_SIZE:
|
||||
case WRONG_HASH:
|
||||
dumpStatus(status, System.err);
|
||||
throw new RhizomeInconsistencyException(status.payload_status_message, conn.getURL());
|
||||
case CRYPTO_FAIL:
|
||||
dumpStatus(status, System.err);
|
||||
throw new RhizomeEncryptionException(status.payload_status_message, conn.getURL());
|
||||
}
|
||||
}
|
||||
catch (RhizomeManifestParseException e) {
|
||||
throw new ServalDInterfaceException("malformed manifest from daemon", e);
|
||||
}
|
||||
finally {
|
||||
if (status.input_stream != null)
|
||||
status.input_stream.close();
|
||||
}
|
||||
dumpStatus(status, System.err);
|
||||
throw unexpectedResponse(conn, status);
|
||||
}
|
||||
|
||||
private static void dumpHeaders(HttpURLConnection conn, PrintStream out)
|
||||
{
|
||||
for (Map.Entry<String,List<String>> e: conn.getHeaderFields().entrySet())
|
||||
for (String v: e.getValue())
|
||||
out.println("received header " + e.getKey() + ": " + v);
|
||||
}
|
||||
|
||||
private static RhizomeManifest manifestFromHeaders(HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
BundleId id = header(conn, "Serval-Rhizome-Bundle-Id", BundleId.class);
|
||||
long version = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Version");
|
||||
long filesize = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Filesize");
|
||||
FileHash filehash = filesize == 0 ? null : header(conn, "Serval-Rhizome-Bundle-Filehash", FileHash.class);
|
||||
SubscriberId sender = headerOrNull(conn, "Serval-Rhizome-Bundle-Sender", SubscriberId.class);
|
||||
SubscriberId recipient = headerOrNull(conn, "Serval-Rhizome-Bundle-Recipient", SubscriberId.class);
|
||||
BundleKey BK = headerOrNull(conn, "Serval-Rhizome-Bundle-BK", BundleKey.class);
|
||||
Integer crypt = headerIntegerOrNull(conn, "Serval-Rhizome-Bundle-Crypt");
|
||||
Long tail = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Tail");
|
||||
Long date = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Date");
|
||||
String service = conn.getHeaderField("Serval-Rhizome-Bundle-Service");
|
||||
String name = headerQuotedStringOrNull(conn, "Serval-Rhizome-Bundle-Name");
|
||||
return new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name);
|
||||
}
|
||||
|
||||
private static class BundleExtra {
|
||||
public Long rowId;
|
||||
public Long insertTime;
|
||||
public SubscriberId author;
|
||||
public BundleSecret secret;
|
||||
}
|
||||
|
||||
private static BundleExtra bundleExtraFromHeaders(HttpURLConnection conn) throws ServalDInterfaceException
|
||||
{
|
||||
BundleExtra extra = new BundleExtra();
|
||||
extra.rowId = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Rowid");
|
||||
extra.insertTime = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Inserttime");
|
||||
extra.author = headerOrNull(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class);
|
||||
extra.secret = headerOrNull(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class);
|
||||
return extra;
|
||||
}
|
||||
|
||||
private static String quoteString(String unquoted)
|
||||
{
|
||||
if (unquoted == null)
|
||||
return "null";
|
||||
StringBuilder b = new StringBuilder(unquoted.length() + 2);
|
||||
b.append('"');
|
||||
for (int i = 0; i < unquoted.length(); ++i) {
|
||||
char c = unquoted.charAt(i);
|
||||
if (c == '"' || c == '\\')
|
||||
b.append('\\');
|
||||
b.append(c);
|
||||
}
|
||||
b.append('"');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private static String headerStringOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
return conn.getHeaderField(header);
|
||||
}
|
||||
|
||||
private static String headerString(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = headerStringOrNull(conn, header);
|
||||
if (str == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return str;
|
||||
}
|
||||
|
||||
private static String headerQuotedStringOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String quoted = conn.getHeaderField(header);
|
||||
if (quoted == null)
|
||||
return null;
|
||||
if (quoted.length() == 0 || quoted.charAt(0) != '"')
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at start of quoted-string");
|
||||
boolean slosh = false;
|
||||
boolean end = false;
|
||||
StringBuilder b = new StringBuilder(quoted.length());
|
||||
for (int i = 1; i < quoted.length(); ++i) {
|
||||
char c = quoted.charAt(i);
|
||||
if (end)
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": spurious character after quoted-string");
|
||||
if (c < ' ' || c > '~')
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": invalid character in quoted-string");
|
||||
if (slosh) {
|
||||
b.append(c);
|
||||
slosh = false;
|
||||
}
|
||||
else if (c == '"')
|
||||
end = true;
|
||||
else if (c == '\\')
|
||||
slosh = true;
|
||||
else
|
||||
b.append(c);
|
||||
}
|
||||
if (!end)
|
||||
throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at end of quoted-string");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private static Integer headerIntegerOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = conn.getHeaderField(header);
|
||||
if (str == null)
|
||||
return null;
|
||||
try {
|
||||
return Integer.valueOf(str);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
|
||||
}
|
||||
|
||||
private static Long headerUnsignedLongOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
String str = conn.getHeaderField(header);
|
||||
if (str == null)
|
||||
return null;
|
||||
try {
|
||||
Long value = Long.valueOf(str);
|
||||
if (value >= 0)
|
||||
return value;
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str);
|
||||
}
|
||||
|
||||
private static long headerUnsignedLong(HttpURLConnection conn, String header) throws ServalDInterfaceException
|
||||
{
|
||||
Long value = headerUnsignedLongOrNull(conn, header);
|
||||
if (value == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static <T> T headerOrNull(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException
|
||||
{
|
||||
String str = conn.getHeaderField(header);
|
||||
try {
|
||||
try {
|
||||
Constructor<T> constructor = cls.getConstructor(String.class);
|
||||
if (str == null)
|
||||
return null;
|
||||
return constructor.newInstance(str);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
}
|
||||
try {
|
||||
Method method = cls.getMethod("fromCode", Integer.TYPE);
|
||||
if ((method.getModifiers() & Modifier.STATIC) != 0 && method.getReturnType() == cls) {
|
||||
Integer integer = headerIntegerOrNull(conn, header);
|
||||
if (integer == null)
|
||||
return null;
|
||||
return cls.cast(method.invoke(null, integer));
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
}
|
||||
throw new ServalDInterfaceException("don't know how to instantiate: " + cls.getName());
|
||||
}
|
||||
catch (ServalDInterfaceException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e.getTargetException());
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T header(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException
|
||||
{
|
||||
T value = headerOrNull(conn, header, cls);
|
||||
if (value == null)
|
||||
throw new ServalDInterfaceException("missing header field: " + header);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API method is asked to decrypt a payload without possessing the necessary
|
||||
* recipient identity (ie, is locked or not in the keyring).
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeDecryptionException extends RhizomeException
|
||||
{
|
||||
public RhizomeDecryptionException(URL url) {
|
||||
super("cannot decrypt payload", url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when Rhizome already has a given manifest in the store.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeDuplicateBundleException extends RhizomeException
|
||||
{
|
||||
public RhizomeDuplicateBundleException(URL url) {
|
||||
super("duplicate bundle", url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API method is asked to encrypt a payload without possessing the necessary
|
||||
* author or sender secret (not in keyring, or identity not unlocked) and without possessing the
|
||||
* bundle secret.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeEncryptionException extends RhizomeException
|
||||
{
|
||||
public RhizomeEncryptionException(String message, URL url) {
|
||||
super(message == null ? "cannot encrypt payload" : message, url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API encounters an exceptional condition. This exception is subclassed for
|
||||
* specific causes.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public abstract class RhizomeException extends Exception
|
||||
{
|
||||
public final URL url;
|
||||
|
||||
public RhizomeException(String message) {
|
||||
super(message);
|
||||
this.url = null;
|
||||
}
|
||||
|
||||
public RhizomeException(String message, URL url) {
|
||||
super(message + "; " + url);
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API method is passed a manifest with an invalid or missing signature.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeFakeManifestException extends RhizomeException
|
||||
{
|
||||
public RhizomeFakeManifestException(String message, URL url) {
|
||||
super(message == null ? "unsigned manifest" : message, url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import org.servalproject.servaldna.AbstractId;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
|
||||
public class RhizomeIncompleteManifest {
|
||||
|
||||
public BundleId id;
|
||||
public Long version;
|
||||
public Long filesize;
|
||||
public FileHash filehash;
|
||||
public SubscriberId sender;
|
||||
public SubscriberId recipient;
|
||||
public BundleKey BK;
|
||||
public Long tail;
|
||||
public Integer crypt;
|
||||
public Long date;
|
||||
public String service;
|
||||
public String name;
|
||||
private HashMap<String,String> extraFields;
|
||||
|
||||
public RhizomeIncompleteManifest()
|
||||
{
|
||||
this.extraFields = new HashMap<String,String>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public RhizomeIncompleteManifest(RhizomeManifest m)
|
||||
{
|
||||
this.id = m.id;
|
||||
this.version = m.version;
|
||||
this.filesize = m.filesize;
|
||||
this.filehash = m.filehash;
|
||||
this.sender = m.sender;
|
||||
this.recipient = m.recipient;
|
||||
this.BK = m.BK;
|
||||
this.crypt = m.crypt;
|
||||
this.tail = m.tail;
|
||||
this.date = m.date;
|
||||
this.service = m.service;
|
||||
this.name = m.name;
|
||||
this.extraFields = (HashMap<String,String>) m.extraFields.clone(); // unchecked cast
|
||||
}
|
||||
|
||||
/** Return the Rhizome manifest in its text format representation.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public void toTextFormat(OutputStream os) throws IOException
|
||||
{
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, "US-ASCII");
|
||||
if (id != null)
|
||||
osw.write("id=" + id.toHex() + "\n");
|
||||
if (version != null)
|
||||
osw.write("version=" + version + "\n");
|
||||
if (filesize != null)
|
||||
osw.write("filesize=" + filesize + "\n");
|
||||
if (filehash != null)
|
||||
osw.write("filehash=" + filehash.toHex() + "\n");
|
||||
if (sender != null)
|
||||
osw.write("sender=" + sender.toHex() + "\n");
|
||||
if (recipient != null)
|
||||
osw.write("recipient=" + recipient.toHex() + "\n");
|
||||
if (BK != null)
|
||||
osw.write("BK=" + BK.toHex() + "\n");
|
||||
if (crypt != null)
|
||||
osw.write("crypt=" + crypt + "\n");
|
||||
if (tail != null)
|
||||
osw.write("tail=" + tail + "\n");
|
||||
if (date != null)
|
||||
osw.write("date=" + date + "\n");
|
||||
if (service != null)
|
||||
osw.write("service=" + service + "\n");
|
||||
if (name != null)
|
||||
osw.write("name=" + name + "\n");
|
||||
for (Map.Entry<String,String> e: extraFields.entrySet())
|
||||
osw.write(e.getKey() + "=" + e.getValue() + "\n");
|
||||
osw.flush();
|
||||
}
|
||||
|
||||
/** Construct a Rhizome manifest from its text format representation.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public static RhizomeIncompleteManifest fromTextFormat(byte[] bytes) throws RhizomeManifestParseException
|
||||
{
|
||||
RhizomeIncompleteManifest m = new RhizomeIncompleteManifest();
|
||||
try {
|
||||
m.parseTextFormat(new ByteArrayInputStream(bytes, 0, bytes.length));
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Construct a Rhizome manifest from its text format representation.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public static RhizomeIncompleteManifest fromTextFormat(byte[] bytes, int off, int len) throws RhizomeManifestParseException
|
||||
{
|
||||
RhizomeIncompleteManifest m = new RhizomeIncompleteManifest();
|
||||
try {
|
||||
m.parseTextFormat(new ByteArrayInputStream(bytes, off, len));
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Convenience method: construct a Rhizome manifest from all the bytes read from a given
|
||||
* InputStream.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
static public RhizomeIncompleteManifest fromTextFormat(InputStream in) throws IOException, RhizomeManifestParseException
|
||||
{
|
||||
RhizomeIncompleteManifest m = new RhizomeIncompleteManifest();
|
||||
m.parseTextFormat(in);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Fill in manifest fields from a text format representation.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public void parseTextFormat(InputStream in) throws IOException, RhizomeManifestParseException
|
||||
{
|
||||
try {
|
||||
InputStreamReader inr = new InputStreamReader(in, "US-ASCII");
|
||||
int pos = 0;
|
||||
int lnum = 1;
|
||||
int eq = -1;
|
||||
StringBuilder line = new StringBuilder();
|
||||
while (true) {
|
||||
int c = inr.read();
|
||||
if (c != -1 && c != '\n') {
|
||||
if (eq == -1 && c == '=')
|
||||
eq = line.length();
|
||||
line.append((char)c);
|
||||
}
|
||||
else if (line.length() == 0)
|
||||
break;
|
||||
else if (eq < 1)
|
||||
throw new RhizomeManifestParseException("malformed (missing '=') at line " + lnum + ": " + line);
|
||||
else {
|
||||
String fieldName = line.substring(0, eq);
|
||||
String fieldValue = line.substring(eq + 1);
|
||||
if (!isFieldNameFirstChar(fieldName.charAt(0)))
|
||||
throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + line);
|
||||
for (int i = 1; i < fieldName.length(); ++i)
|
||||
if (!isFieldNameChar(fieldName.charAt(i)))
|
||||
throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + line);
|
||||
try {
|
||||
if (fieldName.equals("id"))
|
||||
this.id = parseField(this.id, new BundleId(fieldValue));
|
||||
else if (fieldName.equals("version"))
|
||||
this.version = parseField(this.version, parseUnsignedLong(fieldValue));
|
||||
else if (fieldName.equals("filesize"))
|
||||
this.filesize = parseField(this.filesize, parseUnsignedLong(fieldValue));
|
||||
else if (fieldName.equals("filehash"))
|
||||
this.filehash = parseField(this.filehash, new FileHash(fieldValue));
|
||||
else if (fieldName.equals("sender"))
|
||||
this.sender = parseField(this.sender, new SubscriberId(fieldValue));
|
||||
else if (fieldName.equals("recipient"))
|
||||
this.recipient = parseField(this.recipient, new SubscriberId(fieldValue));
|
||||
else if (fieldName.equals("BK"))
|
||||
this.BK = parseField(this.BK, new BundleKey(fieldValue));
|
||||
else if (fieldName.equals("crypt"))
|
||||
this.crypt = parseField(this.crypt, Integer.parseInt(fieldValue));
|
||||
else if (fieldName.equals("tail"))
|
||||
this.tail = parseField(this.tail, parseUnsignedLong(fieldValue));
|
||||
else if (fieldName.equals("date"))
|
||||
this.date = parseField(this.date, parseUnsignedLong(fieldValue));
|
||||
else if (fieldName.equals("service"))
|
||||
this.service = parseField(this.service, fieldValue);
|
||||
else if (fieldName.equals("name"))
|
||||
this.name = parseField(this.name, fieldValue);
|
||||
else if (this.extraFields.containsKey(fieldName))
|
||||
throw new RhizomeManifestParseException("duplicate field");
|
||||
else
|
||||
this.extraFields.put(fieldName, fieldValue);
|
||||
}
|
||||
catch (RhizomeManifestParseException e) {
|
||||
throw new RhizomeManifestParseException(e.getMessage() + " at line " + lnum + ": " + line);
|
||||
}
|
||||
catch (AbstractId.InvalidHexException e) {
|
||||
throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + line, e);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + line, e);
|
||||
}
|
||||
line.setLength(0);
|
||||
eq = -1;
|
||||
++lnum;
|
||||
}
|
||||
}
|
||||
if (line.length() > 0)
|
||||
throw new RhizomeManifestParseException("malformed (missing newline) at line " + lnum + ": " + line);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new RhizomeManifestParseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static private <T> T parseField(T currentValue, T newValue) throws RhizomeManifestParseException
|
||||
{
|
||||
if (currentValue == null)
|
||||
return newValue;
|
||||
if (!currentValue.equals(newValue))
|
||||
throw new RhizomeManifestParseException("duplicate field");
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
private static boolean isFieldNameFirstChar(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
private static boolean isFieldNameChar(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
private static Long parseUnsignedLong(String text) throws NumberFormatException
|
||||
{
|
||||
Long value = Long.valueOf(text);
|
||||
if (value < 0)
|
||||
throw new NumberFormatException("negative value not allowed");
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API method is passed a manifest which is inconsistent with a supplied
|
||||
* payload. I.e., filesize or filehash does not match.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeInconsistencyException extends RhizomeException
|
||||
{
|
||||
public RhizomeInconsistencyException(String message, URL url) {
|
||||
super(message == null ? "manifest inconsistent with payload" : message, url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
public class RhizomeInsertBundle extends RhizomeManifestBundle {
|
||||
|
||||
public final RhizomeBundleStatus status;
|
||||
|
||||
protected RhizomeInsertBundle(RhizomeBundleStatus status,
|
||||
RhizomeManifest manifest,
|
||||
Long rowId,
|
||||
Long insertTime,
|
||||
SubscriberId author,
|
||||
BundleSecret secret)
|
||||
{
|
||||
super(manifest, rowId, insertTime, author, secret);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when the Rhizome API rejects a caller-supplied manifest as invalid. This error does not
|
||||
* originate from the Serval DNA interface, so it is not a subclass of ServalDInterfaceException.
|
||||
* The programmer must deal with it, and not treat it as an interface malfunction.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeInvalidManifestException extends RhizomeException
|
||||
{
|
||||
public RhizomeInvalidManifestException(String message, URL url) {
|
||||
super(message == null ? "invalid manifest" : message, url);
|
||||
}
|
||||
|
||||
public RhizomeInvalidManifestException(RhizomeIncompleteManifest manifest) {
|
||||
super("invalid manifest");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
|
||||
public class RhizomeListBundle {
|
||||
|
||||
public final int rowNumber;
|
||||
public final int rowId;
|
||||
public final String token;
|
||||
public final long insertTime;
|
||||
public final SubscriberId author;
|
||||
public final int fromHere;
|
||||
public final RhizomeManifest manifest;
|
||||
|
||||
protected RhizomeListBundle(RhizomeManifest manifest,
|
||||
int rowNumber,
|
||||
int rowId,
|
||||
String token,
|
||||
long insertTime,
|
||||
SubscriberId author,
|
||||
int fromHere)
|
||||
{
|
||||
this.manifest = manifest;
|
||||
this.rowNumber = rowNumber;
|
||||
this.rowId = rowId;
|
||||
this.token = token;
|
||||
this.insertTime = insertTime;
|
||||
this.author = author;
|
||||
this.fromHere = fromHere;
|
||||
}
|
||||
|
||||
}
|
234
java/org/servalproject/servaldna/rhizome/RhizomeManifest.java
Normal file
234
java/org/servalproject/servaldna/rhizome/RhizomeManifest.java
Normal file
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import org.servalproject.servaldna.AbstractId;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.FileHash;
|
||||
import org.servalproject.servaldna.BundleKey;
|
||||
|
||||
public class RhizomeManifest {
|
||||
|
||||
public final static int TEXT_FORMAT_MAX_SIZE = 8192;
|
||||
|
||||
// Core fields used for routing and expiry (cannot be null)
|
||||
public final BundleId id;
|
||||
public final long version;
|
||||
public final long filesize;
|
||||
|
||||
// Principal fields (can be null)
|
||||
public final FileHash filehash; // null iff filesize == 0
|
||||
public final SubscriberId sender;
|
||||
public final SubscriberId recipient;
|
||||
public final BundleKey BK;
|
||||
public final Long tail; // null iff not a journal
|
||||
public final Integer crypt;
|
||||
|
||||
// Optional fields (all can be null)
|
||||
public final Long date; // can be null
|
||||
public final String service;
|
||||
public final String name;
|
||||
|
||||
protected HashMap<String,String> extraFields;
|
||||
private byte[] signatureBlock;
|
||||
private byte[] textFormat;
|
||||
|
||||
protected RhizomeManifest( BundleId id,
|
||||
long version,
|
||||
long filesize,
|
||||
FileHash filehash,
|
||||
SubscriberId sender,
|
||||
SubscriberId recipient,
|
||||
BundleKey BK,
|
||||
Integer crypt,
|
||||
Long tail,
|
||||
Long date,
|
||||
String service,
|
||||
String name
|
||||
)
|
||||
{
|
||||
assert id != null;
|
||||
if (filesize == 0)
|
||||
assert filehash == null;
|
||||
else
|
||||
assert filehash != null;
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
this.filesize = filesize;
|
||||
this.filehash = filehash;
|
||||
this.sender = sender;
|
||||
this.recipient = recipient;
|
||||
this.BK = BK;
|
||||
this.crypt = crypt;
|
||||
this.tail = tail;
|
||||
this.date = date;
|
||||
this.service = service;
|
||||
this.name = name;
|
||||
this.extraFields = null;
|
||||
this.signatureBlock = null;
|
||||
this.textFormat = null;
|
||||
}
|
||||
|
||||
protected RhizomeManifest(RhizomeIncompleteManifest m)
|
||||
{
|
||||
this(m.id, m.version, m.filesize, m.filehash, m.sender, m.recipient, m.BK, m.crypt, m.tail, m.date, m.service, m.name);
|
||||
}
|
||||
|
||||
/** Return the Rhizome manifest in its text format representation, with the signature block at
|
||||
* the end if present.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public byte[] toTextFormat() throws RhizomeManifestSizeException
|
||||
{
|
||||
if (this.textFormat == null) {
|
||||
try {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
toTextFormat(os);
|
||||
os.close();
|
||||
if (os.size() > TEXT_FORMAT_MAX_SIZE)
|
||||
throw new RhizomeManifestSizeException("manifest text format overflow", os.size(), TEXT_FORMAT_MAX_SIZE);
|
||||
this.textFormat = os.toByteArray();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// should not happen with ByteArrayOutputStream
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
byte[] ret = new byte[this.textFormat.length];
|
||||
System.arraycopy(this.textFormat, 0, ret, 0, ret.length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Write the Rhizome manifest in its text format representation to the given output stream,
|
||||
* with the signature block at the end if present.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public void toTextFormat(OutputStream os) throws IOException
|
||||
{
|
||||
new RhizomeIncompleteManifest(this).toTextFormat(os);
|
||||
if (this.signatureBlock != null) {
|
||||
os.write(0);
|
||||
os.write(this.signatureBlock);
|
||||
}
|
||||
}
|
||||
|
||||
/** Construct a Rhizome manifest from its text format representation.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
static public RhizomeManifest fromTextFormat(byte[] bytes) throws RhizomeManifestParseException
|
||||
{
|
||||
return fromTextFormat(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/** Construct a complete Rhizome manifest from its text format representation, including a
|
||||
* trailing signature block.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
static public RhizomeManifest fromTextFormat(byte[] bytes, int off, int len) throws RhizomeManifestParseException
|
||||
{
|
||||
// The signature block follows the first nul character at the start of a line.
|
||||
byte[] sigblock = null;
|
||||
int proplen = len;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (bytes[off + i] == 0 && (i == 0 || bytes[off + i - 1] == '\n')) {
|
||||
sigblock = new byte[len - i - 1];
|
||||
System.arraycopy(bytes, off + i + 1, sigblock, 0, sigblock.length);
|
||||
proplen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
RhizomeIncompleteManifest im = new RhizomeIncompleteManifest();
|
||||
try {
|
||||
im.parseTextFormat(new ByteArrayInputStream(bytes, off, proplen));
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
if (im.id == null)
|
||||
throw new RhizomeManifestParseException("missing 'id' field");
|
||||
if (im.version == null)
|
||||
throw new RhizomeManifestParseException("missing 'version' field");
|
||||
if (im.filesize == null)
|
||||
throw new RhizomeManifestParseException("missing 'filesize' field");
|
||||
if (im.filesize != 0 && im.filehash == null)
|
||||
throw new RhizomeManifestParseException("missing 'filehash' field");
|
||||
else if (im.filesize == 0 && im.filehash != null)
|
||||
throw new RhizomeManifestParseException("spurious 'filehash' field");
|
||||
RhizomeManifest m = new RhizomeManifest(im);
|
||||
m.signatureBlock = sigblock;
|
||||
m.textFormat = new byte[len];
|
||||
System.arraycopy(bytes, off, m.textFormat, 0, m.textFormat.length);
|
||||
return m;
|
||||
}
|
||||
|
||||
/** Convenience method: construct a Rhizome manifest from all the bytes read from a given
|
||||
* InputStream.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
static public RhizomeManifest fromTextFormat(InputStream in) throws IOException, RhizomeManifestParseException
|
||||
{
|
||||
byte[] bytes = new byte[TEXT_FORMAT_MAX_SIZE];
|
||||
int n = 0;
|
||||
int offset = 0;
|
||||
while (offset < bytes.length && (n = in.read(bytes, offset, bytes.length - offset)) != -1)
|
||||
offset += n;
|
||||
assert offset <= bytes.length;
|
||||
if (offset == bytes.length)
|
||||
n = in.read();
|
||||
if (n != -1)
|
||||
throw new RhizomeManifestParseException("input stream too long");
|
||||
return fromTextFormat(bytes, 0, offset);
|
||||
}
|
||||
|
||||
private static boolean isFieldNameFirstChar(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
private static boolean isFieldNameChar(char c)
|
||||
{
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
private static Long parseUnsignedLong(String text) throws NumberFormatException
|
||||
{
|
||||
Long value = Long.valueOf(text);
|
||||
if (value < 0)
|
||||
throw new NumberFormatException("negative value not allowed");
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when Rhizome already has a given manifest in the store.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeManifestAlreadyStoredException extends RhizomeException
|
||||
{
|
||||
public RhizomeManifestAlreadyStoredException(URL url) {
|
||||
super("manifest already stored", url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
public class RhizomeManifestBundle {
|
||||
|
||||
public final Long insertTime;
|
||||
public final Long rowId;
|
||||
public final SubscriberId author;
|
||||
public final BundleSecret secret;
|
||||
public final RhizomeManifest manifest;
|
||||
|
||||
protected RhizomeManifestBundle(RhizomeManifest manifest,
|
||||
Long rowId,
|
||||
Long insertTime,
|
||||
SubscriberId author,
|
||||
BundleSecret secret)
|
||||
{
|
||||
this.manifest = manifest;
|
||||
this.rowId = rowId;
|
||||
this.insertTime = insertTime;
|
||||
this.author = author;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public byte[] manifestText() throws ServalDInterfaceException
|
||||
{
|
||||
try {
|
||||
return manifest.toTextFormat();
|
||||
}
|
||||
catch (RhizomeManifestSizeException e) {
|
||||
throw new ServalDInterfaceException("manifest text overflow", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when Rhizome has no manifest with the given ID.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeManifestNotFoundException extends RhizomeException
|
||||
{
|
||||
public RhizomeManifestNotFoundException(URL url) {
|
||||
super("manifest not found", url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome manifest cannot be parsed from its text format.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeManifestParseException extends Exception
|
||||
{
|
||||
public RhizomeManifestParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RhizomeManifestParseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public RhizomeManifestParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Thrown when the text or binary format of a Rhizome manifest is too long to fit in a limited-size
|
||||
* byte stream.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeManifestSizeException extends Exception
|
||||
{
|
||||
private long mSize;
|
||||
private long mMaxSize;
|
||||
|
||||
public RhizomeManifestSizeException(String message, long size, long maxSize)
|
||||
{
|
||||
super(message + " (" + size + " bytes exceeds " + maxSize + ")");
|
||||
mSize = size;
|
||||
mMaxSize = maxSize;
|
||||
}
|
||||
|
||||
public RhizomeManifestSizeException(File manifestFile, long maxSize)
|
||||
{
|
||||
this(manifestFile.toString(), manifestFile.length(), maxSize);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when Rhizome has a newer manifest in the store, ie, same Bundle ID and higher version
|
||||
* number.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeOutdatedBundleException extends RhizomeException
|
||||
{
|
||||
public RhizomeOutdatedBundleException(URL url) {
|
||||
super("outdated bundle", url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
public class RhizomePayloadBundle {
|
||||
|
||||
public final RhizomeManifest manifest;
|
||||
public final InputStream payloadInputStream;
|
||||
public final Long rowId;
|
||||
public final Long insertTime;
|
||||
public final SubscriberId author;
|
||||
public final BundleSecret secret;
|
||||
|
||||
protected RhizomePayloadBundle(RhizomeManifest manifest,
|
||||
InputStream payloadInputStream,
|
||||
Long rowId,
|
||||
Long insertTime,
|
||||
SubscriberId author,
|
||||
BundleSecret secret)
|
||||
{
|
||||
this.payloadInputStream = payloadInputStream;
|
||||
this.manifest = manifest;
|
||||
this.rowId = rowId;
|
||||
this.insertTime = insertTime;
|
||||
this.author = author;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.io.InputStream;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
public class RhizomePayloadRawBundle {
|
||||
|
||||
public final RhizomeManifest manifest;
|
||||
public final InputStream rawPayloadInputStream;
|
||||
public final Long rowId;
|
||||
public final Long insertTime;
|
||||
public final SubscriberId author;
|
||||
public final BundleSecret secret;
|
||||
|
||||
protected RhizomePayloadRawBundle(RhizomeManifest manifest,
|
||||
InputStream rawPayloadInputStream,
|
||||
Long rowId,
|
||||
Long insertTime,
|
||||
SubscriberId author,
|
||||
BundleSecret secret)
|
||||
{
|
||||
this.rawPayloadInputStream = rawPayloadInputStream;
|
||||
this.manifest = manifest;
|
||||
this.rowId = rowId;
|
||||
this.insertTime = insertTime;
|
||||
this.author = author;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
|
||||
/* This enum is a direct isomorphism from the C "enum rhizome_payload_status" defined in rhizome.h.
|
||||
*/
|
||||
public enum RhizomePayloadStatus {
|
||||
ERROR(-1), // unexpected error (underlying failure)
|
||||
EMPTY(0), // payload is empty (zero length)
|
||||
NEW(1), // payload is not yet in store (added)
|
||||
STORED(2), // payload is already in store
|
||||
WRONG_SIZE(3), // payload's size does not match manifest
|
||||
WRONG_HASH(4), // payload's hash does not match manifest
|
||||
CRYPTO_FAIL(5), // cannot encrypt/decrypt (payload key unknown)
|
||||
TOO_BIG(6), // payload will never fit in our store
|
||||
EVICTED(7) // other payloads in our store are more important
|
||||
;
|
||||
|
||||
final public int code;
|
||||
|
||||
private RhizomePayloadStatus(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public static RhizomePayloadStatus fromCode(int code) throws InvalidException
|
||||
{
|
||||
RhizomePayloadStatus status = null;
|
||||
switch (code) {
|
||||
case -1: status = ERROR; break;
|
||||
case 0: status = EMPTY; break;
|
||||
case 1: status = NEW; break;
|
||||
case 2: status = STORED; break;
|
||||
case 3: status = WRONG_SIZE; break;
|
||||
case 4: status = WRONG_HASH; break;
|
||||
case 5: status = CRYPTO_FAIL; break;
|
||||
case 6: status = TOO_BIG; break;
|
||||
case 7: status = EVICTED; break;
|
||||
default: throw new InvalidException(code);
|
||||
}
|
||||
assert status.code == code;
|
||||
return status;
|
||||
}
|
||||
|
||||
public static class InvalidException extends ServalDInterfaceException
|
||||
{
|
||||
public InvalidException(int code) {
|
||||
super("invalid Rhizome payload status code = " + code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when a Rhizome API method is passed a manifest which is inconsistent with a supplied
|
||||
* payload. I.e., filesize or filehash does not match.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeReadOnlyException extends RhizomeException
|
||||
{
|
||||
public RhizomeReadOnlyException(String message, URL url) {
|
||||
super(message == null ? "bundle cannot be modified" : message, url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.servaldna.rhizome;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Thrown when the Rhizome store is full.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
public class RhizomeStoreFullException extends RhizomeException
|
||||
{
|
||||
public RhizomeStoreFullException(URL url) {
|
||||
super("store is full", url);
|
||||
}
|
||||
|
||||
}
|
268
java/org/servalproject/test/Rhizome.java
Normal file
268
java/org/servalproject/test/Rhizome.java
Normal file
@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Serval Project Inc.
|
||||
*
|
||||
* This file is part of Serval Software (http://www.servalproject.org)
|
||||
*
|
||||
* Serval Software 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 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This source code 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 source code; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package org.servalproject.test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import org.servalproject.servaldna.ServalDClient;
|
||||
import org.servalproject.servaldna.ServalDInterfaceException;
|
||||
import org.servalproject.servaldna.ServalDNotImplementedException;
|
||||
import org.servalproject.servaldna.ServerControl;
|
||||
import org.servalproject.servaldna.BundleId;
|
||||
import org.servalproject.servaldna.BundleSecret;
|
||||
import org.servalproject.servaldna.SubscriberId;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeManifest;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeIncompleteManifest;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeListBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeBundleList;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeManifestBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomePayloadBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeInsertBundle;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeException;
|
||||
import org.servalproject.servaldna.rhizome.RhizomeManifestParseException;
|
||||
|
||||
public class Rhizome {
|
||||
|
||||
static String manifestFields(RhizomeManifest manifest, String sep)
|
||||
{
|
||||
return "id=" + manifest.id
|
||||
+ sep + "version=" + manifest.version
|
||||
+ sep + "filesize=" + manifest.filesize
|
||||
+ (manifest.filesize != 0 ? sep + "filehash=" + manifest.filehash : "")
|
||||
+ (manifest.sender != null ? sep + "sender=" + manifest.sender : "")
|
||||
+ (manifest.recipient != null ? sep + "recipient=" + manifest.recipient : "")
|
||||
+ (manifest.date != null ? sep + "date=" + manifest.date : "")
|
||||
+ (manifest.service != null ? sep + "service=" + manifest.service : "")
|
||||
+ (manifest.BK != null ? sep + "BK=" + manifest.BK : "")
|
||||
+ (manifest.name != null ? sep + "name=" + manifest.name : "");
|
||||
}
|
||||
|
||||
static void rhizome_list() throws ServalDInterfaceException, IOException, InterruptedException
|
||||
{
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
RhizomeBundleList list = null;
|
||||
try {
|
||||
list = client.rhizomeListBundles();
|
||||
RhizomeListBundle bundle;
|
||||
while ((bundle = list.nextBundle()) != null) {
|
||||
System.out.println(
|
||||
"_token=" + bundle.token +
|
||||
", _rowId=" + bundle.rowId +
|
||||
", _insertTime=" + bundle.insertTime +
|
||||
", _author=" + bundle.author +
|
||||
", _fromHere=" + bundle.fromHere +
|
||||
", " + manifestFields(bundle.manifest, ", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (list != null)
|
||||
list.close();
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void rhizome_manifest(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException
|
||||
{
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
RhizomeManifestBundle bundle = client.rhizomeManifest(bid);
|
||||
if (bundle == null)
|
||||
System.out.println("not found");
|
||||
else {
|
||||
System.out.println(
|
||||
(bundle.rowId == null ? "" : "_rowId=" + bundle.rowId + "\n") +
|
||||
(bundle.insertTime == null ? "" : "_insertTime=" + bundle.insertTime + "\n") +
|
||||
(bundle.author == null ? "" : "_author=" + bundle.author + "\n") +
|
||||
(bundle.secret == null ? "" : "_secret=" + bundle.secret + "\n") +
|
||||
manifestFields(bundle.manifest, "\n") + "\n"
|
||||
);
|
||||
FileOutputStream out = new FileOutputStream(dstpath);
|
||||
out.write(bundle.manifestText());
|
||||
out.close();
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void rhizome_payload_raw(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException
|
||||
{
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
RhizomePayloadRawBundle bundle = client.rhizomePayloadRaw(bid);
|
||||
if (bundle == null)
|
||||
System.out.println("not found");
|
||||
else {
|
||||
InputStream in = bundle.rawPayloadInputStream;
|
||||
if (in == null)
|
||||
System.out.println("no payload");
|
||||
else {
|
||||
out = new FileOutputStream(dstpath);
|
||||
byte[] buf = new byte[4096];
|
||||
int n;
|
||||
while ((n = in.read(buf)) > 0)
|
||||
out.write(buf, 0, n);
|
||||
in.close();
|
||||
out.close();
|
||||
out = null;
|
||||
}
|
||||
System.out.println(
|
||||
(bundle.rowId == null ? "" : "_rowId=" + bundle.rowId + "\n") +
|
||||
(bundle.insertTime == null ? "" : "_insertTime=" + bundle.insertTime + "\n") +
|
||||
(bundle.author == null ? "" : "_author=" + bundle.author + "\n") +
|
||||
(bundle.secret == null ? "" : "_secret=" + bundle.secret + "\n") +
|
||||
manifestFields(bundle.manifest, "\n") + "\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (out != null)
|
||||
out.close();
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void rhizome_payload_decrypted(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException
|
||||
{
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
RhizomePayloadBundle bundle = client.rhizomePayload(bid);
|
||||
if (bundle == null)
|
||||
System.out.println("not found");
|
||||
else {
|
||||
InputStream in = bundle.payloadInputStream;
|
||||
if (in == null)
|
||||
System.out.println("no payload");
|
||||
else {
|
||||
out = new FileOutputStream(dstpath);
|
||||
byte[] buf = new byte[4096];
|
||||
int n;
|
||||
while ((n = in.read(buf)) > 0)
|
||||
out.write(buf, 0, n);
|
||||
in.close();
|
||||
out.close();
|
||||
out = null;
|
||||
}
|
||||
System.out.println(
|
||||
(bundle.rowId == null ? "" : "_rowId=" + bundle.rowId + "\n") +
|
||||
(bundle.insertTime == null ? "" : "_insertTime=" + bundle.insertTime + "\n") +
|
||||
(bundle.author == null ? "" : "_author=" + bundle.author + "\n") +
|
||||
(bundle.secret == null ? "" : "_secret=" + bundle.secret + "\n") +
|
||||
manifestFields(bundle.manifest, "\n") + "\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (RhizomeException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
finally {
|
||||
if (out != null)
|
||||
out.close();
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void rhizome_insert( String author,
|
||||
String manifestPath,
|
||||
String payloadPath,
|
||||
String manifestoutpath,
|
||||
String payloadName,
|
||||
String secretHex)
|
||||
throws ServalDInterfaceException,
|
||||
IOException,
|
||||
InterruptedException,
|
||||
SubscriberId.InvalidHexException
|
||||
{
|
||||
ServalDClient client = new ServerControl().getRestfulClient();
|
||||
try {
|
||||
RhizomeIncompleteManifest manifest = new RhizomeIncompleteManifest();
|
||||
if (manifestPath != null && manifestPath.length() != 0)
|
||||
manifest.parseTextFormat(new FileInputStream(manifestPath));
|
||||
RhizomeInsertBundle bundle;
|
||||
SubscriberId authorSid = author == null || author.length() == 0 ? null : new SubscriberId(author);
|
||||
BundleSecret secret = secretHex == null || secretHex.length() == 0 ? null : new BundleSecret(secretHex);
|
||||
if (payloadName == null || payloadName.length() == 0)
|
||||
payloadName = new File(payloadPath).getName();
|
||||
if (payloadPath == null || payloadPath.length() == 0)
|
||||
bundle = client.rhizomeInsert(authorSid, manifest, secret);
|
||||
else
|
||||
bundle = client.rhizomeInsert(authorSid, manifest, secret, new FileInputStream(payloadPath), payloadName);
|
||||
System.out.println(
|
||||
"_status=" + bundle.status + "\n" +
|
||||
(bundle.rowId == null ? "" : "_rowId=" + bundle.rowId + "\n") +
|
||||
(bundle.insertTime == null ? "" : "_insertTime=" + bundle.insertTime + "\n") +
|
||||
(bundle.author == null ? "" : "_author=" + bundle.author + "\n") +
|
||||
(bundle.secret == null ? "" : "_secret=" + bundle.secret + "\n") +
|
||||
manifestFields(bundle.manifest, "\n") + "\n"
|
||||
);
|
||||
if (manifestoutpath != null && manifestoutpath.length() != 0) {
|
||||
FileOutputStream out = new FileOutputStream(manifestoutpath);
|
||||
out.write(bundle.manifestText());
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
catch (RhizomeManifestParseException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
catch (RhizomeException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
catch (ServalDNotImplementedException e) {
|
||||
System.out.println(e.toString());
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public static void main(String... args)
|
||||
{
|
||||
if (args.length < 1)
|
||||
return;
|
||||
String methodName = args[0];
|
||||
try {
|
||||
if (methodName.equals("rhizome-list"))
|
||||
rhizome_list();
|
||||
else if (methodName.equals("rhizome-manifest"))
|
||||
rhizome_manifest(new BundleId(args[1]), args[2]);
|
||||
else if (methodName.equals("rhizome-payload-raw"))
|
||||
rhizome_payload_raw(new BundleId(args[1]), args[2]);
|
||||
else if (methodName.equals("rhizome-payload-decrypted"))
|
||||
rhizome_payload_decrypted(new BundleId(args[1]), args[2]);
|
||||
else if (methodName.equals("rhizome-insert"))
|
||||
rhizome_insert( args[1], // author SID
|
||||
args[2], // manifest path
|
||||
args.length > 3 ? args[3] : null, // payload path
|
||||
args.length > 4 ? args[4] : null, // manifest out path
|
||||
args.length > 5 ? args[5] : null, // payload name
|
||||
args.length > 6 ? args[6] : null // bundle secret
|
||||
);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.err.println("No such command: " + methodName);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
12
meshms.c
12
meshms.c
@ -1244,3 +1244,15 @@ done:
|
||||
keyring = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *meshms_status_message(enum meshms_status status)
|
||||
{
|
||||
switch (status) {
|
||||
case MESHMS_STATUS_OK: return "OK";
|
||||
case MESHMS_STATUS_UPDATED: return "Updated";
|
||||
case MESHMS_STATUS_SID_LOCKED: return "Identity unknown";
|
||||
case MESHMS_STATUS_PROTOCOL_FAULT: return "MeshMS protocol fault";
|
||||
case MESHMS_STATUS_ERROR: return "Internal error";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
2
meshms.h
2
meshms.h
@ -47,6 +47,8 @@ __MESHMS_INLINE int meshms_failed(enum meshms_status status) {
|
||||
return status != MESHMS_STATUS_OK && status != MESHMS_STATUS_UPDATED;
|
||||
}
|
||||
|
||||
const char *meshms_status_message(enum meshms_status);
|
||||
|
||||
// the manifest details for one half of a conversation
|
||||
struct meshms_ply {
|
||||
rhizome_bid_t bundle_id;
|
||||
|
@ -66,45 +66,41 @@ static int strn_to_meshms_token(const char *str, rhizome_bid_t *bidp, uint64_t *
|
||||
|
||||
static int http_request_meshms_response(struct httpd_request *r, uint16_t result, const char *message, enum meshms_status status)
|
||||
{
|
||||
r->http.response.result_extra_label = "meshms_status_code";
|
||||
r->http.response.result_extra_value.type = JSON_INTEGER;
|
||||
r->http.response.result_extra_value.u.integer = status;
|
||||
uint16_t meshms_result = 0;
|
||||
switch (status) {
|
||||
case MESHMS_STATUS_OK:
|
||||
if (!result)
|
||||
result = 200;
|
||||
if (!message)
|
||||
message = "OK";
|
||||
meshms_result = 200;
|
||||
break;
|
||||
case MESHMS_STATUS_UPDATED:
|
||||
if (!result)
|
||||
result = 201;
|
||||
if (!message)
|
||||
message = "Updated";
|
||||
meshms_result = 201;
|
||||
break;
|
||||
case MESHMS_STATUS_SID_LOCKED:
|
||||
if (!result)
|
||||
result = 403;
|
||||
if (!message)
|
||||
message = "Identity unknown";
|
||||
break;
|
||||
case MESHMS_STATUS_PROTOCOL_FAULT:
|
||||
if (!result)
|
||||
result = 403;
|
||||
if (!message)
|
||||
message = "MeshMS protocol fault";
|
||||
meshms_result = 403;
|
||||
break;
|
||||
case MESHMS_STATUS_ERROR:
|
||||
if (!result)
|
||||
result = 500;
|
||||
break;
|
||||
default:
|
||||
WHYF("Invalid MeshMS status code %d", status);
|
||||
result = 500;
|
||||
meshms_result = 500;
|
||||
break;
|
||||
}
|
||||
if (meshms_result == 0) {
|
||||
WHYF("Invalid MeshMS status code %d", status);
|
||||
meshms_result = 500;
|
||||
}
|
||||
r->http.response.result_extra[0].label = "meshms_status_code";
|
||||
r->http.response.result_extra[0].value.type = JSON_INTEGER;
|
||||
r->http.response.result_extra[0].value.u.integer = status;
|
||||
const char *status_message = meshms_status_message(status);
|
||||
if (status_message) {
|
||||
r->http.response.result_extra[1].label = "meshms_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 (meshms_result > result) {
|
||||
result = meshms_result;
|
||||
message = NULL;
|
||||
}
|
||||
assert(result != 0);
|
||||
http_request_simple_response(&r->http, result, message);
|
||||
http_request_simple_response(&r->http, result, message ? message : result == 403 ? "MeshMS operation failed" : NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -185,7 +181,7 @@ int restful_meshms_(httpd_request *r, const char *remainder)
|
||||
http_request_simple_response(&r->http, 400, "Bad content length");
|
||||
return 400;
|
||||
}
|
||||
int ret = authorize(&r->http);
|
||||
int ret = authorize_restful(&r->http);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = handler(r, remainder);
|
||||
@ -341,7 +337,7 @@ static int restful_meshms_newsince_messagelist_json(httpd_request *r, const char
|
||||
return 404;
|
||||
}
|
||||
r->u.msglist.token_offset = r->ui64;
|
||||
r->u.msglist.end_time = gettime_ms() + config.rhizome.api.restful.newsince_timeout * 1000;
|
||||
r->u.msglist.end_time = gettime_ms() + config.api.restful.newsince_timeout * 1000;
|
||||
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_meshms_messagelist_json_content);
|
||||
return 1;
|
||||
}
|
||||
@ -402,7 +398,7 @@ static int restful_meshms_messagelist_json_content_chunk(struct http_request *hr
|
||||
r->u.msglist.token_which_ply = r->u.msglist.latest_which_ply;
|
||||
r->u.msglist.token_offset = r->u.msglist.latest_offset;
|
||||
meshms_message_iterator_close(&r->u.msglist.iter);
|
||||
time_ms_t wake_at = now + config.rhizome.api.restful.newsince_poll_ms;
|
||||
time_ms_t wake_at = now + config.api.restful.newsince_poll_ms;
|
||||
if (wake_at > r->u.msglist.end_time)
|
||||
wake_at = r->u.msglist.end_time;
|
||||
http_request_pause_response(&r->http, wake_at);
|
||||
|
83
rhizome.c
83
rhizome.c
@ -158,31 +158,27 @@ enum rhizome_bundle_status rhizome_bundle_import_files(rhizome_manifest *m, rhiz
|
||||
)
|
||||
return RHIZOME_BUNDLE_STATUS_INVALID;
|
||||
enum rhizome_bundle_status status = rhizome_manifest_check_stored(m, mout);
|
||||
if (status == RHIZOME_BUNDLE_STATUS_NEW) {
|
||||
enum rhizome_payload_status pstatus = rhizome_import_payload_from_file(m, filepath);
|
||||
switch (pstatus) {
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
if (rhizome_store_manifest(m) == -1)
|
||||
return -1;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
|
||||
status = RHIZOME_BUNDLE_STATUS_DONOTWANT;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
if (status != RHIZOME_BUNDLE_STATUS_NEW)
|
||||
return status;
|
||||
enum rhizome_payload_status pstatus = rhizome_import_payload_from_file(m, filepath);
|
||||
switch (pstatus) {
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
if (rhizome_store_manifest(m) == -1)
|
||||
return -1;
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
|
||||
break;
|
||||
default:
|
||||
FATALF("pstatus = %d", pstatus);
|
||||
}
|
||||
return status;
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
return RHIZOME_BUNDLE_STATUS_NO_ROOM;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
return -1;
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
return RHIZOME_BUNDLE_STATUS_INCONSISTENT;
|
||||
}
|
||||
return status;
|
||||
FATALF("rhizome_import_payload_from_file() returned status = %d", pstatus);
|
||||
}
|
||||
|
||||
/* Sets the bundle key "BK" field of a manifest. Returns 1 if the field was set, 0 if not.
|
||||
@ -325,8 +321,12 @@ enum rhizome_bundle_status rhizome_manifest_check_stored(rhizome_manifest *m, rh
|
||||
|
||||
enum rhizome_bundle_status rhizome_add_manifest(rhizome_manifest *m, rhizome_manifest **mout)
|
||||
{
|
||||
if (config.debug.rhizome)
|
||||
DEBUGF("rhizome_add_manifest(m=manifest[%d](%p), mout=%p)", m->manifest_record_number, m, mout);
|
||||
if (config.debug.rhizome) {
|
||||
if (mout == NULL)
|
||||
DEBUGF("rhizome_add_manifest(m=manifest[%d](%p), mout=NULL)", m->manifest_record_number, m);
|
||||
else
|
||||
DEBUGF("rhizome_add_manifest(m=manifest[%d](%p), *mout=manifest[%d](%p))", m->manifest_record_number, m, *mout ? (*mout)->manifest_record_number : -1, *mout);
|
||||
}
|
||||
if (!m->finalised && !rhizome_manifest_validate(m))
|
||||
return RHIZOME_BUNDLE_STATUS_INVALID;
|
||||
assert(m->finalised);
|
||||
@ -351,3 +351,36 @@ int rhizome_saw_voice_traffic()
|
||||
rhizome_voice_timeout=gettime_ms()+1000;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *rhizome_bundle_status_message(enum rhizome_bundle_status status)
|
||||
{
|
||||
switch (status) {
|
||||
case RHIZOME_BUNDLE_STATUS_NEW: return "Bundle new to store";
|
||||
case RHIZOME_BUNDLE_STATUS_SAME: return "Bundle already in store";
|
||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE: return "Duplicate bundle already in store";
|
||||
case RHIZOME_BUNDLE_STATUS_OLD: return "Newer bundle already in store";
|
||||
case RHIZOME_BUNDLE_STATUS_INVALID: return "Invalid manifest";
|
||||
case RHIZOME_BUNDLE_STATUS_FAKE: return "Manifest signature does not verify";
|
||||
case RHIZOME_BUNDLE_STATUS_INCONSISTENT: return "Manifest inconsistent with supplied payload";
|
||||
case RHIZOME_BUNDLE_STATUS_NO_ROOM: return "No room in store for bundle";
|
||||
case RHIZOME_BUNDLE_STATUS_READONLY: return "Bundle is read-only";
|
||||
case RHIZOME_BUNDLE_STATUS_ERROR: return "Internal error";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *rhizome_payload_status_message(enum rhizome_payload_status status)
|
||||
{
|
||||
switch (status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW: return "Payload new to store";
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED: return "Payload already in store";
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY: return "Payload empty";
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG: return "Payload size exceeds store";
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED: return "Payload evicted";
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: return "Payload size contradicts manifest";
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: return "Payload hash contradicts manifest";
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: return "Incorrect bundle secret";
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR: return "Internal error";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
20
rhizome.h
20
rhizome.h
@ -108,7 +108,7 @@ typedef struct rhizome_manifest
|
||||
unsigned char *signatories[MAX_MANIFEST_VARS];
|
||||
uint8_t signatureTypes[MAX_MANIFEST_VARS];
|
||||
|
||||
/* Set to non-zero if a manifest has been parsed that cannot be fully
|
||||
/* Set to non-NULL if a manifest has been parsed that cannot be fully
|
||||
* understood by this version of Rhizome (probably from a future or a very
|
||||
* old past version of Rhizome). During add (local injection), the manifest
|
||||
* should not be imported. During extract (local decode) a warning or error
|
||||
@ -116,7 +116,7 @@ typedef struct rhizome_manifest
|
||||
* transported, imported and exported normally, as long as their signature is
|
||||
* valid.
|
||||
*/
|
||||
unsigned short malformed;
|
||||
const char *malformed;
|
||||
|
||||
/* Set non-zero after variables have been packed and signature blocks
|
||||
* appended. All fields below may not be valid until the manifest has been
|
||||
@ -362,25 +362,35 @@ enum rhizome_bundle_status {
|
||||
RHIZOME_BUNDLE_STATUS_INVALID = 4, // manifest is invalid
|
||||
RHIZOME_BUNDLE_STATUS_FAKE = 5, // manifest signature not valid
|
||||
RHIZOME_BUNDLE_STATUS_INCONSISTENT = 6, // manifest filesize/filehash does not match supplied payload
|
||||
RHIZOME_BUNDLE_STATUS_DONOTWANT = 7, // Wont fit or we already have more important bundles
|
||||
RHIZOME_BUNDLE_STATUS_NO_ROOM = 7, // doesn't fit; store may contain more important bundles
|
||||
RHIZOME_BUNDLE_STATUS_READONLY = 8, // cannot modify manifest; secret unknown
|
||||
};
|
||||
|
||||
#define INVALID_RHIZOME_BUNDLE_STATUS ((enum rhizome_bundle_status)-2)
|
||||
|
||||
const char *rhizome_bundle_status_message(enum rhizome_bundle_status);
|
||||
|
||||
enum rhizome_payload_status {
|
||||
RHIZOME_PAYLOAD_STATUS_ERROR = -1,
|
||||
RHIZOME_PAYLOAD_STATUS_EMPTY = 0, // payload is empty (zero length)
|
||||
RHIZOME_PAYLOAD_STATUS_NEW = 1, // payload is not yet in store
|
||||
RHIZOME_PAYLOAD_STATUS_NEW = 1, // payload is not yet in store (added)
|
||||
RHIZOME_PAYLOAD_STATUS_STORED = 2, // payload is already in store
|
||||
RHIZOME_PAYLOAD_STATUS_WRONG_SIZE = 3, // payload's size does not match manifest
|
||||
RHIZOME_PAYLOAD_STATUS_WRONG_HASH = 4, // payload's hash does not match manifest
|
||||
RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL = 5, // cannot encrypt/decrypt (payload key unknown)
|
||||
RHIZOME_PAYLOAD_STATUS_TOO_BIG = 6, // payload will never fit in our store
|
||||
RHIZOME_PAYLOAD_STATUS_UNINITERESTING = 7, // other payloads in our store are more interesting
|
||||
RHIZOME_PAYLOAD_STATUS_EVICTED = 7, // other payloads in our store are more important
|
||||
};
|
||||
|
||||
#define INVALID_RHIZOME_PAYLOAD_STATUS ((enum rhizome_bundle_status)-2)
|
||||
|
||||
const char *rhizome_payload_status_message(enum rhizome_payload_status);
|
||||
|
||||
int rhizome_write_manifest_file(rhizome_manifest *m, const char *filename, char append);
|
||||
int rhizome_manifest_selfsign(rhizome_manifest *m);
|
||||
int rhizome_read_manifest_from_file(rhizome_manifest *m, const char *filename);
|
||||
int rhizome_manifest_validate(rhizome_manifest *m);
|
||||
const char *rhizome_manifest_validate_reason(rhizome_manifest *m);
|
||||
int rhizome_manifest_parse(rhizome_manifest *m);
|
||||
int rhizome_manifest_verify(rhizome_manifest *m);
|
||||
|
||||
|
124
rhizome_bundle.c
124
rhizome_bundle.c
@ -427,7 +427,7 @@ static void rhizome_manifest_clear(rhizome_manifest *m)
|
||||
free(m->signatories[m->sig_count]);
|
||||
m->signatories[m->sig_count] = NULL;
|
||||
}
|
||||
m->malformed = 0;
|
||||
m->malformed = NULL;
|
||||
m->has_id = 0;
|
||||
m->has_filehash = 0;
|
||||
m->is_journal = 0;
|
||||
@ -542,7 +542,7 @@ int rhizome_manifest_parse(rhizome_manifest *m)
|
||||
assert(m->manifest_body_bytes == 0);
|
||||
assert(m->var_count == 0);
|
||||
assert(!m->finalised);
|
||||
assert(!m->malformed);
|
||||
assert(m->malformed == NULL);
|
||||
assert(!m->has_id);
|
||||
assert(!m->has_filehash);
|
||||
assert(!m->is_journal);
|
||||
@ -742,11 +742,11 @@ int rhizome_manifest_parse(rhizome_manifest *m)
|
||||
reason = "invalid";
|
||||
break;
|
||||
case FIELD_UNKNOWN:
|
||||
++m->malformed;
|
||||
m->malformed = "Unsupported field";
|
||||
reason = "unsupported";
|
||||
break;
|
||||
case FIELD_MALFORMED:
|
||||
++m->malformed;
|
||||
m->malformed = "Invalid field";
|
||||
reason = "invalid";
|
||||
break;
|
||||
default:
|
||||
@ -778,75 +778,62 @@ int rhizome_manifest_parse(rhizome_manifest *m)
|
||||
/* If all essential (transport) fields are present and well formed then sets the m->finalised field
|
||||
* and returns 1, otherwise returns 0.
|
||||
*
|
||||
* Increments m->malformed if any non-essential fields are missing or invalid. It is up to the
|
||||
* caller to check the m->malformed field and decide whether or not to process a malformed manifest.
|
||||
* Sets m->malformed if any non-essential fields are missing or invalid. It is up to the caller to
|
||||
* check m->malformed and decide whether or not to process a malformed manifest.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
int rhizome_manifest_validate(rhizome_manifest *m)
|
||||
{
|
||||
int ret = 1;
|
||||
if (!m->has_id) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Missing 'id' field");
|
||||
ret = 0;
|
||||
}
|
||||
if (m->version == 0) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Missing 'version' field");
|
||||
ret = 0;
|
||||
}
|
||||
if (m->filesize == RHIZOME_SIZE_UNSET) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Missing 'filesize' field");
|
||||
ret = 0;
|
||||
} else if (m->filesize == 0 && m->has_filehash) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Spurious 'filehash' field");
|
||||
ret = 0;
|
||||
} else if (m->filesize != 0 && !m->has_filehash) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Missing 'filehash' field");
|
||||
ret = 0;
|
||||
}
|
||||
// Warn if expected fields are missing or invalid
|
||||
if (m->service == NULL) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Missing 'service' field");
|
||||
++m->malformed;
|
||||
}
|
||||
return rhizome_manifest_validate_reason(m) == NULL ? 1 : 0;
|
||||
}
|
||||
|
||||
/* If all essential (transport) fields are present and well formed then sets the m->finalised field
|
||||
* and returns NULL, otherwise returns a pointer to a static string (not malloc(3)ed) describing the
|
||||
* problem.
|
||||
*
|
||||
* If any non-essential fields are missing or invalid, then sets m->malformed to point to a static
|
||||
* string describing the problem. It is up to the caller to check m->malformed and decide whether
|
||||
* or not to process a malformed manifest.
|
||||
*
|
||||
* @author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
const char *rhizome_manifest_validate_reason(rhizome_manifest *m)
|
||||
{
|
||||
const char *reason = NULL;
|
||||
if (!m->has_id)
|
||||
reason = "Missing 'id' field";
|
||||
else if (m->version == 0)
|
||||
reason = "Missing 'version' field";
|
||||
else if (m->filesize == RHIZOME_SIZE_UNSET)
|
||||
reason = "Missing 'filesize' field";
|
||||
else if (m->filesize == 0 && m->has_filehash)
|
||||
reason = "Spurious 'filehash' field";
|
||||
else if (m->filesize != 0 && !m->has_filehash)
|
||||
reason = "Missing 'filehash' field";
|
||||
if (reason && config.debug.rhizome_manifest)
|
||||
DEBUG(reason);
|
||||
if (m->service == NULL)
|
||||
m->malformed = "Missing 'service' field";
|
||||
else if (strcmp(m->service, RHIZOME_SERVICE_FILE) == 0) {
|
||||
if (m->name == NULL) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Manifest with service='" RHIZOME_SERVICE_FILE "' missing 'name' field");
|
||||
++m->malformed;
|
||||
}
|
||||
if (m->name == NULL)
|
||||
m->malformed = "Manifest with service='" RHIZOME_SERVICE_FILE "' missing 'name' field";
|
||||
} else if (strcmp(m->service, RHIZOME_SERVICE_MESHMS) == 0
|
||||
|| strcmp(m->service, RHIZOME_SERVICE_MESHMS2) == 0
|
||||
) {
|
||||
if (!m->has_sender) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUGF("Manifest with service='%s' missing 'sender' field", m->service);
|
||||
++m->malformed;
|
||||
}
|
||||
if (!m->has_recipient) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUGF("Manifest with service='%s' missing 'recipient' field", m->service);
|
||||
++m->malformed;
|
||||
}
|
||||
if (!m->has_recipient)
|
||||
m->malformed = "Manifest missing 'recipient' field";
|
||||
else if (!m->has_sender)
|
||||
m->malformed = "Manifest missing 'sender' field";
|
||||
}
|
||||
else if (!rhizome_str_is_manifest_service(m->service)) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUGF("Manifest invalid 'service' field %s", alloca_str_toprint(m->service));
|
||||
++m->malformed;
|
||||
}
|
||||
if (!m->has_date) {
|
||||
if (config.debug.rhizome_manifest)
|
||||
DEBUG("Missing 'date' field");
|
||||
++m->malformed;
|
||||
}
|
||||
m->finalised = ret;
|
||||
return ret;
|
||||
else if (!rhizome_str_is_manifest_service(m->service))
|
||||
m->malformed = "Manifest invalid 'service' field";
|
||||
else if (!m->has_date)
|
||||
m->malformed = "Missing 'date' field";
|
||||
if (m->malformed && config.debug.rhizome_manifest)
|
||||
DEBUG(m->malformed);
|
||||
m->finalised = (reason == NULL);
|
||||
return reason;
|
||||
}
|
||||
|
||||
int rhizome_read_manifest_from_file(rhizome_manifest *m, const char *filename)
|
||||
@ -1099,17 +1086,23 @@ int rhizome_manifest_dump(rhizome_manifest *m, const char *msg)
|
||||
enum rhizome_bundle_status rhizome_manifest_finalise(rhizome_manifest *m, rhizome_manifest **mout, int deduplicate)
|
||||
{
|
||||
IN();
|
||||
|
||||
assert(*mout == NULL);
|
||||
if (!m->finalised && !rhizome_manifest_validate(m))
|
||||
RETURN(RHIZOME_BUNDLE_STATUS_INVALID);
|
||||
|
||||
// if a manifest was supplied with an ID, don't bother to check for a duplicate.
|
||||
// we only want to filter out added files with no existing manifest.
|
||||
if (deduplicate && m->haveSecret != EXISTING_BUNDLE_ID) {
|
||||
enum rhizome_bundle_status status = rhizome_find_duplicate(m, mout);
|
||||
switch (status) {
|
||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
||||
assert(*mout != NULL);
|
||||
assert(*mout != m);
|
||||
RETURN(status);
|
||||
case RHIZOME_BUNDLE_STATUS_ERROR:
|
||||
if (*mout != NULL && *mout != m) {
|
||||
rhizome_manifest_free(*mout);
|
||||
*mout = NULL;
|
||||
}
|
||||
RETURN(status);
|
||||
case RHIZOME_BUNDLE_STATUS_NEW:
|
||||
break;
|
||||
@ -1117,6 +1110,7 @@ enum rhizome_bundle_status rhizome_manifest_finalise(rhizome_manifest *m, rhizom
|
||||
FATALF("rhizome_find_duplicate() returned %d", status);
|
||||
}
|
||||
}
|
||||
assert(*mout == NULL);
|
||||
*mout = m;
|
||||
|
||||
/* Convert to final form for signing and writing to disk */
|
||||
|
@ -600,7 +600,6 @@ int rhizome_crypt_xor_block(unsigned char *buffer, size_t buffer_size, uint64_t
|
||||
*/
|
||||
int rhizome_derive_payload_key(rhizome_manifest *m)
|
||||
{
|
||||
// don't do anything if the manifest isn't flagged as being encrypted
|
||||
assert(m->payloadEncryption == PAYLOAD_ENCRYPTED);
|
||||
unsigned char hash[crypto_hash_sha512_BYTES];
|
||||
if (m->has_sender && m->has_recipient) {
|
||||
|
@ -1281,10 +1281,7 @@ int rhizome_cleanup(struct rhizome_cleanup_report *report)
|
||||
int rhizome_store_manifest(rhizome_manifest *m)
|
||||
{
|
||||
assert(m->finalised);
|
||||
|
||||
// If we don't have the secret for this manifest, only store it if its self-signature is valid
|
||||
if (!m->haveSecret && !m->selfSigned)
|
||||
return WHY("Manifest is not signed, and I don't have the key. Manifest might be forged or corrupt.");
|
||||
assert(m->haveSecret || m->selfSigned); // should not store an invalid or fake manifest
|
||||
|
||||
/* Bind BAR to data field */
|
||||
rhizome_bar_t bar;
|
||||
|
@ -119,9 +119,10 @@ static int rhizome_direct_import_end(struct http_request *hr)
|
||||
case RHIZOME_BUNDLE_STATUS_FAKE:
|
||||
http_request_simple_response(&r->http, 403, "Manifest not signed");
|
||||
return 0;
|
||||
case RHIZOME_BUNDLE_STATUS_DONOTWANT:
|
||||
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
||||
http_request_simple_response(&r->http, 403, "Not enough space");
|
||||
return 0;
|
||||
case RHIZOME_BUNDLE_STATUS_READONLY:
|
||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
||||
case RHIZOME_BUNDLE_STATUS_ERROR:
|
||||
break;
|
||||
@ -729,17 +730,20 @@ void rhizome_direct_http_dispatch(rhizome_direct_sync_request *r)
|
||||
switch (pstatus) {
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
break;
|
||||
goto pstatus_ok;
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
goto closeit;
|
||||
default:
|
||||
FATALF("pstatus = %d", pstatus);
|
||||
// No "default" label, so the compiler will warn us if a case is not handled.
|
||||
}
|
||||
|
||||
FATALF("pstatus = %d", pstatus);
|
||||
pstatus_ok:
|
||||
;
|
||||
uint64_t read_ofs;
|
||||
for(read_ofs=0;read_ofs<m->filesize;){
|
||||
unsigned char buffer[4096];
|
||||
|
@ -553,10 +553,10 @@ schedule_fetch(struct rhizome_fetch_slot *slot)
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
RETURN(IMPORTED);
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
RETURN(DONOTWANT);
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
break;
|
||||
goto status_ok;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
RETURN(WHY("error writing new payload"));
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
@ -565,9 +565,11 @@ schedule_fetch(struct rhizome_fetch_slot *slot)
|
||||
RETURN(WHY("payload hash does not match"));
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
RETURN(WHY("payload cannot be encrypted"));
|
||||
default:
|
||||
FATALF("status = %d", status);
|
||||
// No "default" label, so the compiler will warn if a case is not handled.
|
||||
}
|
||||
FATALF("status = %d", status);
|
||||
status_ok:
|
||||
;
|
||||
} else {
|
||||
strbuf r = strbuf_local(slot->request, sizeof slot->request);
|
||||
strbuf_sprintf(r, "GET /rhizome/manifestbyprefix/%s HTTP/1.0\r\n\r\n", alloca_tohex(slot->bid.binary, slot->prefix_length));
|
||||
|
@ -63,18 +63,100 @@ static int strn_to_list_token(const char *str, uint64_t *rowidp, const char **af
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int http_request_rhizome_response(struct httpd_request *r, uint16_t result, const char *message, const char *payload_status_message)
|
||||
{
|
||||
uint16_t rhizome_result = 0;
|
||||
switch (r->bundle_status) {
|
||||
case RHIZOME_BUNDLE_STATUS_NEW:
|
||||
rhizome_result = 201;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_SAME:
|
||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
||||
case RHIZOME_BUNDLE_STATUS_OLD:
|
||||
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
||||
rhizome_result = 200;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_INVALID:
|
||||
case RHIZOME_BUNDLE_STATUS_FAKE:
|
||||
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
|
||||
case RHIZOME_BUNDLE_STATUS_READONLY:
|
||||
rhizome_result = 403;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_ERROR:
|
||||
rhizome_result = 500;
|
||||
break;
|
||||
}
|
||||
if (rhizome_result) {
|
||||
r->http.response.result_extra[0].label = "rhizome_bundle_status_code";
|
||||
r->http.response.result_extra[0].value.type = JSON_INTEGER;
|
||||
r->http.response.result_extra[0].value.u.integer = r->bundle_status;
|
||||
const char *status_message = rhizome_bundle_status_message(r->bundle_status);
|
||||
if (status_message) {
|
||||
r->http.response.result_extra[1].label = "rhizome_bundle_status_message";
|
||||
r->http.response.result_extra[1].value.type = JSON_STRING_NULTERM;
|
||||
r->http.response.result_extra[1].value.u.string.content = status_message;
|
||||
}
|
||||
if (rhizome_result > result) {
|
||||
result = rhizome_result;
|
||||
message = NULL;
|
||||
}
|
||||
}
|
||||
rhizome_result = 0;
|
||||
switch (r->payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
rhizome_result = 201;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
rhizome_result = 200;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
rhizome_result = 403;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
rhizome_result = 500;
|
||||
break;
|
||||
}
|
||||
if (rhizome_result) {
|
||||
r->http.response.result_extra[2].label = "rhizome_payload_status_code";
|
||||
r->http.response.result_extra[2].value.type = JSON_INTEGER;
|
||||
r->http.response.result_extra[2].value.u.integer = r->payload_status;
|
||||
const char *status_message = payload_status_message ? payload_status_message : rhizome_payload_status_message(r->payload_status);
|
||||
if (status_message) {
|
||||
r->http.response.result_extra[3].label = "rhizome_payload_status_message";
|
||||
r->http.response.result_extra[3].value.type = JSON_STRING_NULTERM;
|
||||
r->http.response.result_extra[3].value.u.string.content = status_message;
|
||||
}
|
||||
if (rhizome_result > result) {
|
||||
result = rhizome_result;
|
||||
message = NULL;
|
||||
}
|
||||
}
|
||||
if (result == 0) {
|
||||
result = 500;
|
||||
message = NULL;
|
||||
}
|
||||
http_request_simple_response(&r->http, result, message ? message : result == 403 ? "Rhizome operation failed" : NULL);
|
||||
return result;
|
||||
}
|
||||
|
||||
static HTTP_CONTENT_GENERATOR restful_rhizome_bundlelist_json_content;
|
||||
|
||||
int restful_rhizome_bundlelist_json(httpd_request *r, const char *remainder)
|
||||
{
|
||||
r->http.response.header.content_type = CONTENT_TYPE_JSON;
|
||||
r->http.render_extra_headers = render_manifest_headers;
|
||||
if (!is_rhizome_http_enabled())
|
||||
return 403;
|
||||
if (*remainder)
|
||||
return 404;
|
||||
if (r->http.verb != HTTP_VERB_GET)
|
||||
return 405;
|
||||
int ret = authorize(&r->http);
|
||||
int ret = authorize_restful(&r->http);
|
||||
if (ret)
|
||||
return ret;
|
||||
r->u.rhlist.phase = LIST_HEADER;
|
||||
@ -108,14 +190,14 @@ int restful_rhizome_newsince(httpd_request *r, const char *remainder)
|
||||
return 404;
|
||||
if (r->http.verb != HTTP_VERB_GET)
|
||||
return 405;
|
||||
int ret = authorize(&r->http);
|
||||
int ret = authorize_restful(&r->http);
|
||||
if (ret)
|
||||
return ret;
|
||||
r->u.rhlist.phase = LIST_HEADER;
|
||||
r->u.rhlist.rowcount = 0;
|
||||
bzero(&r->u.rhlist.cursor, sizeof r->u.rhlist.cursor);
|
||||
r->u.rhlist.cursor.rowid_since = rowid;
|
||||
r->u.rhlist.end_time = gettime_ms() + config.rhizome.api.restful.newsince_timeout * 1000;
|
||||
r->u.rhlist.end_time = gettime_ms() + config.api.restful.newsince_timeout * 1000;
|
||||
http_request_response_generated(&r->http, 200, CONTENT_TYPE_JSON, restful_rhizome_bundlelist_json_content);
|
||||
return 1;
|
||||
}
|
||||
@ -163,7 +245,7 @@ static int restful_rhizome_bundlelist_json_content_chunk(struct http_request *hr
|
||||
r->u.rhlist.phase = LIST_END;
|
||||
return 1;
|
||||
}
|
||||
time_ms_t wake_at = now + config.rhizome.api.restful.newsince_poll_ms;
|
||||
time_ms_t wake_at = now + config.api.restful.newsince_poll_ms;
|
||||
if (wake_at > r->u.rhlist.end_time)
|
||||
wake_at = r->u.rhlist.end_time;
|
||||
http_request_pause_response(&r->http, wake_at);
|
||||
@ -242,13 +324,14 @@ static int insert_mime_part_body(struct http_request *, char *, size_t);
|
||||
int restful_rhizome_insert(httpd_request *r, const char *remainder)
|
||||
{
|
||||
r->http.response.header.content_type = CONTENT_TYPE_JSON;
|
||||
r->http.render_extra_headers = render_manifest_headers;
|
||||
if (*remainder)
|
||||
return 404;
|
||||
if (!is_rhizome_http_enabled())
|
||||
return 403;
|
||||
if (r->http.verb != HTTP_VERB_POST)
|
||||
return 405;
|
||||
int ret = authorize(&r->http);
|
||||
int ret = authorize_restful(&r->http);
|
||||
if (ret)
|
||||
return ret;
|
||||
// Parse the request body as multipart/form-data.
|
||||
@ -293,17 +376,20 @@ static int insert_make_manifest(httpd_request *r)
|
||||
r->manifest->manifest_all_bytes = r->u.insert.manifest.length;
|
||||
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;
|
||||
rhizome_manifest_free(r->manifest);
|
||||
r->manifest = NULL;
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_INVALID;
|
||||
return http_request_rhizome_response(r, 403, "Malformed manifest", NULL);
|
||||
default:
|
||||
WHYF("rhizome_manifest_parse() returned %d", n);
|
||||
// fall through
|
||||
case -1:
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -332,10 +418,12 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa
|
||||
if (r->u.insert.received_manifest)
|
||||
return http_response_form_part(r, "Duplicate", PART_MANIFEST, NULL, 0);
|
||||
form_buf_malloc_init(&r->u.insert.manifest, MAX_MANIFEST_BYTES);
|
||||
if ( strcmp(h->content_type.type, "rhizome-manifest") != 0
|
||||
|| strcmp(h->content_type.subtype, "text") != 0
|
||||
if ( strcmp(h->content_type.type, "rhizome") != 0
|
||||
|| strcmp(h->content_type.subtype, "manifest") != 0
|
||||
)
|
||||
return http_response_form_part(r, "Unsupported Content-Type in", PART_MANIFEST, NULL, 0);
|
||||
if (strcmp(h->content_type.format, "text+binarysig") != 0)
|
||||
return http_response_form_part(r, "Unsupported rhizome/manifest format in", PART_MANIFEST, NULL, 0);
|
||||
r->u.insert.current_part = PART_MANIFEST;
|
||||
}
|
||||
else if (strcmp(h->content_disposition.name, PART_PAYLOAD) == 0) {
|
||||
@ -355,17 +443,17 @@ static int insert_mime_part_header(struct http_request *hr, const struct mime_pa
|
||||
rhizome_manifest_set_name_from_path(r->manifest, h->content_disposition.filename);
|
||||
// Start writing the payload content into the Rhizome store. Note: r->manifest->filesize can be
|
||||
// RHIZOME_SIZE_UNSET at this point, if the manifest did not contain a 'filesize' field.
|
||||
r->u.insert.payload_status = rhizome_write_open_manifest(&r->u.insert.write, r->manifest);
|
||||
r->payload_status = rhizome_write_open_manifest(&r->u.insert.write, r->manifest);
|
||||
r->u.insert.payload_size = 0;
|
||||
switch (r->u.insert.payload_status) {
|
||||
switch (r->payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
WHYF("rhizome_write_open_manifest() returned %d", r->u.insert.payload_status);
|
||||
WHYF("rhizome_write_open_manifest() returned %d", r->payload_status);
|
||||
return 500;
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
// TODO: initialise payload hash so it can be compared with stored payload
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
break; // r->payload_status gets dealt with later
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -395,7 +483,7 @@ static int insert_mime_part_body(struct http_request *hr, char *buf, size_t len)
|
||||
}
|
||||
else if (r->u.insert.current_part == PART_PAYLOAD) {
|
||||
r->u.insert.payload_size += len;
|
||||
switch (r->u.insert.payload_status) {
|
||||
switch (r->payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
if (rhizome_write_buffer(&r->u.insert.write, (unsigned char *)buf, len) == -1)
|
||||
return 500;
|
||||
@ -434,6 +522,8 @@ static int insert_mime_part_end(struct http_request *hr)
|
||||
}
|
||||
else if (r->u.insert.current_part == PART_MANIFEST) {
|
||||
r->u.insert.received_manifest = 1;
|
||||
if (config.debug.rhizome)
|
||||
DEBUGF("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;
|
||||
@ -445,19 +535,25 @@ static int insert_mime_part_end(struct http_request *hr)
|
||||
WHY("rhizome_fill_manifest() failed");
|
||||
return 500;
|
||||
}
|
||||
if (r->manifest->is_journal) {
|
||||
http_request_simple_response(&r->http, 403, "Insert not supported for journals");
|
||||
return 403;
|
||||
}
|
||||
if (r->manifest->is_journal)
|
||||
return http_request_rhizome_response(r, 501, "Insert not supported for journals", NULL);
|
||||
assert(r->manifest != NULL);
|
||||
}
|
||||
else if (r->u.insert.current_part == PART_PAYLOAD) {
|
||||
r->u.insert.received_payload = 1;
|
||||
if (r->u.insert.payload_status == RHIZOME_PAYLOAD_STATUS_NEW)
|
||||
r->u.insert.payload_status = rhizome_finish_write(&r->u.insert.write);
|
||||
if (r->u.insert.payload_status == RHIZOME_PAYLOAD_STATUS_ERROR) {
|
||||
WHYF("rhizome_finish_write() returned status = %d", r->u.insert.payload_status);
|
||||
return 500;
|
||||
switch (r->payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
r->payload_status = rhizome_finish_write(&r->u.insert.write);
|
||||
if (r->payload_status == RHIZOME_PAYLOAD_STATUS_ERROR) {
|
||||
WHYF("rhizome_finish_write() returned status = %d", r->payload_status);
|
||||
return 500;
|
||||
}
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
// TODO: finish calculating payload hash and compare it with stored payload
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else
|
||||
FATALF("current_part = %s", alloca_str_toprint(r->u.insert.current_part));
|
||||
@ -475,40 +571,49 @@ static int restful_rhizome_insert_end(struct http_request *hr)
|
||||
// Fill in the missing manifest fields and ensure payload and manifest are consistent.
|
||||
assert(r->manifest != NULL);
|
||||
assert(r->u.insert.write.file_length != RHIZOME_SIZE_UNSET);
|
||||
switch (r->u.insert.payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
return 500;
|
||||
int status_valid = 0;
|
||||
if (config.debug.rhizome)
|
||||
DEBUGF("r->payload_status=%d", r->payload_status);
|
||||
switch (r->payload_status) {
|
||||
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:
|
||||
assert(r->manifest->filesize != RHIZOME_SIZE_UNSET);
|
||||
// TODO: check that stored hash matches received payload's hash
|
||||
// fall through
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
assert(r->manifest->filesize != RHIZOME_SIZE_UNSET);
|
||||
status_valid = 1;
|
||||
if (r->manifest->filesize == RHIZOME_SIZE_UNSET)
|
||||
rhizome_manifest_set_filesize(r->manifest, 0);
|
||||
if (r->u.insert.payload_size == r->manifest->filesize)
|
||||
break;
|
||||
// fall through
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
r->payload_status = RHIZOME_PAYLOAD_STATUS_WRONG_SIZE;
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
|
||||
{
|
||||
strbuf msg = strbuf_alloca(200);
|
||||
strbuf_sprintf(msg, "Payload size (%"PRIu64") contradicts manifest (filesize=%"PRIu64")", r->u.insert.payload_size, r->manifest->filesize);
|
||||
http_request_simple_response(&r->http, 403, strbuf_str(msg));
|
||||
return 403;
|
||||
return http_request_rhizome_response(r, 403, NULL, strbuf_str(msg));
|
||||
}
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
|
||||
http_request_simple_response(&r->http, 403, "Not enough space");
|
||||
return 403;
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
http_request_simple_response(&r->http, 403, "Payload hash contradicts manifest");
|
||||
return 403;
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
|
||||
return http_request_rhizome_response(r, 403, NULL, NULL);
|
||||
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);
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY;
|
||||
return http_request_rhizome_response(r, 403, "Missing bundle secret", NULL);
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_NO_ROOM;
|
||||
// fall through
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
return http_request_rhizome_response(r, 403, NULL, NULL);
|
||||
}
|
||||
if (!status_valid) {
|
||||
WHYF("r->payload_status = %d", r->payload_status);
|
||||
return http_request_rhizome_response(r, 500, NULL, NULL);
|
||||
}
|
||||
// Finalise the manifest and add it to the store.
|
||||
if (r->manifest->filesize) {
|
||||
@ -517,44 +622,54 @@ static int restful_rhizome_insert_end(struct http_request *hr)
|
||||
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;
|
||||
const char *invalid_reason = rhizome_manifest_validate_reason(r->manifest);
|
||||
if (invalid_reason) {
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_INVALID;
|
||||
return http_request_rhizome_response(r, 403, invalid_reason, NULL);
|
||||
}
|
||||
if (r->manifest->malformed) {
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_INVALID;
|
||||
return http_request_rhizome_response(r, 403, r->manifest->malformed, NULL);
|
||||
}
|
||||
if (!r->manifest->haveSecret) {
|
||||
http_request_simple_response(&r->http, 403, "Missing bundle secret");
|
||||
return 403;
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_READONLY;
|
||||
return http_request_rhizome_response(r, 403, "Missing bundle secret", NULL);
|
||||
}
|
||||
rhizome_manifest *mout = NULL;
|
||||
int result;
|
||||
switch (rhizome_manifest_finalise(r->manifest, &mout, !r->u.insert.force_new)) {
|
||||
r->bundle_status = rhizome_manifest_finalise(r->manifest, &mout, !r->u.insert.force_new);
|
||||
int result = 500;
|
||||
if (config.debug.rhizome)
|
||||
DEBUGF("r->bundle_status=%d", r->bundle_status);
|
||||
switch (r->bundle_status) {
|
||||
case RHIZOME_BUNDLE_STATUS_NEW:
|
||||
result = 201;
|
||||
if (mout && mout != r->manifest)
|
||||
rhizome_manifest_free(mout);
|
||||
mout = NULL;
|
||||
result = 201;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_SAME:
|
||||
case RHIZOME_BUNDLE_STATUS_OLD:
|
||||
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
||||
if (mout && mout != r->manifest) {
|
||||
rhizome_manifest_free(r->manifest);
|
||||
r->manifest = mout;
|
||||
}
|
||||
result = 200;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_INVALID:
|
||||
result = 403;
|
||||
break;
|
||||
case RHIZOME_BUNDLE_STATUS_FAKE:
|
||||
case RHIZOME_BUNDLE_STATUS_INCONSISTENT:
|
||||
case RHIZOME_BUNDLE_STATUS_NO_ROOM:
|
||||
case RHIZOME_BUNDLE_STATUS_READONLY:
|
||||
case RHIZOME_BUNDLE_STATUS_ERROR:
|
||||
default:
|
||||
result = 500;
|
||||
break;
|
||||
if (mout && mout != r->manifest)
|
||||
rhizome_manifest_free(mout);
|
||||
rhizome_manifest_free(r->manifest);
|
||||
r->manifest = NULL;
|
||||
return http_request_rhizome_response(r, 0, NULL, NULL);
|
||||
}
|
||||
if (mout && mout != r->manifest) {
|
||||
rhizome_manifest_free(r->manifest);
|
||||
r->manifest = mout;
|
||||
}
|
||||
if (result >= 400)
|
||||
return result;
|
||||
if (result == 500)
|
||||
FATALF("rhizome_manifest_finalise() returned status = %d", r->bundle_status);
|
||||
rhizome_authenticate_author(r->manifest);
|
||||
r->http.render_extra_headers = render_manifest_headers;
|
||||
http_request_response_static(&r->http, result, "rhizome-manifest/text",
|
||||
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
|
||||
);
|
||||
@ -568,6 +683,7 @@ static HTTP_HANDLER restful_rhizome_bid_decrypted_bin;
|
||||
int restful_rhizome_(httpd_request *r, const char *remainder)
|
||||
{
|
||||
r->http.response.header.content_type = CONTENT_TYPE_JSON;
|
||||
r->http.render_extra_headers = render_manifest_headers;
|
||||
if (!is_rhizome_http_enabled())
|
||||
return 403;
|
||||
HTTP_HANDLER *handler = NULL;
|
||||
@ -589,20 +705,25 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
|
||||
return 404;
|
||||
if (r->http.verb != HTTP_VERB_GET)
|
||||
return 405;
|
||||
int ret = authorize(&r->http);
|
||||
int ret = authorize_restful(&r->http);
|
||||
if (ret)
|
||||
return ret;
|
||||
if ((r->manifest = rhizome_new_manifest()) == NULL)
|
||||
return 500;
|
||||
ret = rhizome_retrieve_manifest(&bid, r->manifest);
|
||||
if (ret == -1)
|
||||
if (ret == -1) {
|
||||
rhizome_manifest_free(r->manifest);
|
||||
r->manifest = NULL;
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_ERROR;
|
||||
return 500;
|
||||
}
|
||||
if (ret == 0) {
|
||||
rhizome_authenticate_author(r->manifest);
|
||||
r->http.render_extra_headers = render_manifest_headers;
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_SAME;
|
||||
} else {
|
||||
assert(r->manifest == NULL);
|
||||
assert(r->http.render_extra_headers == NULL);
|
||||
r->bundle_status = RHIZOME_BUNDLE_STATUS_NEW;
|
||||
rhizome_manifest_free(r->manifest);
|
||||
r->manifest = NULL;
|
||||
}
|
||||
ret = handler(r, remainder);
|
||||
return ret;
|
||||
@ -610,8 +731,10 @@ int restful_rhizome_(httpd_request *r, const char *remainder)
|
||||
|
||||
static int restful_rhizome_bid_rhm(httpd_request *r, const char *remainder)
|
||||
{
|
||||
if (*remainder || r->manifest == NULL)
|
||||
if (*remainder)
|
||||
return 404;
|
||||
if (r->manifest == NULL)
|
||||
return http_request_rhizome_response(r, 403, NULL, NULL);
|
||||
http_request_response_static(&r->http, 200, "rhizome-manifest/text",
|
||||
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
|
||||
);
|
||||
@ -620,23 +743,27 @@ static int restful_rhizome_bid_rhm(httpd_request *r, const char *remainder)
|
||||
|
||||
static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder)
|
||||
{
|
||||
if (*remainder || r->manifest == NULL)
|
||||
if (*remainder)
|
||||
return 404;
|
||||
if (r->manifest == NULL)
|
||||
return http_request_rhizome_response(r, 403, NULL, NULL);
|
||||
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;
|
||||
return http_request_rhizome_response(r, ret, NULL, NULL);
|
||||
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 || r->manifest == NULL)
|
||||
if (*remainder)
|
||||
return 404;
|
||||
if (r->manifest == NULL)
|
||||
return http_request_rhizome_response(r, 403, NULL, NULL);
|
||||
if (r->manifest->filesize == 0) {
|
||||
// TODO use Content Type from manifest (once it is implemented)
|
||||
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);
|
||||
@ -644,7 +771,7 @@ static int restful_rhizome_bid_decrypted_bin(httpd_request *r, const char *remai
|
||||
}
|
||||
int ret = rhizome_response_content_init_payload(r, r->manifest);
|
||||
if (ret)
|
||||
return ret;
|
||||
return http_request_rhizome_response(r, ret, NULL, NULL);
|
||||
// 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;
|
||||
@ -681,22 +808,22 @@ int rhizome_response_content_init_filehash(httpd_request *r, const rhizome_fileh
|
||||
r->u.read_state.blob_fd = -1;
|
||||
assert(r->finalise_union == NULL);
|
||||
r->finalise_union = finalise_union_read_state;
|
||||
enum rhizome_payload_status status = rhizome_open_read(&r->u.read_state, hash);
|
||||
switch (status) {
|
||||
r->payload_status = rhizome_open_read(&r->u.read_state, hash);
|
||||
switch (r->payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
break;
|
||||
return rhizome_response_content_init_read_state(r);
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
return 404;
|
||||
return 403;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
return -1;
|
||||
default:
|
||||
FATALF("status = %d", status);
|
||||
}
|
||||
return rhizome_response_content_init_read_state(r);
|
||||
FATALF("rhizome_open_read() returned status = %d", r->payload_status);
|
||||
}
|
||||
|
||||
int rhizome_response_content_init_payload(httpd_request *r, rhizome_manifest *m)
|
||||
@ -705,22 +832,22 @@ int rhizome_response_content_init_payload(httpd_request *r, rhizome_manifest *m)
|
||||
r->u.read_state.blob_fd = -1;
|
||||
assert(r->finalise_union == NULL);
|
||||
r->finalise_union = finalise_union_read_state;
|
||||
enum rhizome_payload_status status = rhizome_open_decrypt_read(m, &r->u.read_state);
|
||||
switch (status) {
|
||||
r->payload_status = rhizome_open_decrypt_read(m, &r->u.read_state);
|
||||
switch (r->payload_status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
break;
|
||||
return rhizome_response_content_init_read_state(r);
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
return 404;
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
return 403;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
return -1;
|
||||
default:
|
||||
FATALF("status = %d", status);
|
||||
}
|
||||
return rhizome_response_content_init_read_state(r);
|
||||
FATALF("rhizome_open_decrypt_read() returned status = %d", r->payload_status);
|
||||
}
|
||||
|
||||
int rhizome_payload_content(struct http_request *hr, unsigned char *buf, size_t bufsz, struct http_content_generator_result *result)
|
||||
@ -754,33 +881,63 @@ int rhizome_payload_content(struct http_request *hr, unsigned char *buf, size_t
|
||||
static void render_manifest_headers(struct http_request *hr, strbuf sb)
|
||||
{
|
||||
httpd_request *r = (httpd_request *) hr;
|
||||
const char *status_message;
|
||||
if ((status_message = rhizome_bundle_status_message(r->bundle_status))) {
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Result-Bundle-Status-Code: %d\r\n", r->bundle_status);
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Result-Bundle-Status-Message: %s\r\n", status_message);
|
||||
}
|
||||
if ((status_message = rhizome_payload_status_message(r->payload_status))) {
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Result-Payload-Status-Code: %d\r\n", r->payload_status);
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Result-Payload-Status-Message: %s\r\n", status_message);
|
||||
}
|
||||
rhizome_manifest *m = r->manifest;
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->cryptoSignPublic));
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Version: %"PRIu64"\r\n", m->version);
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize);
|
||||
if (m->filesize != 0)
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filehash: %s\r\n", alloca_tohex_rhizome_filehash_t(m->filehash));
|
||||
if (m->has_bundle_key)
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-BK: %s\r\n", alloca_tohex_rhizome_bk_t(m->bundle_key));
|
||||
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) {
|
||||
if (m->has_id)
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Id: %s\r\n", alloca_tohex_rhizome_bid_t(m->cryptoSignPublic));
|
||||
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->cryptoSignSecret, 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);
|
||||
}
|
||||
if (m->service)
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Service: %s\r\n", m->service);
|
||||
assert(m->authorship != AUTHOR_LOCAL);
|
||||
if (m->authorship == AUTHOR_AUTHENTIC)
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Author: %s\r\n", alloca_tohex_sid_t(m->author));
|
||||
assert(m->haveSecret);
|
||||
{
|
||||
char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1];
|
||||
rhizome_bytes_to_hex_upper(m->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES);
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Secret: %s\r\n", secret);
|
||||
}
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Rowid: %"PRIu64"\r\n", m->rowid);
|
||||
strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Inserttime: %"PRIu64"\r\n", m->inserttime);
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizo
|
||||
DEBUGF("Not enough space for %"PRIu64". Used; %"PRIu64" = %"PRIu64" + %"PRIu64" * (%"PRIu64" - %"PRIu64"), Limit; %"PRIu64,
|
||||
bytes, db_used, external_bytes, db_page_size, db_page_count, db_free_page_count, limit);
|
||||
|
||||
return RHIZOME_PAYLOAD_STATUS_UNINITERESTING;
|
||||
return RHIZOME_PAYLOAD_STATUS_EVICTED;
|
||||
}
|
||||
|
||||
int rhizome_store_cleanup(struct rhizome_cleanup_report *report)
|
||||
@ -679,14 +679,18 @@ enum rhizome_payload_status rhizome_finish_write(struct rhizome_write *write)
|
||||
{
|
||||
enum rhizome_payload_status status = RHIZOME_PAYLOAD_STATUS_NEW;
|
||||
|
||||
// Once the whole file has been processed, we should finally know its.
|
||||
// Once the whole file has been processed, we should finally know its length
|
||||
if (write->file_length == RHIZOME_SIZE_UNSET) {
|
||||
if (config.debug.rhizome_store)
|
||||
DEBUGF("Wrote %"PRIu64" bytes, set file_length", write->file_offset);
|
||||
write->file_length = write->file_offset;
|
||||
status = store_make_space(write->file_length, NULL);
|
||||
if (status!=RHIZOME_PAYLOAD_STATUS_NEW)
|
||||
goto failure;
|
||||
if (write->file_length == 0)
|
||||
status = RHIZOME_PAYLOAD_STATUS_EMPTY;
|
||||
else {
|
||||
status = store_make_space(write->file_length, NULL);
|
||||
if (status != RHIZOME_PAYLOAD_STATUS_NEW)
|
||||
goto failure;
|
||||
}
|
||||
}
|
||||
|
||||
// flush out any remaining buffered pieces to disk
|
||||
@ -709,7 +713,7 @@ enum rhizome_payload_status rhizome_finish_write(struct rhizome_write *write)
|
||||
}
|
||||
assert(write->file_offset == write->file_length);
|
||||
|
||||
if (write->file_length==0){
|
||||
if (write->file_length == 0) {
|
||||
// whoops, no payload, don't store anything
|
||||
if (config.debug.rhizome_store)
|
||||
DEBUGF("Ignoring empty write");
|
||||
@ -966,21 +970,23 @@ enum rhizome_payload_status rhizome_store_payload_file(rhizome_manifest *m, cons
|
||||
struct rhizome_write write;
|
||||
bzero(&write, sizeof(write));
|
||||
enum rhizome_payload_status status = rhizome_write_open_manifest(&write, m);
|
||||
int status_ok = 0;
|
||||
switch (status) {
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
status_ok = 1;
|
||||
break;
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
return status;
|
||||
default:
|
||||
FATALF("status = %d", status);
|
||||
}
|
||||
if (!status_ok)
|
||||
FATALF("rhizome_write_open_manifest() returned status = %d", status);
|
||||
if (rhizome_write_file(&write, filepath) == -1) {
|
||||
rhizome_fail_write(&write);
|
||||
return RHIZOME_PAYLOAD_STATUS_ERROR;
|
||||
@ -990,26 +996,24 @@ enum rhizome_payload_status rhizome_store_payload_file(rhizome_manifest *m, cons
|
||||
case RHIZOME_PAYLOAD_STATUS_EMPTY:
|
||||
assert(write.file_length == 0);
|
||||
assert(m->filesize == 0);
|
||||
break;
|
||||
return status;
|
||||
case RHIZOME_PAYLOAD_STATUS_NEW:
|
||||
assert(m->filesize == write.file_length);
|
||||
if (m->has_filehash)
|
||||
assert(cmp_rhizome_filehash_t(&m->filehash, &write.id) == 0);
|
||||
else
|
||||
rhizome_manifest_set_filehash(m, &write.id);
|
||||
break;
|
||||
return status;
|
||||
case RHIZOME_PAYLOAD_STATUS_ERROR:
|
||||
case RHIZOME_PAYLOAD_STATUS_STORED:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
|
||||
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
|
||||
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
|
||||
case RHIZOME_PAYLOAD_STATUS_TOO_BIG:
|
||||
case RHIZOME_PAYLOAD_STATUS_UNINITERESTING:
|
||||
break;
|
||||
default:
|
||||
FATALF("status = %d", status);
|
||||
case RHIZOME_PAYLOAD_STATUS_EVICTED:
|
||||
return status;
|
||||
}
|
||||
return status;
|
||||
FATALF("rhizome_finish_write() returned status = %d", status);
|
||||
}
|
||||
|
||||
/* Return RHIZOME_PAYLOAD_STATUS_STORED if file blob found, RHIZOME_PAYLOAD_STATUS_NEW if not found.
|
||||
|
@ -813,6 +813,10 @@ strbuf strbuf_append_mime_content_type(strbuf sb, const struct mime_content_type
|
||||
strbuf_puts(sb, "; boundary=");
|
||||
strbuf_append_quoted_string(sb, ct->multipart_boundary);
|
||||
}
|
||||
if (ct->format) {
|
||||
strbuf_puts(sb, "; format=");
|
||||
strbuf_append_quoted_string(sb, ct->format);
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Common definitions for Rhizome test suites.
|
||||
# Copyright 2012 Serval Project Inc.
|
||||
# Copyright 2012-2014 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
|
||||
@ -28,6 +28,8 @@ rexp_crypt='[01]'
|
||||
rexp_date='[0-9]\{1,\}'
|
||||
rexp_rowid='[0-9]\{1,\}'
|
||||
|
||||
BID_NONEXISTENT=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
|
||||
|
||||
assert_manifest_complete() {
|
||||
local manifest="$1"
|
||||
tfw_cat -v "$manifest"
|
||||
@ -194,7 +196,7 @@ unpack_manifest_for_grep() {
|
||||
re_name=$(escape_grep_basic "${filename##*/}")
|
||||
if [ -e "$manifestname" ]; then
|
||||
re_filesize=$($SED -n -e '/^filesize=/s///p' "$manifestname")
|
||||
if [ "$filesize" = 0 ]; then
|
||||
if [ "$re_filesize" = 0 ]; then
|
||||
re_filehash=
|
||||
else
|
||||
re_filehash=$($SED -n -e '/^filehash=/s///p' "$manifestname")
|
||||
@ -241,10 +243,18 @@ extract_stdout_version() {
|
||||
extract_stdout_keyvalue "$1" version "$rexp_version"
|
||||
}
|
||||
|
||||
extract_stdout_author_optional() {
|
||||
extract_stdout_keyvalue_optional "$1" .author "$rexp_author"
|
||||
}
|
||||
|
||||
extract_stdout_author() {
|
||||
extract_stdout_keyvalue "$1" .author "$rexp_author"
|
||||
}
|
||||
|
||||
extract_stdout_secret_optional() {
|
||||
extract_stdout_keyvalue_optional "$1" .secret "$rexp_bundlesecret"
|
||||
}
|
||||
|
||||
extract_stdout_secret() {
|
||||
extract_stdout_keyvalue "$1" .secret "$rexp_bundlesecret"
|
||||
}
|
||||
@ -511,3 +521,52 @@ assert_rhizome_received() {
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
rhizome_add_bundles() {
|
||||
local encrypted=false
|
||||
case "$1" in
|
||||
--encrypted) encrypted=true; shift;;
|
||||
esac
|
||||
local SID="${1?}"
|
||||
shift
|
||||
local n
|
||||
for ((n = $1; n <= $2; ++n)); do
|
||||
create_file file$n $((1000 + $n))
|
||||
if $encrypted; then
|
||||
echo "crypt=1" >file$n.manifest
|
||||
fi
|
||||
executeOk_servald rhizome add file "$SID" file$n file$n.manifest
|
||||
extract_stdout_manifestid BID[$n]
|
||||
extract_stdout_version VERSION[$n]
|
||||
extract_stdout_filesize SIZE[$n]
|
||||
extract_stdout_filehash HASH[$n]
|
||||
extract_stdout_date DATE[$n]
|
||||
extract_stdout_BK BK[$n]
|
||||
extract_stdout_rowid ROWID[$n]
|
||||
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 ]
|
||||
else
|
||||
CRYPT[$n]=
|
||||
fi
|
||||
executeOk_servald rhizome export file ${HASH[$n]} raw$n
|
||||
if $encrypted; then
|
||||
assert ! cmp file$n raw$n
|
||||
else
|
||||
assert cmp file$n raw$n
|
||||
fi
|
||||
[ "${ROWID[$n]}" -gt "${ROWID_MAX:-0}" ] && ROWID_MAX=${ROWID[$n]}
|
||||
done
|
||||
}
|
||||
|
||||
rhizome_delete_payload_blobs() {
|
||||
local filehash
|
||||
for filehash; do
|
||||
assert --message="Rhizome external blob file exists, filehash=$filehash" [ -e "$SERVALINSTANCE_PATH/blob/$filehash" ]
|
||||
rm -f "$SERVALINSTANCE_PATH/blob/$filehash"
|
||||
done
|
||||
}
|
||||
|
@ -99,13 +99,13 @@ Options:
|
||||
"
|
||||
}
|
||||
|
||||
# Internal utility for setting shopt variables and restoring their original
|
||||
# Utility functions for setting shopt variables and restoring their original
|
||||
# value:
|
||||
# local oo
|
||||
# _tfw_shopt oo -s extglob -u extdebug
|
||||
# tfw_shopt oo -s extglob -u extdebug
|
||||
# ...
|
||||
# _tfw_shopt_restore oo
|
||||
_tfw_shopt() {
|
||||
# tfw_shopt_restore oo
|
||||
tfw_shopt() {
|
||||
local _var="$1"
|
||||
shift
|
||||
local op=s
|
||||
@ -125,7 +125,7 @@ _tfw_shopt() {
|
||||
done
|
||||
eval $_var='"$restore"'
|
||||
}
|
||||
_tfw_shopt_restore() {
|
||||
tfw_shopt_restore() {
|
||||
local _var="$1"
|
||||
[ -n "${!_var}" ] && eval "${!_var}"
|
||||
}
|
||||
@ -138,7 +138,7 @@ declare -a _tfw_forked_pids=()
|
||||
declare -a _tfw_forked_labels=()
|
||||
|
||||
# The rest of this file is parsed for extended glob patterns.
|
||||
_tfw_shopt _tfw_orig_shopt -s extglob
|
||||
tfw_shopt _tfw_orig_shopt -s extglob
|
||||
|
||||
includeTests() {
|
||||
local arg
|
||||
@ -190,7 +190,7 @@ runTests() {
|
||||
local allargs="$*"
|
||||
local -a filters=()
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
while [ $# -ne 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help) usage; exit 0;;
|
||||
@ -242,7 +242,7 @@ runTests() {
|
||||
esac
|
||||
shift
|
||||
done
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
if $_tfw_verbose && [ $_tfw_njobs -ne 1 ]; then
|
||||
_tfw_fatal "--verbose is incompatible with --jobs=$_tfw_njobs"
|
||||
fi
|
||||
@ -967,7 +967,7 @@ _tfw_getopts() {
|
||||
_tfw_opt_grepopts=()
|
||||
_tfw_getopts_shift=0
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
while [ $# -ne 0 ]; do
|
||||
case "$context:$1" in
|
||||
*:--stdout) _tfw_dump_on_fail --stdout;;
|
||||
@ -1014,29 +1014,29 @@ _tfw_getopts() {
|
||||
[ -z "$_tfw_executable" ] && _tfw_error "missing executable argument"
|
||||
;;
|
||||
esac
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
return 0
|
||||
}
|
||||
|
||||
_tfw_is_uint() {
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
local ret=1
|
||||
case "$1" in
|
||||
+([0-9])) ret=0;;
|
||||
esac
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
return $ret
|
||||
}
|
||||
|
||||
_tfw_is_float() {
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
local ret=1
|
||||
case "$1" in
|
||||
@(+([0-9])?(.+([0-9]))|*([0-9]).+([0-9]))) ret=0;;
|
||||
esac
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
return $ret
|
||||
}
|
||||
|
||||
@ -1173,7 +1173,7 @@ _tfw_assert_grep() {
|
||||
local ret=0
|
||||
local info="$matches match"$([ $matches -ne 1 ] && echo "es")
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
case "$_tfw_opt_matches" in
|
||||
'')
|
||||
done=true
|
||||
@ -1231,7 +1231,7 @@ _tfw_assert_grep() {
|
||||
_tfw_error "unsupported value for --matches=$_tfw_opt_matches"
|
||||
ret=$?
|
||||
fi
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
fi
|
||||
if [ $ret -ne 0 ]; then
|
||||
_tfw_backtrace
|
||||
@ -1310,7 +1310,7 @@ _tfw_unpack_words() {
|
||||
_tfw_find_tests() {
|
||||
(
|
||||
local oo
|
||||
_tfw_shopt oo -s extdebug
|
||||
tfw_shopt oo -s extdebug
|
||||
local func
|
||||
for func in $(builtin declare -F | $SED -n -e '/^declare -f test_[A-Za-z]/s/^declare -f //p'); do
|
||||
local funcname
|
||||
@ -1337,7 +1337,7 @@ _tfw_find_tests() {
|
||||
echo $lineno $number $name "$path"
|
||||
done
|
||||
done
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
) | sort -n -k1 -k2 | $SED -e 's/^[0-9][0-9]* [0-9][0-9]* //'
|
||||
}
|
||||
|
||||
@ -1369,7 +1369,7 @@ _tfw_filter_predicate() {
|
||||
local -a filters=("$@")
|
||||
local ret=1
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
if [ ${#filters[*]} -eq 0 ]; then
|
||||
ret=0
|
||||
else
|
||||
@ -1425,7 +1425,7 @@ _tfw_filter_predicate() {
|
||||
esac
|
||||
done
|
||||
fi
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
return $ret
|
||||
}
|
||||
|
||||
@ -1966,7 +1966,7 @@ fork_wait_all() {
|
||||
|
||||
_tfw_set_forklabel() {
|
||||
local oo
|
||||
_tfw_shopt oo -s extglob
|
||||
tfw_shopt oo -s extglob
|
||||
local ret=1
|
||||
case "$1" in
|
||||
'%'+([[A-Za-z0-9]))
|
||||
@ -1979,7 +1979,7 @@ _tfw_set_forklabel() {
|
||||
ret=0
|
||||
;;
|
||||
esac
|
||||
_tfw_shopt_restore oo
|
||||
tfw_shopt_restore oo
|
||||
return $ret
|
||||
}
|
||||
|
||||
@ -2191,4 +2191,4 @@ escape_grep_extended() {
|
||||
}
|
||||
|
||||
# Restore the caller's shopt preferences before returning.
|
||||
_tfw_shopt_restore _tfw_orig_shopt
|
||||
tfw_shopt_restore _tfw_orig_shopt
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# Aggregation of all tests except high-load stress tests.
|
||||
#
|
||||
# Copyright 2012 Serval Project, Inc.
|
||||
# Copyright 2012-2014 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
|
||||
@ -39,6 +39,7 @@ includeTests directory_service
|
||||
includeTests vomp
|
||||
if type -p "$JAVAC" >/dev/null; then
|
||||
includeTests jni
|
||||
includeTests rhizomejava
|
||||
includeTests meshmsjava
|
||||
fi
|
||||
|
||||
|
@ -33,9 +33,9 @@ setup() {
|
||||
set_instance +A
|
||||
set_meshms_config
|
||||
executeOk_servald config \
|
||||
set rhizome.api.restful.users.harry.password potter \
|
||||
set rhizome.api.restful.users.ron.password weasley \
|
||||
set rhizome.api.restful.users.hermione.password grainger
|
||||
set api.restful.users.harry.password potter \
|
||||
set api.restful.users.ron.password weasley \
|
||||
set api.restful.users.hermione.password grainger
|
||||
set_extra_config
|
||||
if [ -z "$IDENTITY_COUNT" ]; then
|
||||
create_single_identity
|
||||
@ -81,7 +81,7 @@ test_AuthBasicMissing() {
|
||||
--dump-header http.headers \
|
||||
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA/conversationlist.json"
|
||||
assertStdoutIs '401'
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$"
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval RESTful API\"$CR\$"
|
||||
assertJq http.output 'contains({"http_status_code": 401})'
|
||||
assertJq http.output 'contains({"http_status_message": ""})'
|
||||
}
|
||||
@ -99,7 +99,7 @@ test_AuthBasicWrong() {
|
||||
--basic --user fred:nurks \
|
||||
"http://$addr_localhost:$PORTA/restful/meshms/$SIDA/conversationlist.json"
|
||||
assertStdoutIs '401'
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$"
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval RESTful API\"$CR\$"
|
||||
assertJq http.output 'contains({"http_status_code": 401})'
|
||||
assertJq http.output 'contains({"http_status_message": ""})'
|
||||
executeOk curl \
|
||||
@ -280,16 +280,17 @@ test_MeshmsListMessagesNoIdentity() {
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 403
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'meshms operation failed'
|
||||
assertJq http.body 'contains({"meshms_status_code": 2})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown'
|
||||
assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown'
|
||||
}
|
||||
|
||||
doc_MeshmsListMessagesNewSince="HTTP RESTful list MeshMS messages in one conversation since token as JSON"
|
||||
setup_MeshmsListMessagesNewSince() {
|
||||
IDENTITY_COUNT=2
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.api.restful.newsince_timeout 1s \
|
||||
set rhizome.api.restful.newsince_poll_ms 500
|
||||
executeOk_servald config set api.restful.newsince_timeout 1s \
|
||||
set api.restful.newsince_poll_ms 500
|
||||
}
|
||||
setup
|
||||
meshms_add_messages $SIDA1 $SIDA2 '><>>A>A<>><><><>>>A>A><<<<A<>><>>A<<>'
|
||||
@ -340,8 +341,8 @@ doc_MeshmsListMessagesNewSinceArrival="HTTP RESTful list newly arriving MeshMS m
|
||||
setup_MeshmsListMessagesNewSinceArrival() {
|
||||
IDENTITY_COUNT=2
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.api.restful.newsince_timeout 60s \
|
||||
set rhizome.api.restful.newsince_poll_ms 500
|
||||
executeOk_servald config set api.restful.newsince_timeout 60s \
|
||||
set api.restful.newsince_poll_ms 500
|
||||
}
|
||||
setup
|
||||
meshms_add_messages $SIDA1 $SIDA2 '><>A>'
|
||||
@ -555,8 +556,9 @@ test_MeshmsSendNoIdentity() {
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 403
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'meshms operation failed'
|
||||
assertJq http.body 'contains({"meshms_status_code": 2})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown'
|
||||
assertJqGrep --ignore-case http.body '.meshms_status_message' 'identity.*unknown'
|
||||
}
|
||||
|
||||
doc_MeshmsReadAllConversations="HTTP RESTful MeshMS mark all conversations read"
|
||||
|
500
tests/rhizomejava
Executable file
500
tests/rhizomejava
Executable file
@ -0,0 +1,500 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tests for Rhizome Java API.
|
||||
#
|
||||
# Copyright 2014 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.
|
||||
|
||||
source "${0%/*}/../testframework.sh"
|
||||
source "${0%/*}/../testdefs.sh"
|
||||
source "${0%/*}/../testdefs_java.sh"
|
||||
source "${0%/*}/../testdefs_rhizome.sh"
|
||||
|
||||
setup() {
|
||||
setup_servald
|
||||
setup_servald_so
|
||||
compile_java_classes
|
||||
set_instance +A
|
||||
executeOk_servald config \
|
||||
set log.console.level debug \
|
||||
set debug.httpd on \
|
||||
set debug.rhizome on \
|
||||
set debug.rhizome_manifest on
|
||||
set_extra_config
|
||||
create_identities 4
|
||||
start_servald_server
|
||||
}
|
||||
|
||||
set_extra_config() {
|
||||
:
|
||||
}
|
||||
|
||||
teardown() {
|
||||
stop_all_servald_servers
|
||||
kill_all_servald_processes
|
||||
assert_no_servald_processes
|
||||
report_all_servald_servers
|
||||
}
|
||||
|
||||
# Utility function:
|
||||
#
|
||||
# unset_vars_with_prefix PREFIX
|
||||
#
|
||||
# Unsets all shell variables whose names starting with the given PREFIX
|
||||
unset_vars_with_prefix() {
|
||||
local __prefix="${1?}"
|
||||
local __varname
|
||||
for __varname in $(declare -p | sed -n -e "s/^declare -[^ ]* \($__prefix[A-Za-z0-9_]\+\)=.*/\1/p"); do
|
||||
unset $__varname
|
||||
done
|
||||
}
|
||||
|
||||
# Utility function:
|
||||
#
|
||||
# unpack_vars PREFIX TEXT
|
||||
#
|
||||
# parses the given TEXT which must have the form:
|
||||
#
|
||||
# ident1=..., ident2=...., ... identN=...
|
||||
#
|
||||
# into shell variables:
|
||||
#
|
||||
# PREFIXident1=...
|
||||
# PREFIXident2=...
|
||||
# ...
|
||||
# PREFIXidentN=...
|
||||
#
|
||||
# Sets the UNPACKED_VAR_NAMES[] array variable to a list of the names of the
|
||||
# variables that were set (names include the PREFIX).
|
||||
#
|
||||
# Warning: overwrites existing shell variables. Names of overwritten shell
|
||||
# variables are derived directly from the output of the command, so cannot be
|
||||
# controlled. PREFIX should be used to ensure that special variables cannot
|
||||
# be clobbered by accident.
|
||||
unpack_vars() {
|
||||
local __prefix="${1?}"
|
||||
local __text="${2?}"
|
||||
local __oo
|
||||
tfw_shopt __oo -s extglob
|
||||
UNPACKED_VAR_NAMES=()
|
||||
while [ -n "$__text" ]; do
|
||||
case "$__text" in
|
||||
[A-Za-z_.]+([A-Za-z_.0-9])=*)
|
||||
local __ident="${__text%%=*}"
|
||||
__ident="${__ident//./__}"
|
||||
__text="${__text#*=}"
|
||||
local __value="${__text%%, [A-Za-z_.]+([A-Za-z_.0-9])=*}"
|
||||
__text="${__text:${#__value}}"
|
||||
__text="${__text#, }"
|
||||
UNPACKED_VAR_NAMES+=("$__ident")
|
||||
eval ${__prefix}${__ident}=\"\$__value\"
|
||||
;;
|
||||
*)
|
||||
fail "cannot unpack variable from '$__text'"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
tfw_shopt_restore __oo
|
||||
}
|
||||
|
||||
doc_RhizomeList="Java API Rhizome list 100 bundles"
|
||||
setup_RhizomeList() {
|
||||
setup
|
||||
NBUNDLES=100
|
||||
rhizome_add_bundles $SIDA1 0 $((NBUNDLES-1))
|
||||
assert [ "$ROWID_MAX" -ge "$NBUNDLES" ]
|
||||
}
|
||||
test_RhizomeList() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-list
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutLineCount == $NBUNDLES
|
||||
let lnum=NBUNDLES
|
||||
for ((n = 0; n != NBUNDLES; ++n)); do
|
||||
line="$(sed -n -e ${lnum}p "$TFWSTDOUT")"
|
||||
unset_vars_with_prefix XX_
|
||||
unpack_vars XX_ "$line"
|
||||
if [ "${ROWID[$n]}" -eq "$ROWID_MAX" ]; then
|
||||
# The first row must contain a non-null token string.
|
||||
assert [ -n "$XX__token" ]
|
||||
assert [ "$lnum" -eq 1 ]
|
||||
fi
|
||||
assert [ "$XX_id" = "${BID[$n]}" ]
|
||||
assert [ "$XX_version" = "${VERSION[$n]}" ]
|
||||
assert [ "$XX_filesize" = "${SIZE[$n]}" ]
|
||||
assert [ "$XX_filehash" = "${HASH[$n]}" ]
|
||||
assert [ "$XX_date" = "${DATE[$n]}" ]
|
||||
assert [ "$XX_service" = "file" ]
|
||||
assert [ "$XX_name" = "file$n" ]
|
||||
assert [ "$XX__rowId" = "${ROWID[$n]}" ]
|
||||
assert [ "$XX__fromHere" = "1" ]
|
||||
assert [ "$XX__author" = "$SIDA1" ]
|
||||
assert [ "$XX__insertTime" = "${INSERTTIME[$n]}" ]
|
||||
let --lnum
|
||||
done
|
||||
}
|
||||
|
||||
assert_metadata() {
|
||||
local n=$1
|
||||
assertStdoutGrep --matches=1 "^id=${BID[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^version=${VERSION[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^filesize=${SIZE[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^filehash=${HASH[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^BK=${BK[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^date=${DATE[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^name=${NAME[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^service=file$CR\$"
|
||||
assertStdoutGrep --matches=1 "^_insertTime=${INSERTTIME[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^_author=${AUTHOR[$n]}$CR\$"
|
||||
assertStdoutGrep --matches=1 "^_secret=${SECRET[$n]}$CR\$"
|
||||
}
|
||||
|
||||
doc_RhizomeManifest="Java API fetch Rhizome manifest"
|
||||
setup_RhizomeManifest() {
|
||||
setup
|
||||
rhizome_add_bundles $SIDA1 0 2
|
||||
}
|
||||
test_RhizomeManifest() {
|
||||
for n in 0 1 2; do
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-manifest "${BID[$n]}" bundle$n.rhm
|
||||
tfw_cat --stdout --stderr
|
||||
assert_metadata $n
|
||||
ls -l file$n.manifest bundle$n.rhm
|
||||
tfw_cat -v file$n.manifest -v bundle$n.rhm
|
||||
assert diff file$n.manifest bundle$n.rhm
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomeManifestNonexist="Java API fetch non-existent Rhizome manifest"
|
||||
setup_RhizomeManifestNonexist() {
|
||||
setup
|
||||
}
|
||||
test_RhizomeManifestNonexist() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-manifest "$BID_NONEXISTENT" ''
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutLineCount == 1
|
||||
assertStdoutGrep --ignore-case '^not found$'
|
||||
}
|
||||
|
||||
doc_RhizomePayloadRaw="Java API fetch Rhizome raw payload"
|
||||
setup_RhizomePayloadRaw() {
|
||||
setup
|
||||
rhizome_add_bundles $SIDA1 0 1
|
||||
rhizome_add_bundles --encrypted $SIDA1 2 3
|
||||
}
|
||||
test_RhizomePayloadRaw() {
|
||||
for n in 0 1 2 3; do
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-payload-raw "${BID[$n]}" raw.bin$n
|
||||
tfw_cat --stdout --stderr
|
||||
assert_metadata $n
|
||||
done
|
||||
for n in 0 1 2 3; do
|
||||
assert cmp raw$n raw.bin$n
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomePayloadRawNonexistManifest="Java API fetch Rhizome raw payload for non-existent manifest"
|
||||
setup_RhizomePayloadRawNonexistManifest() {
|
||||
setup
|
||||
}
|
||||
test_RhizomePayloadRawNonexistManifest() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-payload-raw "$BID_NONEXISTENT" ''
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutLineCount == 1
|
||||
assertStdoutGrep --ignore-case '^not found$'
|
||||
}
|
||||
|
||||
doc_RhizomePayloadRawNonexistPayload="Java API fetch non-existent Rhizome raw payload"
|
||||
setup_RhizomePayloadRawNonexistPayload() {
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.max_blob_size 0
|
||||
}
|
||||
setup
|
||||
rhizome_add_bundles $SIDA1 0 0
|
||||
rhizome_delete_payload_blobs "${HASH[0]}"
|
||||
}
|
||||
test_RhizomePayloadRawNonexistPayload() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-payload-raw "${BID[0]}" raw.bin
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep --ignore-case '^no payload$'
|
||||
assert_metadata 0
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecrypted="Java API fetch Rhizome decrypted payload"
|
||||
setup_RhizomePayloadDecrypted() {
|
||||
setup
|
||||
rhizome_add_bundles $SIDA1 0 1
|
||||
rhizome_add_bundles --encrypted $SIDA1 2 3
|
||||
}
|
||||
test_RhizomePayloadDecrypted() {
|
||||
for n in 0 1 2 3; do
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-payload-decrypted "${BID[$n]}" decrypted.bin$n
|
||||
tfw_cat --stdout --stderr
|
||||
assert_metadata $n
|
||||
done
|
||||
for n in 0 1 2 3; do
|
||||
assert cmp file$n decrypted.bin$n
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecryptedNonexistManifest="Java API fetch Rhizome decrypted payload for non-existent manifest"
|
||||
setup_RhizomePayloadDecryptedNonexistManifest() {
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.max_blob_size 0
|
||||
}
|
||||
setup
|
||||
rhizome_add_bundles $SIDA1 0 0
|
||||
rhizome_delete_payload_blobs "${HASH[0]}"
|
||||
}
|
||||
test_RhizomePayloadDecryptedNonexistManifest() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-payload-decrypted "${BID[0]}" ''
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep --ignore-case '^no payload$'
|
||||
assert_metadata 0
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecryptedForeign="Java API cannot fetch foreign Rhizome decrypted payload"
|
||||
setup_RhizomePayloadDecryptedForeign() {
|
||||
setup
|
||||
rhizome_add_bundles --encrypted $SIDA1 0 0
|
||||
set_instance +B
|
||||
create_single_identity
|
||||
rhizome_add_bundles --encrypted $SIDB 1 1
|
||||
executeOk_servald rhizome export manifest "${BID[1]}" file1.manifest
|
||||
set_instance +A
|
||||
executeOk_servald rhizome import bundle raw1 file1.manifest
|
||||
}
|
||||
test_RhizomePayloadDecryptedForeign() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-payload-decrypted "${BID[1]}" decrypted.bin$n
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep RhizomeDecryptionException
|
||||
}
|
||||
|
||||
doc_RhizomeInsert="Java API insert new Rhizome bundles"
|
||||
setup_RhizomeInsert() {
|
||||
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]=elvis
|
||||
echo "name=elvis" >manifest1
|
||||
name[2]=file2
|
||||
echo "crypt=1" >manifest2
|
||||
name[3]=fintlewoodlewix
|
||||
payload_filename[3]=fintlewoodlewix
|
||||
>manifest3
|
||||
name[4]=
|
||||
author[4]=
|
||||
service[4]=wah
|
||||
echo -e "service=wah\ncrypt=0" >manifest4
|
||||
}
|
||||
test_RhizomeInsert() {
|
||||
for n in 1 2 3 4; do
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert "${author[$n]}" manifest$n file$n file$n.manifest "${payload_filename[$n]}"
|
||||
tfw_cat --stdout --stderr -v file$n.manifest
|
||||
assertStdoutGrep '^_status=NEW$'
|
||||
replayStdout >stdout-insert
|
||||
extract_manifest_id BID[$n] stdout-insert
|
||||
extract_manifest SECRET[$n] stdout-insert _secret "$rexp_bundlesecret"
|
||||
executeOk_servald rhizome extract bundle "${BID[$n]}" xfile$n.manifest xfile$n
|
||||
tfw_cat --stdout -v xfile$n.manifest
|
||||
extract_stdout_rowid ROWID[$n]
|
||||
extract_stdout_inserttime INSERTTIME[$n]
|
||||
assertGrep stdout-insert "^_rowId=${ROWID[$n]}\$"
|
||||
assertGrep stdout-insert "^_insertTime=${INSERTTIME[$n]}\$"
|
||||
if extract_stdout_author_optional AUTHOR[$n]; then
|
||||
assertGrep stdout-insert "^_author=${AUTHOR[$n]}\$"
|
||||
else
|
||||
assertGrep --matches=0 stdout-insert "^_author="
|
||||
fi
|
||||
assert diff xfile$n.manifest file$n.manifest
|
||||
assert diff file$n xfile$n
|
||||
unpack_manifest_for_grep xfile$n
|
||||
assertGrep stdout-insert "^id=$re_manifestid\$"
|
||||
assertGrep stdout-insert "^version=$re_version\$"
|
||||
assertGrep stdout-insert "^filesize=$re_filesize\$"
|
||||
if [ -n "$re_filehash" ]; then
|
||||
assertGrep stdout-insert "^filehash=$re_filehash\$"
|
||||
else
|
||||
assertGrep --matches=0 stdout-insert "^filehash="
|
||||
fi
|
||||
assertGrep stdout-insert "^date=$re_date\$"
|
||||
assertGrep stdout-insert "^service=$re_service\$"
|
||||
if [ -n "${name[$n]}" ]; then
|
||||
assertGrep stdout-insert "^name=$re_name\$"
|
||||
assert [ "$re_name" = "${name[$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
|
||||
$SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d;/^[^a-zA-Z]/,$d' xfile$n.manifest >nmanifest$n
|
||||
assert_manifest_fields nmanifest$n id !version !date !filehash !filesize
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' nmanifest$n nfile$n nfile$n.manifest "nfile$n"
|
||||
tfw_cat --stdout --stderr -v nfile$n.manifest
|
||||
if [ -n "${author[$n]}" ]; then
|
||||
assertStdoutGrep '^_status=NEW$'
|
||||
assertStdoutGrep "^id=${BID[$n]}\$"
|
||||
assertStderrGrep --matches=1 "^bundle_status_code=NEW$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^bundle_status_message=.*bundle new to store.*$CR\$"
|
||||
assertStderrGrep --matches=1 "^payload_status_code=NEW$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^payload_status_message=.*payload new to store.*$CR\$"
|
||||
else
|
||||
assertStdoutGrep RhizomeReadOnlyException
|
||||
assertStderrGrep --ignore-case "missing bundle secret"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomeInsertAnon="Java API update anonymous Rhizome bundle"
|
||||
setup_RhizomeInsertAnon() {
|
||||
setup
|
||||
create_file file1 1001
|
||||
executeOk_servald rhizome add file '' file1 file1.manifest
|
||||
extract_stdout_manifestid BID
|
||||
extract_stdout_secret SECRET
|
||||
assert_manifest_fields file1.manifest id !BK
|
||||
$SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d;/^[^a-zA-Z]/,$d' file1.manifest >file2.manifest
|
||||
assert_manifest_fields file2.manifest id !version !date !filehash !filesize
|
||||
create_file file2 1002
|
||||
}
|
||||
test_RhizomeInsertAnon() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' file2.manifest file2 ifile2.manifest "file2" "$SECRET"
|
||||
tfw_cat --stdout --stderr -v ifile2.manifest
|
||||
assertStdoutGrep '^_status=NEW$'
|
||||
assertStdoutGrep "^id=$BID\$"
|
||||
assertStderrGrep --matches=1 "^bundle_status_code=NEW$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^bundle_status_message=.*bundle new to store.*$CR\$"
|
||||
assertStderrGrep --matches=1 "^payload_status_code=NEW$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^payload_status_message=.*payload new to store.*$CR\$"
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list --fromhere=0 --manifest=ifile2.manifest file2
|
||||
}
|
||||
|
||||
doc_RhizomeInsertEmptyNew="Java API update existing Rhizome bundle to empty"
|
||||
setup_RhizomeInsertEmptyNew() {
|
||||
setup
|
||||
>empty
|
||||
assert [ ! -s empty ]
|
||||
}
|
||||
test_RhizomeInsertEmptyNew() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' '' empty empty.manifest "lucky"
|
||||
tfw_cat --stdout --stderr -v empty.manifest
|
||||
extract_manifest_id BID empty.manifest
|
||||
assertStdoutGrep '^_status=NEW$'
|
||||
assertStdoutGrep "^id=$BID\$"
|
||||
assertStdoutGrep "^filesize=0\$"
|
||||
assertStdoutGrep --matches=0 "^filehash="
|
||||
assertStderrGrep --matches=1 "^bundle_status_code=NEW$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^bundle_status_message=.*bundle new to store.*$CR\$"
|
||||
assertStderrGrep --matches=1 "^payload_status_code=EMPTY$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^payload_status_message=.*payload empty.*$CR\$"
|
||||
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_RhizomeInsertEmptyUpdate="Java API insert new empty Rhizome bundle"
|
||||
setup_RhizomeInsertEmptyUpdate() {
|
||||
setup
|
||||
create_file file1 1001
|
||||
executeOk_servald rhizome add file "$SIDA1" file1 file1.manifest
|
||||
extract_stdout_manifestid BID
|
||||
extract_stdout_BK BK
|
||||
>empty
|
||||
assert [ ! -s empty ]
|
||||
echo "id=$BID" >iempty.manifest
|
||||
echo "BK=$BK" >>iempty.manifest
|
||||
}
|
||||
test_RhizomeInsertEmptyUpdate() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' iempty.manifest empty empty.manifest
|
||||
tfw_cat --stdout --stderr -v empty.manifest
|
||||
assertStdoutGrep '^_status=NEW$'
|
||||
assertStdoutGrep "^id=$BID\$"
|
||||
assertStdoutGrep "^filesize=0\$"
|
||||
assertStdoutGrep --matches=0 "^filehash="
|
||||
assertStderrGrep --matches=1 "^bundle_status_code=NEW$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^bundle_status_message=.*bundle new to store.*$CR\$"
|
||||
assertStderrGrep --matches=1 "^payload_status_code=EMPTY$CR\$"
|
||||
assertStderrGrep --matches=1 --ignore-case "^payload_status_message=.*payload empty.*$CR\$"
|
||||
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_RhizomeInsertJournal="Java API insert Rhizome bundle does not support journals"
|
||||
setup_RhizomeInsertJournal() {
|
||||
setup
|
||||
echo 'File one' >file1
|
||||
echo 'tail=0' >file1.manifest
|
||||
}
|
||||
test_RhizomeInsertJournal() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' file1.manifest file1 ifile1.manifest
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep ServalDNotImplementedException
|
||||
assertStdoutGrep --ignore-case "not supported.*journal"
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
}
|
||||
|
||||
doc_RhizomeInsertIncorrectFilesize="Java API insert Rhizome bundle, 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() {
|
||||
for n in 1 2; do
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' file$n.manifest file$n ifile$n.manifest
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep RhizomeInconsistencyException
|
||||
assertStdoutGrep --ignore-case 'payload size.*contradicts manifest'
|
||||
done
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
}
|
||||
|
||||
doc_RhizomeInsertIncorrectFilehash="Java API insert Rhizome bundle, incorrect filehash"
|
||||
setup_RhizomeInsertIncorrectFilehash() {
|
||||
setup
|
||||
echo 'File one' >file1
|
||||
echo 'filehash=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' >file1.manifest
|
||||
}
|
||||
test_RhizomeInsertIncorrectFilehash() {
|
||||
executeJavaOk org.servalproject.test.Rhizome rhizome-insert '' file1.manifest file1 ifile1.manifest
|
||||
tfw_cat --stdout --stderr
|
||||
assertStdoutGrep RhizomeInconsistencyException
|
||||
assertStdoutGrep --ignore-case 'payload hash.*contradicts manifest'
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
}
|
||||
|
||||
runTests "$@"
|
@ -34,9 +34,9 @@ setup() {
|
||||
set_instance +A
|
||||
set_rhizome_config
|
||||
executeOk_servald config \
|
||||
set rhizome.api.restful.users.harry.password potter \
|
||||
set rhizome.api.restful.users.ron.password weasley \
|
||||
set rhizome.api.restful.users.hermione.password grainger
|
||||
set api.restful.users.harry.password potter \
|
||||
set api.restful.users.ron.password weasley \
|
||||
set api.restful.users.hermione.password grainger
|
||||
set_extra_config
|
||||
if [ -z "$IDENTITY_COUNT" ]; then
|
||||
create_single_identity
|
||||
@ -81,7 +81,7 @@ test_AuthBasicMissing() {
|
||||
--dump-header http.headers \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
|
||||
assertStdoutIs '401'
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$"
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval RESTful API\"$CR\$"
|
||||
assertJq http.output 'contains({"http_status_code": 401})'
|
||||
assertJq http.output 'contains({"http_status_message": ""})'
|
||||
}
|
||||
@ -99,7 +99,7 @@ test_AuthBasicWrong() {
|
||||
--basic --user fred:nurks \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
|
||||
assertStdoutIs '401'
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$"
|
||||
assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval RESTful API\"$CR\$"
|
||||
assertJq http.output 'contains({"http_status_code": 401})'
|
||||
assertJq http.output 'contains({"http_status_message": ""})'
|
||||
executeOk curl \
|
||||
@ -115,50 +115,11 @@ teardown_AuthBasicWrong() {
|
||||
teardown
|
||||
}
|
||||
|
||||
add_bundles() {
|
||||
local encrypted=false
|
||||
case "$1" in
|
||||
--encrypted) encrypted=true; shift;;
|
||||
esac
|
||||
local n
|
||||
for ((n = $1; n <= $2; ++n)); do
|
||||
create_file file$n $((1000 + $n))
|
||||
if $encrypted; then
|
||||
echo "crypt=1" >file$n.manifest
|
||||
fi
|
||||
executeOk_servald rhizome add file $SIDA file$n file$n.manifest
|
||||
extract_stdout_manifestid BID[$n]
|
||||
extract_stdout_version VERSION[$n]
|
||||
extract_stdout_filesize SIZE[$n]
|
||||
extract_stdout_filehash HASH[$n]
|
||||
extract_stdout_date DATE[$n]
|
||||
extract_stdout_BK BK[$n]
|
||||
extract_stdout_rowid ROWID[$n]
|
||||
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 ]
|
||||
else
|
||||
CRYPT[$n]=
|
||||
fi
|
||||
executeOk_servald rhizome export file ${HASH[$n]} raw$n
|
||||
if $encrypted; then
|
||||
assert ! cmp file$n raw$n
|
||||
else
|
||||
assert cmp file$n raw$n
|
||||
fi
|
||||
[ "${ROWID[$n]}" -gt "${ROWID_MAX:-0}" ] && ROWID_MAX=${ROWID[$n]}
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomeList="HTTP RESTful list Rhizome bundles as JSON"
|
||||
doc_RhizomeList="HTTP RESTful list 100 Rhizome bundles as JSON"
|
||||
setup_RhizomeList() {
|
||||
setup
|
||||
NBUNDLES=100
|
||||
add_bundles 0 $((NBUNDLES-1))
|
||||
rhizome_add_bundles $SIDA 0 $((NBUNDLES-1))
|
||||
assert [ "$ROWID_MAX" -ge "$NBUNDLES" ]
|
||||
}
|
||||
test_RhizomeList() {
|
||||
@ -201,11 +162,11 @@ test_RhizomeList() {
|
||||
doc_RhizomeNewSince="HTTP RESTful list Rhizome bundles since token as JSON"
|
||||
setup_RhizomeNewSince() {
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.api.restful.newsince_timeout 60s \
|
||||
set rhizome.api.restful.newsince_poll_ms 500
|
||||
executeOk_servald config set api.restful.newsince_timeout 60s \
|
||||
set api.restful.newsince_poll_ms 500
|
||||
}
|
||||
setup
|
||||
add_bundles 0 5
|
||||
rhizome_add_bundles $SIDA 0 5
|
||||
executeOk curl \
|
||||
--silent --fail --show-error \
|
||||
--output bundlelist.json \
|
||||
@ -227,7 +188,7 @@ test_RhizomeNewSince() {
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/newsince/$token/bundlelist.json"
|
||||
done
|
||||
wait_until [ -e newsince1.json -a -e newsince2.json -a -e newsince3.json ]
|
||||
add_bundles 6 10
|
||||
rhizome_add_bundles $SIDA 6 10
|
||||
for i in 1 2 3; do
|
||||
wait_until --timeout=10 grep "${BID[10]}" newsince$i.json
|
||||
done
|
||||
@ -264,25 +225,25 @@ test_RhizomeNewSince() {
|
||||
}
|
||||
|
||||
assert_http_response_headers() {
|
||||
local n=$1
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Id: ${BID[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Version: ${VERSION[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Filesize: ${SIZE[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Filehash: ${HASH[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-BK: ${BK[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Date: ${DATE[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Name: \"${NAME[$n]}\"$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Service: file$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Author: ${AUTHOR[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Secret: ${SECRET[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Inserttime: ${INSERTTIME[$n]}$CR\$"
|
||||
assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Rowid: ${ROWID[$n]}$CR\$"
|
||||
local file="$1"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Id: ${BID[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Version: ${VERSION[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Filesize: ${SIZE[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Filehash: ${HASH[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-BK: ${BK[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Date: ${DATE[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Name: \"${NAME[$n]}\"$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Service: file$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Author: ${AUTHOR[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Secret: ${SECRET[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Inserttime: ${INSERTTIME[$n]}$CR\$"
|
||||
assertGrep --matches=1 "$file" "^Serval-Rhizome-Bundle-Rowid: ${ROWID[$n]}$CR\$"
|
||||
}
|
||||
|
||||
doc_RhizomeManifest="HTTP RESTful fetch Rhizome manifest"
|
||||
setup_RhizomeManifest() {
|
||||
setup
|
||||
add_bundles 0 2
|
||||
rhizome_add_bundles $SIDA 0 2
|
||||
}
|
||||
test_RhizomeManifest() {
|
||||
for n in 0 1 2; do
|
||||
@ -297,15 +258,37 @@ test_RhizomeManifest() {
|
||||
done
|
||||
for n in 0 1 2; do
|
||||
assert diff file$n.manifest bundle$n.rhm
|
||||
assert_http_response_headers $n
|
||||
assert_http_response_headers http.headers$n
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomeManifestNonexist="HTTP RESTful fetch non-existent Rhizome manifest"
|
||||
setup_RhizomeManifestNonexist() {
|
||||
setup
|
||||
}
|
||||
test_RhizomeManifestNonexist() {
|
||||
executeOk curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output http.content \
|
||||
--dump-header http.headers \
|
||||
--basic --user harry:potter \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT.rhm"
|
||||
tfw_cat http.headers http.content
|
||||
assertStdoutIs 403
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code:"
|
||||
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
|
||||
assertJq http.content 'contains({"http_status_code": 403})'
|
||||
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
|
||||
}
|
||||
|
||||
doc_RhizomePayloadRaw="HTTP RESTful fetch Rhizome raw payload"
|
||||
setup_RhizomePayloadRaw() {
|
||||
setup
|
||||
add_bundles 0 1
|
||||
add_bundles --encrypted 2 3
|
||||
rhizome_add_bundles $SIDA 0 1
|
||||
rhizome_add_bundles --encrypted $SIDA 2 3
|
||||
}
|
||||
test_RhizomePayloadRaw() {
|
||||
for n in 0 1 2 3; do
|
||||
@ -319,15 +302,70 @@ test_RhizomePayloadRaw() {
|
||||
done
|
||||
for n in 0 1 2 3; do
|
||||
assert cmp raw$n raw.bin$n
|
||||
assert_http_response_headers $n
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 2$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload already in store.*$CR\$"
|
||||
assert_http_response_headers http.headers$n
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomePayloadRawNonexistManifest="HTTP RESTful fetch Rhizome raw payload for non-existent manifest"
|
||||
setup_RhizomePayloadRawNonexistManifest() {
|
||||
setup
|
||||
}
|
||||
test_RhizomePayloadRawNonexistManifest() {
|
||||
executeOk curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output http.content \
|
||||
--dump-header http.headers \
|
||||
--basic --user harry:potter \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/raw.bin"
|
||||
tfw_cat http.headers http.content
|
||||
assertStdoutIs 403
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code:"
|
||||
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
|
||||
assertJq http.content 'contains({"http_status_code": 403})'
|
||||
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
|
||||
}
|
||||
|
||||
doc_RhizomePayloadRawNonexistPayload="HTTP RESTful fetch non-existent Rhizome raw payload"
|
||||
setup_RhizomePayloadRawNonexistPayload() {
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.max_blob_size 0
|
||||
}
|
||||
setup
|
||||
rhizome_add_bundles $SIDA 0 0
|
||||
rhizome_delete_payload_blobs "${HASH[0]}"
|
||||
}
|
||||
test_RhizomePayloadRawNonexistPayload() {
|
||||
executeOk curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output http.content \
|
||||
--dump-header http.headers \
|
||||
--basic --user harry:potter \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[0]}/raw.bin"
|
||||
tfw_cat http.headers http.content
|
||||
assertStdoutIs 403
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
|
||||
assertJq http.content 'contains({"http_status_code": 403})'
|
||||
assertJq http.content 'contains({"rhizome_bundle_status_code": 1})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle already in store"
|
||||
assertJq http.content 'contains({"rhizome_payload_status_code": 1})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_payload_status_message' "payload new to store"
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecrypted="HTTP RESTful fetch Rhizome decrypted payload"
|
||||
setup_RhizomePayloadDecrypted() {
|
||||
setup
|
||||
add_bundles 0 1
|
||||
add_bundles --encrypted 2 3
|
||||
rhizome_add_bundles $SIDA 0 1
|
||||
rhizome_add_bundles --encrypted $SIDA 2 3
|
||||
}
|
||||
test_RhizomePayloadDecrypted() {
|
||||
for n in 0 1 2 3; do
|
||||
@ -341,10 +379,91 @@ test_RhizomePayloadDecrypted() {
|
||||
done
|
||||
for n in 0 1 2 3; do
|
||||
assert cmp file$n decrypted.bin$n
|
||||
assert_http_response_headers $n
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 2$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload already in store.*$CR\$"
|
||||
assert_http_response_headers http.headers$n
|
||||
done
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecryptedForeign="HTTP RESTful cannot fetch foreign Rhizome decrypted payload"
|
||||
setup_RhizomePayloadDecryptedForeign() {
|
||||
setup
|
||||
rhizome_add_bundles --encrypted $SIDA 0 0
|
||||
set_instance +B
|
||||
create_single_identity
|
||||
rhizome_add_bundles --encrypted $SIDB 1 1
|
||||
executeOk_servald rhizome export manifest "${BID[1]}" file1.manifest
|
||||
set_instance +A
|
||||
executeOk_servald rhizome import bundle raw1 file1.manifest
|
||||
}
|
||||
test_RhizomePayloadDecryptedForeign() {
|
||||
executeOk curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output decrypted.bin$n \
|
||||
--dump-header http.headers$n \
|
||||
--basic --user harry:potter \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[1]}/decrypted.bin"
|
||||
tfw_cat http.headers$n decrypted.bin$n
|
||||
assertStdoutIs 403
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 5$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*incorrect bundle secret*$CR\$"
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecryptedNonexistManifest="HTTP RESTful fetch Rhizome decrypted payload for non-existent manifest"
|
||||
setup_RhizomePayloadDecryptedNonexistManifest() {
|
||||
setup
|
||||
}
|
||||
test_RhizomePayloadDecryptedNonexistManifest() {
|
||||
executeOk curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output http.content \
|
||||
--dump-header http.headers \
|
||||
--basic --user harry:potter \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/decrypted.bin"
|
||||
tfw_cat http.headers http.content
|
||||
assertStdoutIs 403
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code:"
|
||||
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
|
||||
assertJq http.content 'contains({"http_status_code": 403})'
|
||||
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
|
||||
}
|
||||
|
||||
doc_RhizomePayloadDecryptedNonexistPayload="HTTP RESTful fetch non-existent Rhizome decrypted payload"
|
||||
setup_RhizomePayloadDecryptedNonexistPayload() {
|
||||
set_extra_config() {
|
||||
executeOk_servald config set rhizome.max_blob_size 0
|
||||
}
|
||||
setup
|
||||
rhizome_add_bundles $SIDA 0 0
|
||||
rhizome_delete_payload_blobs "${HASH[0]}"
|
||||
}
|
||||
test_RhizomePayloadDecryptedNonexistPayload() {
|
||||
executeOk curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output http.content \
|
||||
--dump-header http.headers \
|
||||
--basic --user harry:potter \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/${BID[0]}/decrypted.bin"
|
||||
tfw_cat http.headers http.content
|
||||
assertStdoutIs 403
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle already in store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
|
||||
assertJq http.content 'contains({"http_status_code": 403})'
|
||||
assertJq http.content 'contains({"rhizome_bundle_status_code": 1})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle already in store"
|
||||
assertJq http.content 'contains({"rhizome_payload_status_code": 1})'
|
||||
assertJqGrep --ignore-case http.content '.rhizome_payload_status_message' "payload new to store"
|
||||
}
|
||||
|
||||
extract_http_header() {
|
||||
local __var="$1"
|
||||
local __headerfile="$2"
|
||||
@ -396,7 +515,7 @@ test_RhizomeInsert() {
|
||||
--dump-header http.header$n \
|
||||
--basic --user harry:potter \
|
||||
"${authorargs[@]}" \
|
||||
--form "manifest=@manifest$n;type=rhizome-manifest/text" \
|
||||
--form "manifest=@manifest$n;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--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
|
||||
@ -458,15 +577,19 @@ test_RhizomeInsert() {
|
||||
execute curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output nfile$n.manifest \
|
||||
--dump-header http.header$n \
|
||||
--dump-header http.headers$n \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@nmanifest$n;type=rhizome-manifest/text" \
|
||||
--form "manifest=@nmanifest$n;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@nfile$n;filename=\"nfile$n\"" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header$n nfile$n.manifest
|
||||
tfw_cat http.headers$n nfile$n.manifest
|
||||
assertExitStatus == 0
|
||||
if [ -n "${author[$n]}" ]; then
|
||||
assertStdoutIs 201
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
|
||||
else
|
||||
assertStdoutIs 403
|
||||
assertJq nfile$n.manifest 'contains({"http_status_code": 403})'
|
||||
@ -493,12 +616,16 @@ test_RhizomeInsertAnon() {
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "bundle-secret=$SECRET" \
|
||||
--form "manifest=@file2.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file2.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file2" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header ifile2.manifest
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 201
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list --fromhere=0 --manifest=ifile2.manifest file2
|
||||
}
|
||||
@ -515,12 +642,16 @@ test_RhizomeInsertEmpty() {
|
||||
--output empty.manifest \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=;type=rhizome-manifest/text" \
|
||||
--form "manifest=;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@empty;filename=\"lucky\"" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header empty.manifest
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 201
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Message: .*payload empty.*$CR\$"
|
||||
extract_manifest_id BID empty.manifest
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list empty
|
||||
@ -540,12 +671,16 @@ test_RhizomeInsertLarge() {
|
||||
--output file1.manifest \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=;type=rhizome-manifest/text" \
|
||||
--form "manifest=;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header file1.manifest
|
||||
tfw_cat http.header -v file1.manifest
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 201
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Code: 0$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Bundle-Status-Message: .*bundle new to store.*$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Code: 1$CR\$"
|
||||
assertGrep --matches=1 --ignore-case http.header "^Serval-Rhizome-Result-Payload-Status-Message: .*payload new to store.*$CR\$"
|
||||
extract_manifest_id BID file1.manifest
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list file1
|
||||
@ -587,7 +722,7 @@ test_RhizomeInsertIncorrectManifestType() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=;type=rhizome-manifest/something" \
|
||||
--form "manifest=;type=rhizome-manifest/text" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
@ -599,6 +734,29 @@ test_RhizomeInsertIncorrectManifestType() {
|
||||
assert_rhizome_list
|
||||
}
|
||||
|
||||
doc_RhizomeInsertIncorrectManifestFormat="HTTP RESTful insert Rhizome bundle, incorrect 'manifest' content format"
|
||||
setup_RhizomeInsertIncorrectManifestFormat() {
|
||||
setup
|
||||
echo 'File one' >file1
|
||||
}
|
||||
test_RhizomeInsertIncorrectManifestFormat() {
|
||||
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;format=\"text\"" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 403
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*format.*manifest.*form.*part'
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
}
|
||||
|
||||
doc_RhizomeInsertDuplicateManifest="HTTP RESTful insert Rhizome bundle, duplicate 'manifest' form part"
|
||||
setup_RhizomeInsertDuplicateManifest() {
|
||||
setup
|
||||
@ -612,8 +770,8 @@ test_RhizomeInsertDuplicateManifest() {
|
||||
--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 "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "manifest=@file2.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
@ -637,13 +795,13 @@ test_RhizomeInsertJournal() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 403
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertStdoutIs 501
|
||||
assertJq http.body 'contains({"http_status_code": 501})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'not supported.*journal'
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
@ -661,7 +819,7 @@ test_RhizomeInsertMissingPayload() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
assertExitStatus == 0
|
||||
@ -685,7 +843,7 @@ test_RhizomeInsertDuplicatePayload() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
--form "payload=@file2" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
@ -711,7 +869,7 @@ test_RhizomeInsertPartOrder() {
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "payload=@file1" \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
assertExitStatus == 0
|
||||
@ -734,7 +892,7 @@ test_RhizomeInsertPartUnsupported() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
--form "happyhappy=joyjoy" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
@ -762,26 +920,29 @@ test_RhizomeInsertIncorrectFilesize() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 403
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'payload size.*contradicts manifest'
|
||||
assertJq http.body 'contains({"rhizome_payload_status_code": 3})'
|
||||
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload size.*contradicts manifest'
|
||||
execute curl \
|
||||
--silent --show-error --write-out '%{http_code}' \
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file2.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file2.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--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'
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertJq http.body 'contains({"rhizome_payload_status_code": 3})'
|
||||
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload size.*contradicts manifest'
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
}
|
||||
@ -798,14 +959,15 @@ test_RhizomeInsertIncorrectFilehash() {
|
||||
--output http.body \
|
||||
--dump-header http.header \
|
||||
--basic --user harry:potter \
|
||||
--form "manifest=@file1.manifest;type=rhizome-manifest/text" \
|
||||
--form "manifest=@file1.manifest;type=rhizome/manifest;format=\"text+binarysig\"" \
|
||||
--form "payload=@file1" \
|
||||
"http://$addr_localhost:$PORTA/restful/rhizome/insert"
|
||||
tfw_cat http.header http.body
|
||||
assertExitStatus == 0
|
||||
assertStdoutIs 403
|
||||
assertJq http.body 'contains({"http_status_code": 403})'
|
||||
assertJqGrep --ignore-case http.body '.http_status_message' 'payload hash.*contradicts manifest'
|
||||
assertJq http.body 'contains({"rhizome_payload_status_code": 4})'
|
||||
assertJqGrep --ignore-case http.body '.rhizome_payload_status_message' 'payload hash.*contradicts manifest'
|
||||
executeOk_servald rhizome list
|
||||
assert_rhizome_list
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user