Rhizome Java API: insert bundle

This commit is contained in:
Andrew Bettison 2014-07-07 17:54:52 +09:30
parent 3715c5bf0b
commit a81d05b4f6
18 changed files with 878 additions and 213 deletions

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;
@ -36,11 +37,19 @@ import org.servalproject.servaldna.BundleId;
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.RhizomeException;
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;
@ -87,6 +96,30 @@ public class ServalDClient implements ServalDHttpConnectionFactory
return RhizomeCommon.rhizomePayload(this, bid);
}
public RhizomeInsertBundle rhizomeInsert(SubscriberId author, RhizomeIncompleteManifest manifest)
throws ServalDInterfaceException,
IOException,
RhizomeInvalidManifestException,
RhizomeFakeManifestException,
RhizomeInconsistencyException,
RhizomeReadOnlyException,
RhizomeEncryptionException
{
return RhizomeCommon.rhizomeInsert(this, author, manifest);
}
public RhizomeInsertBundle rhizomeInsert(SubscriberId author, RhizomeIncompleteManifest manifest, InputStream payloadStream, String fileName)
throws ServalDInterfaceException,
IOException,
RhizomeInvalidManifestException,
RhizomeFakeManifestException,
RhizomeInconsistencyException,
RhizomeReadOnlyException,
RhizomeEncryptionException
{
return RhizomeCommon.rhizomeInsert(this, author, manifest, payloadStream, fileName);
}
public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException, MeshMSException
{
MeshMSConversationList list = new MeshMSConversationList(this, sid);

View File

@ -33,7 +33,8 @@ public enum RhizomeBundleStatus {
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
NO_ROOM(7), // doesn't fit; store may contain more important bundles
READONLY(8) // cannot modify manifest; secret unknown
;
final public int code;
@ -55,6 +56,7 @@ public enum RhizomeBundleStatus {
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;

View File

@ -31,6 +31,7 @@ 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;
@ -57,6 +58,17 @@ public class RhizomeCommon
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 };
@ -84,9 +96,9 @@ public class RhizomeCommon
throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode());
}
protected static void unexpectedResponse(Status status) throws ServalDInterfaceException
protected static ServalDInterfaceException unexpectedResponse(Status status)
{
throw new ServalDInterfaceException(
return new ServalDInterfaceException(
"unexpected Rhizome failure, \"" + status.http_status_message + "\""
+ (status.bundle_status_code == null ? "" : ", " + status.bundle_status_code)
+ (status.bundle_status_message == null ? "" : " \"" + status.bundle_status_message + "\"")
@ -95,34 +107,6 @@ public class RhizomeCommon
);
}
/*
protected static void throwRestfulResponseExceptions(Status status, URL url) throws RhizomeException, ServalDFailureException
{
if (status.bundle_status_code != null) {
switch (status.bundle_status_code) {
case ERROR:
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + url);
case NEW:
throw new RhizomeManifestNotFoundException(url);
case SAME:
throw new RhizomeManifestAlreadyStoredException(url);
case DUPLICATE:
throw new RhizomeDuplicateBundleException(url);
case OLD:
throw new RhizomeOutdatedBundleException(url);
case NO_ROOM:
throw new RhizomeStoreFullException(url);
case INVALID:
throw new RhizomeInvalidManifestException(url);
case FAKE:
throw new RhizomeFakeManifestException(url);
case INCONSISTENT:
throw new RhizomeInconsistencyException(url);
}
}
}
*/
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, RhizomeException
{
int[] expected_response_codes = { expected_response_code };
@ -227,6 +211,7 @@ public class RhizomeCommon
try {
dumpHeaders(conn, System.err);
decodeHeaderBundleStatus(status, conn);
dumpStatus(status, System.err);
switch (status.bundle_status_code) {
case NEW:
return null;
@ -235,7 +220,7 @@ public class RhizomeCommon
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.insertTime, extra.author, extra.secret);
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());
}
@ -247,8 +232,7 @@ public class RhizomeCommon
if (status.input_stream != null)
status.input_stream.close();
}
unexpectedResponse(status);
return null;
throw unexpectedResponse(status);
}
public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid)
@ -260,6 +244,7 @@ public class RhizomeCommon
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());
@ -285,7 +270,7 @@ public class RhizomeCommon
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.insertTime, extra.author, extra.secret);
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;
}
@ -296,8 +281,7 @@ public class RhizomeCommon
if (status.input_stream != null)
status.input_stream.close();
}
unexpectedResponse(status);
return null;
throw unexpectedResponse(status);
}
public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid)
@ -309,6 +293,7 @@ public class RhizomeCommon
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());
@ -336,7 +321,7 @@ public class RhizomeCommon
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.insertTime, extra.author, extra.secret);
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;
}
@ -347,8 +332,134 @@ public class RhizomeCommon
if (status.input_stream != null)
status.input_stream.close();
}
unexpectedResponse(status);
return null;
throw unexpectedResponse(status);
}
public static RhizomeInsertBundle rhizomeInsert(ServalDHttpConnectionFactory connector,
SubscriberId author,
RhizomeIncompleteManifest manifest)
throws ServalDInterfaceException,
IOException,
RhizomeInvalidManifestException,
RhizomeFakeManifestException,
RhizomeInconsistencyException,
RhizomeReadOnlyException,
RhizomeEncryptionException
{
return rhizomeInsert(connector, author, manifest, null, null);
}
public static RhizomeInsertBundle rhizomeInsert(ServalDHttpConnectionFactory connector,
SubscriberId author,
RhizomeIncompleteManifest manifest,
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");
if (author != null) {
wr.print("\r\n--" + boundary + "\r\n");
wr.print("Content-Disposition: form-data; name=\"bundle-author\"\r\n");
wr.print("\r\n");
wr.print(author.toHex());
}
wr.print("\r\n--" + boundary + "\r\n");
wr.print("Content-Disposition: form-data; name=\"manifest\"\r\n");
wr.print("Content-Type: rhizome-manifest/text\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("\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_code=ERROR(-1) 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_code=ERROR(-1) 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());
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(conn.getURL());
case FAKE:
throw new RhizomeFakeManifestException(conn.getURL());
case INCONSISTENT:
throw new RhizomeInconsistencyException(conn.getURL());
case READONLY:
throw new RhizomeReadOnlyException(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(conn.getURL());
case CRYPTO_FAIL:
dumpStatus(status, System.err);
throw new RhizomeEncryptionException(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(status);
}
private static void dumpHeaders(HttpURLConnection conn, PrintStream out)
@ -376,7 +487,8 @@ public class RhizomeCommon
}
private static class BundleExtra {
public long insertTime;
public Long rowId;
public Long insertTime;
public SubscriberId author;
public BundleSecret secret;
}
@ -384,15 +496,35 @@ public class RhizomeCommon
private static BundleExtra bundleExtraFromHeaders(HttpURLConnection conn) throws ServalDInterfaceException
{
BundleExtra extra = new BundleExtra();
extra.insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime");
extra.author = header(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class);
extra.secret = header(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class);
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)
{
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 = conn.getHeaderField(header);
String str = headerStringOrNull(conn, header);
if (str == null)
throw new ServalDInterfaceException("missing header field: " + header);
return str;

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(URL url) {
super("cannot encrypt payload", url);
}
}

View File

@ -32,6 +32,11 @@ 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,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,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

@ -23,9 +23,9 @@ package org.servalproject.servaldna.rhizome;
import java.net.URL;
/**
* Thrown when a Rhizome API method is passed an invalid manifest. This is not an error within the
* Serval DNA interface, so it is not a subclass of ServalDInterfaceException. The programmer must
* explicitly deal with it instead of just absorbing it as an interface malfunction.
* 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>
*/
@ -35,4 +35,8 @@ public class RhizomeInvalidManifestException extends RhizomeException
super("invalid manifest", url);
}
public RhizomeInvalidManifestException(RhizomeIncompleteManifest manifest) {
super("invalid manifest");
}
}

View File

@ -39,7 +39,6 @@ public class RhizomeListBundle {
long insertTime,
SubscriberId author,
int fromHere)
{
this.manifest = manifest;
this.rowNumber = rowNumber;

View File

@ -26,8 +26,10 @@ import java.util.HashSet;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
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;
@ -56,7 +58,7 @@ public class RhizomeManifest {
public final String service;
public final String name;
private HashMap<String,String> extraFields;
protected HashMap<String,String> extraFields;
private byte[] signatureBlock;
private byte[] textFormat;
@ -96,59 +98,51 @@ public class RhizomeManifest {
this.textFormat = null;
}
/** Return the Rhizome manifest in its text format representation.
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 (textFormat == null) {
if (this.textFormat == null) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, "US-ASCII");
osw.write("id=" + id.toHex() + "\n");
osw.write("version=" + version + "\n");
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();
if (signatureBlock != null) {
os.write(0);
os.write(signatureBlock);
}
osw.close();
toTextFormat(os);
os.close();
if (os.size() > TEXT_FORMAT_MAX_SIZE)
throw new RhizomeManifestSizeException("manifest text format overflow", os.size(), TEXT_FORMAT_MAX_SIZE);
textFormat = os.toByteArray();
this.textFormat = os.toByteArray();
}
catch (IOException e) {
// should not happen with ByteArrayOutputStream
return new byte[0];
}
}
byte[] ret = new byte[textFormat.length];
System.arraycopy(textFormat, 0, ret, 0, ret.length);
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>
@ -158,7 +152,8 @@ public class RhizomeManifest {
return fromTextFormat(bytes, 0, bytes.length);
}
/** Construct a Rhizome manifest from its text format representation.
/** Construct a complete Rhizome manifest from its text format representation, including a
* trailing signature block.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
@ -175,95 +170,23 @@ public class RhizomeManifest {
break;
}
}
String text;
RhizomeIncompleteManifest im = new RhizomeIncompleteManifest();
try {
text = new String(bytes, off, proplen, "US-ASCII");
im.parseTextFormat(new ByteArrayInputStream(bytes, off, proplen));
}
catch (UnsupportedEncodingException e) {
throw new RhizomeManifestParseException(e);
catch (IOException e) {
}
BundleId id = null;
Long version = null;
Long filesize = null;
FileHash filehash = null;
SubscriberId sender = null;
SubscriberId recipient = null;
BundleKey BK = null;
Integer crypt = null;
Long tail = null;
Long date = null;
String service = null;
String name = null;
HashMap<String,String> extras = new HashMap<String,String>();
int pos = 0;
int lnum = 1;
while (pos < text.length()) {
int nl = text.indexOf('\n', pos);
if (nl == -1)
nl = text.length();
int field = pos;
if (!isFieldNameFirstChar(text.charAt(field)))
throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + text.substring(pos, nl - pos));
++field;
while (isFieldNameChar(text.charAt(field)))
++field;
assert field < nl;
if (text.charAt(field) != '=')
throw new RhizomeManifestParseException("invalid field name at line " + lnum + ": " + text.substring(pos, nl - pos));
String fieldName = text.substring(pos, field);
String fieldValue = text.substring(field + 1, nl);
HashSet<String> fieldNames = new HashSet<String>(50);
try {
if (fieldNames.contains(fieldName))
throw new RhizomeManifestParseException("duplicate field at line " + lnum + ": " + text.substring(pos, nl - pos));
fieldNames.add(fieldName);
if (fieldName.equals("id"))
id = new BundleId(fieldValue);
else if (fieldName.equals("version"))
version = parseUnsignedLong(fieldValue);
else if (fieldName.equals("filesize"))
filesize = parseUnsignedLong(fieldValue);
else if (fieldName.equals("filehash"))
filehash = new FileHash(fieldValue);
else if (fieldName.equals("sender"))
sender = new SubscriberId(fieldValue);
else if (fieldName.equals("recipient"))
recipient = new SubscriberId(fieldValue);
else if (fieldName.equals("BK"))
BK = new BundleKey(fieldValue);
else if (fieldName.equals("crypt"))
crypt = Integer.parseInt(fieldValue);
else if (fieldName.equals("tail"))
tail = parseUnsignedLong(fieldValue);
else if (fieldName.equals("date"))
date = parseUnsignedLong(fieldValue);
else if (fieldName.equals("service"))
service = fieldValue;
else if (fieldName.equals("name"))
name = fieldValue;
else
extras.put(fieldName, fieldValue);
}
catch (AbstractId.InvalidHexException e) {
throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + text.substring(pos, nl - pos), e);
}
catch (NumberFormatException e) {
throw new RhizomeManifestParseException("invalid value at line " + lnum + ": " + text.substring(pos, nl - pos), e);
}
pos = nl + 1;
}
if (id == null)
if (im.id == null)
throw new RhizomeManifestParseException("missing 'id' field");
if (version == null)
if (im.version == null)
throw new RhizomeManifestParseException("missing 'version' field");
if (filesize == null)
if (im.filesize == null)
throw new RhizomeManifestParseException("missing 'filesize' field");
if (filesize != 0 && filehash == null)
if (im.filesize != 0 && im.filehash == null)
throw new RhizomeManifestParseException("missing 'filehash' field");
else if (filesize == 0 && filehash != null)
else if (im.filesize == 0 && im.filehash != null)
throw new RhizomeManifestParseException("spurious 'filehash' field");
RhizomeManifest m = new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name);
m.extraFields = extras;
RhizomeManifest m = new RhizomeManifest(im);
m.signatureBlock = sigblock;
m.textFormat = new byte[len];
System.arraycopy(bytes, off, m.textFormat, 0, m.textFormat.length);

View File

@ -26,18 +26,20 @@ import org.servalproject.servaldna.ServalDInterfaceException;
public class RhizomeManifestBundle {
public final long insertTime;
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 insertTime,
Long rowId,
Long insertTime,
SubscriberId author,
BundleSecret secret)
{
this.manifest = manifest;
this.rowId = rowId;
this.insertTime = insertTime;
this.author = author;
this.secret = secret;

View File

@ -29,19 +29,21 @@ public class RhizomePayloadBundle {
public final RhizomeManifest manifest;
public final InputStream payloadInputStream;
public final long insertTime;
public final Long rowId;
public final Long insertTime;
public final SubscriberId author;
public final BundleSecret secret;
protected RhizomePayloadBundle(RhizomeManifest manifest,
InputStream payloadInputStream,
long insertTime,
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

@ -29,19 +29,21 @@ public class RhizomePayloadRawBundle {
public final RhizomeManifest manifest;
public final InputStream rawPayloadInputStream;
public final long insertTime;
public final Long rowId;
public final Long insertTime;
public final SubscriberId author;
public final BundleSecret secret;
protected RhizomePayloadRawBundle(RhizomeManifest manifest,
InputStream rawPayloadInputStream,
long insertTime,
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,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(URL url) {
super("bundle cannot be modified", url);
}
}

View File

@ -20,36 +20,42 @@
package org.servalproject.test;
import java.io.IOException;
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.ServerControl;
import org.servalproject.servaldna.BundleId;
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 + sep +
"filehash=" + manifest.filehash + sep +
"sender=" + manifest.sender + sep +
"recipient=" + manifest.recipient + sep +
"date=" + manifest.date + sep +
"service=" + manifest.service + sep +
"name=" + manifest.name + sep +
"BK=" + manifest.BK;
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
@ -61,8 +67,8 @@ public class Rhizome {
RhizomeListBundle bundle;
while ((bundle = list.nextBundle()) != null) {
System.out.println(
"_rowId=" + bundle.rowId +
", _token=" + bundle.token +
"_token=" + bundle.token +
", _rowId=" + bundle.rowId +
", _insertTime=" + bundle.insertTime +
", _author=" + bundle.author +
", _fromHere=" + bundle.fromHere +
@ -89,9 +95,10 @@ public class Rhizome {
System.out.println("not found");
else {
System.out.println(
"_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" +
"_secret=" + bundle.secret + "\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"
);
FileOutputStream out = new FileOutputStream(dstpath);
@ -128,9 +135,10 @@ public class Rhizome {
out = null;
}
System.out.println(
"_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" +
"_secret=" + bundle.secret + "\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"
);
}
@ -168,9 +176,10 @@ public class Rhizome {
out = null;
}
System.out.println(
"_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" +
"_secret=" + bundle.secret + "\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"
);
}
@ -185,6 +194,43 @@ public class Rhizome {
System.exit(0);
}
static void rhizome_insert(String author, String manifestpath, String payloadPath, String manifestoutpath, String payloadName)
throws ServalDInterfaceException, IOException, InterruptedException, SubscriberId.InvalidHexException
{
ServalDClient client = new ServerControl().getRestfulClient();
try {
RhizomeIncompleteManifest manifest = RhizomeIncompleteManifest.fromTextFormat(new FileInputStream(manifestpath));
RhizomeInsertBundle bundle;
SubscriberId authorSid = author == null || author.length() == 0 ? null : new SubscriberId(author);
if (payloadName == null || payloadName.length() == 0)
payloadName = new File(payloadPath).getName();
if (payloadPath == null || payloadPath.length() == 0)
bundle = client.rhizomeInsert(authorSid, manifest);
else
bundle = client.rhizomeInsert(authorSid, manifest, 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());
}
System.exit(0);
}
public static void main(String... args)
{
if (args.length < 1)
@ -199,6 +245,13 @@ public class Rhizome {
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
);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);

View File

@ -376,16 +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:
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;
}
}
@ -516,6 +520,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;
@ -564,14 +570,14 @@ static int restful_rhizome_insert_end(struct http_request *hr)
assert(r->manifest != NULL);
assert(r->u.insert.write.file_length != RHIZOME_SIZE_UNSET);
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:
status_valid = 1;
if (r->manifest->filesize == RHIZOME_SIZE_UNSET)
rhizome_manifest_set_filesize(r->manifest, r->u.insert.write.file_length);
// fall through
case RHIZOME_PAYLOAD_STATUS_STORED:
status_valid = 1;
// TODO: check that stored hash matches received payload's hash
// fall through
case RHIZOME_PAYLOAD_STATUS_EMPTY:
@ -579,18 +585,25 @@ static int restful_rhizome_insert_end(struct http_request *hr)
assert(r->manifest->filesize != RHIZOME_SIZE_UNSET);
if (r->u.insert.payload_size == r->manifest->filesize)
break;
// fall through
case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE:
r->payload_status = RHIZOME_PAYLOAD_STATUS_WRONG_SIZE;
status_valid = 1;
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);
return http_request_rhizome_response(r, 403, NULL, strbuf_str(msg));
}
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
r->bundle_status = RHIZOME_BUNDLE_STATUS_INCONSISTENT;
return http_request_rhizome_response(r, 403, NULL, NULL);
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
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:
case RHIZOME_PAYLOAD_STATUS_WRONG_HASH:
case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL:
r->bundle_status = RHIZOME_BUNDLE_STATUS_NO_ROOM;
// fall through
case RHIZOME_PAYLOAD_STATUS_ERROR:
return http_request_rhizome_response(r, 403, NULL, NULL);
}
@ -605,17 +618,24 @@ 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;
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:
if (mout && mout != r->manifest)
@ -639,6 +659,8 @@ static int restful_rhizome_insert_end(struct http_request *hr)
case RHIZOME_BUNDLE_STATUS_ERROR:
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 (result == 500)

View File

@ -196,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")
@ -243,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"
}

View File

@ -30,12 +30,18 @@ setup() {
set_instance +A
executeOk_servald config \
set log.console.level debug \
set debug.httpd on
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
@ -165,6 +171,7 @@ test_RhizomeManifest() {
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
@ -275,4 +282,91 @@ test_RhizomePayloadDecryptedForeign() {
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
assertGrep nmanifest$n '^id='
tfw_cat -v nmanifest$n
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
}
runTests "$@"