Merge latest Rhizome Java API into development

This commit is contained in:
Andrew Bettison 2014-07-11 12:29:24 +09:30
commit 606f087dd5
59 changed files with 4273 additions and 485 deletions

View File

@ -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)

View File

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

View File

@ -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

View File

@ -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, &param) && _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;

View File

@ -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
View File

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

View File

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

View 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;
}
}

View File

@ -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);

View 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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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
);
}

View File

@ -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;
}
}

View 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;
}
}
}

View File

@ -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);
}
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -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 */

View File

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

View File

@ -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;

View File

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

View File

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

View File

@ -63,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);
}

View File

@ -296,7 +296,7 @@ static enum rhizome_payload_status store_make_space(uint64_t bytes, struct rhizo
DEBUGF("Not enough space for %"PRIu64". Used; %"PRIu64" = %"PRIu64" + %"PRIu64" * (%"PRIu64" - %"PRIu64"), Limit; %"PRIu64,
bytes, db_used, external_bytes, db_page_size, db_page_count, db_free_page_count, limit);
return RHIZOME_PAYLOAD_STATUS_UNINITERESTING;
return RHIZOME_PAYLOAD_STATUS_EVICTED;
}
int rhizome_store_cleanup(struct rhizome_cleanup_report *report)
@ -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.

View File

@ -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;
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
View 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 "$@"

View File

@ -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
}