Rhizome Java API: negative fetch tests

This commit is contained in:
Andrew Bettison 2014-07-04 17:48:40 +09:30
parent 2aec8f31a4
commit 3715c5bf0b
11 changed files with 548 additions and 129 deletions

View File

@ -1814,6 +1814,7 @@ static const char *httpResultString(int response_code)
switch (response_code) { switch (response_code) {
case 200: return "OK"; case 200: return "OK";
case 201: return "Created"; case 201: return "Created";
case 204: return "No Content";
case 206: return "Partial Content"; case 206: return "Partial Content";
case 400: return "Bad Request"; case 400: return "Bad Request";
case 401: return "Unauthorized"; case 401: return "Unauthorized";

View File

@ -21,6 +21,9 @@
package org.servalproject.servaldna.rhizome; package org.servalproject.servaldna.rhizome;
import java.lang.StringBuilder; 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.lang.reflect.InvocationTargetException;
import java.util.Map; import java.util.Map;
import java.util.List; import java.util.List;
@ -45,6 +48,7 @@ public class RhizomeCommon
{ {
private static class Status { private static class Status {
InputStream input_stream;
public int http_status_code; public int http_status_code;
public String http_status_message; public String http_status_message;
RhizomeBundleStatus bundle_status_code; RhizomeBundleStatus bundle_status_code;
@ -53,24 +57,35 @@ public class RhizomeCommon
String payload_status_message; String payload_status_message;
} }
protected static InputStream receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, RhizomeException protected static Status receiveResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException
{ {
int[] expected_response_codes = { expected_response_code }; int[] expected_response_codes = { expected_response_code };
return receiveResponse(conn, expected_response_codes); return receiveResponse(conn, expected_response_codes);
} }
protected static InputStream receiveResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, RhizomeException 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) { for (int code: expected_response_codes) {
if (conn.getResponseCode() == code) if (status.http_status_code == code) {
return conn.getInputStream(); status.input_stream = conn.getInputStream();
return status;
}
} }
if (!conn.getContentType().equals("application/json")) if (!conn.getContentType().equals("application/json"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) { if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) {
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII")); JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII"));
Status status = decodeRestfulStatus(json); decodeRestfulStatus(status, json);
throwRestfulResponseExceptions(status, conn.getURL()); return status;
}
throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode());
}
protected static void unexpectedResponse(Status status) throws ServalDInterfaceException
{
throw new ServalDInterfaceException( throw new ServalDInterfaceException(
"unexpected Rhizome failure, \"" + status.http_status_message + "\"" "unexpected Rhizome failure, \"" + status.http_status_message + "\""
+ (status.bundle_status_code == null ? "" : ", " + status.bundle_status_code) + (status.bundle_status_code == null ? "" : ", " + status.bundle_status_code)
@ -79,9 +94,8 @@ public class RhizomeCommon
+ (status.payload_status_message == null ? "" : ", " + status.payload_status_message + "\"") + (status.payload_status_message == null ? "" : ", " + status.payload_status_message + "\"")
); );
} }
throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode());
}
/*
protected static void throwRestfulResponseExceptions(Status status, URL url) throws RhizomeException, ServalDFailureException protected static void throwRestfulResponseExceptions(Status status, URL url) throws RhizomeException, ServalDFailureException
{ {
if (status.bundle_status_code != null) { if (status.bundle_status_code != null) {
@ -89,11 +103,15 @@ public class RhizomeCommon
case ERROR: case ERROR:
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + url); throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + url);
case NEW: case NEW:
throw new RhizomeManifestNotFoundException(url);
case SAME: case SAME:
throw new RhizomeManifestAlreadyStoredException(url);
case DUPLICATE: case DUPLICATE:
throw new RhizomeDuplicateBundleException(url);
case OLD: case OLD:
throw new RhizomeOutdatedBundleException(url);
case NO_ROOM: case NO_ROOM:
break; throw new RhizomeStoreFullException(url);
case INVALID: case INVALID:
throw new RhizomeInvalidManifestException(url); throw new RhizomeInvalidManifestException(url);
case FAKE: case FAKE:
@ -102,24 +120,8 @@ public class RhizomeCommon
throw new RhizomeInconsistencyException(url); throw new RhizomeInconsistencyException(url);
} }
} }
if (status.payload_status_code != null) {
switch (status.payload_status_code) {
case ERROR:
throw new ServalDFailureException("received rhizome_payload_status_code=ERROR(-1) from " + url);
case EMPTY:
case NEW:
case STORED:
case TOO_BIG:
case EVICTED:
break;
case WRONG_SIZE:
case WRONG_HASH:
throw new RhizomeInconsistencyException(url);
case CRYPTO_FAIL:
throw new RhizomeDecryptionException(url);
}
}
} }
*/
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, RhizomeException protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, RhizomeException
{ {
@ -129,21 +131,38 @@ public class RhizomeCommon
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, RhizomeException protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int[] expected_response_codes) throws IOException, ServalDInterfaceException, RhizomeException
{ {
InputStream in = receiveResponse(conn, expected_response_codes); Status status = receiveResponse(conn, expected_response_codes);
if (!conn.getContentType().equals("application/json")) if (!conn.getContentType().equals("application/json"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
return new JSONTokeniser(new InputStreamReader(in, "US-ASCII")); return new JSONTokeniser(new InputStreamReader(status.input_stream, "US-ASCII"));
} }
protected static Status decodeRestfulStatus(JSONTokeniser json) throws IOException, ServalDInterfaceException 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 { try {
Status status = new Status();
json.consume(JSONTokeniser.Token.START_OBJECT); json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("http_status_code"); json.consume("http_status_code");
json.consume(JSONTokeniser.Token.COLON); json.consume(JSONTokeniser.Token.COLON);
status.http_status_code = json.consume(Integer.class); int hs = json.consume(Integer.class);
json.consume(JSONTokeniser.Token.COMMA); 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("http_status_message");
json.consume(JSONTokeniser.Token.COLON); json.consume(JSONTokeniser.Token.COLON);
status.http_status_message = json.consume(String.class); status.http_status_message = json.consume(String.class);
@ -151,79 +170,185 @@ public class RhizomeCommon
while (tok == JSONTokeniser.Token.COMMA) { while (tok == JSONTokeniser.Token.COMMA) {
String label = json.consume(String.class); String label = json.consume(String.class);
json.consume(JSONTokeniser.Token.COLON); json.consume(JSONTokeniser.Token.COLON);
if (label.equals("rhizome_bundle_status_code")) if (label.equals("rhizome_bundle_status_code")) {
status.bundle_status_code = RhizomeBundleStatus.fromCode(json.consume(Integer.class)); RhizomeBundleStatus bs = RhizomeBundleStatus.fromCode(json.consume(Integer.class));
else if (label.equals("rhizome_bundle_status_message")) if (status.bundle_status_code == null)
status.bundle_status_message = json.consume(String.class); status.bundle_status_code = bs;
else if (label.equals("rhizome_payload_status_code")) else if (status.bundle_status_code != bs)
status.payload_status_code = RhizomePayloadStatus.fromCode(json.consume(Integer.class)); throw new ServalDInterfaceException("JSON/header conflict"
else if (label.equals("rhizome_payload_status_message")) + ", rhizome_bundle_status_code=" + bs.code
status.payload_status_message = json.consume(String.class); + " 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 else
json.unexpected(label); json.unexpected(label);
tok = json.nextToken(); tok = json.nextToken();
} }
json.match(tok, JSONTokeniser.Token.END_OBJECT); json.match(tok, JSONTokeniser.Token.END_OBJECT);
json.consume(JSONTokeniser.Token.EOF); json.consume(JSONTokeniser.Token.EOF);
return status;
} }
catch (JSONInputException e) { catch (JSONInputException e) {
throw new ServalDInterfaceException("malformed JSON status response", e); throw new ServalDInterfaceException("malformed JSON status response", e);
} }
} }
public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException, RhizomeException public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory connector, BundleId bid)
throws IOException, ServalDInterfaceException
{ {
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + ".rhm"); HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + ".rhm");
conn.connect(); conn.connect();
InputStream in = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK); Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
try {
dumpHeaders(conn, System.err);
decodeHeaderBundleStatus(status, conn);
switch (status.bundle_status_code) {
case NEW:
return null;
case SAME:
if (!conn.getContentType().equals("rhizome-manifest/text")) if (!conn.getContentType().equals("rhizome-manifest/text"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
RhizomeManifest manifest; RhizomeManifest manifest = RhizomeManifest.fromTextFormat(status.input_stream);
try { BundleExtra extra = bundleExtraFromHeaders(conn);
manifest = RhizomeManifest.fromTextFormat(in); return new RhizomeManifestBundle(manifest, extra.insertTime, extra.author, extra.secret);
case ERROR:
throw new ServalDFailureException("received rhizome_bundle_status_code=ERROR(-1) from " + conn.getURL());
}
} }
catch (RhizomeManifestParseException e) { catch (RhizomeManifestParseException e) {
throw new ServalDInterfaceException("malformed manifest from daemon", e); throw new ServalDInterfaceException("malformed manifest from daemon", e);
} }
finally { finally {
in.close(); if (status.input_stream != null)
status.input_stream.close();
} }
dumpHeaders(conn, System.err); unexpectedResponse(status);
long insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime"); return null;
SubscriberId author = header(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class);
BundleSecret secret = header(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class);
return new RhizomeManifestBundle(manifest, insertTime, author, secret);
} }
public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException, RhizomeException public static RhizomePayloadRawBundle rhizomePayloadRaw(ServalDHttpConnectionFactory connector, BundleId bid)
throws IOException, ServalDInterfaceException
{ {
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/raw.bin"); HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/raw.bin");
conn.connect(); conn.connect();
InputStream in = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK); Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
if (!conn.getContentType().equals("application/octet-stream")) try {
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
dumpHeaders(conn, System.err); dumpHeaders(conn, System.err);
decodeHeaderBundleStatus(status, conn);
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); RhizomeManifest manifest = manifestFromHeaders(conn);
long insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime"); BundleExtra extra = bundleExtraFromHeaders(conn);
SubscriberId author = header(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class); RhizomePayloadRawBundle ret = new RhizomePayloadRawBundle(manifest, status.input_stream, extra.insertTime, extra.author, extra.secret);
BundleSecret secret = header(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class); status.input_stream = null; // don't close when we return
return new RhizomePayloadRawBundle(manifest, in, insertTime, author, secret); return ret;
}
}
}
}
finally {
if (status.input_stream != null)
status.input_stream.close();
}
unexpectedResponse(status);
return null;
} }
public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid) throws IOException, ServalDInterfaceException, RhizomeException public static RhizomePayloadBundle rhizomePayload(ServalDHttpConnectionFactory connector, BundleId bid)
throws IOException, ServalDInterfaceException, RhizomeDecryptionException
{ {
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/decrypted.bin"); HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/decrypted.bin");
conn.connect(); conn.connect();
InputStream in = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK); Status status = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK);
if (!conn.getContentType().equals("application/octet-stream")) try {
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
dumpHeaders(conn, System.err); dumpHeaders(conn, System.err);
decodeHeaderBundleStatus(status, conn);
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); RhizomeManifest manifest = manifestFromHeaders(conn);
long insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime"); BundleExtra extra = bundleExtraFromHeaders(conn);
SubscriberId author = header(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class); RhizomePayloadBundle ret = new RhizomePayloadBundle(manifest, status.input_stream, extra.insertTime, extra.author, extra.secret);
BundleSecret secret = header(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class); status.input_stream = null; // don't close when we return
return new RhizomePayloadBundle(manifest, in, insertTime, author, secret); return ret;
}
}
}
}
finally {
if (status.input_stream != null)
status.input_stream.close();
}
unexpectedResponse(status);
return null;
} }
private static void dumpHeaders(HttpURLConnection conn, PrintStream out) private static void dumpHeaders(HttpURLConnection conn, PrintStream out)
@ -250,6 +375,21 @@ public class RhizomeCommon
return new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name); return new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name);
} }
private static class BundleExtra {
public long insertTime;
public SubscriberId author;
public BundleSecret secret;
}
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);
return extra;
}
private static String headerString(HttpURLConnection conn, String header) throws ServalDInterfaceException private static String headerString(HttpURLConnection conn, String header) throws ServalDInterfaceException
{ {
String str = conn.getHeaderField(header); String str = conn.getHeaderField(header);
@ -329,10 +469,30 @@ public class RhizomeCommon
private static <T> T headerOrNull(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException private static <T> T headerOrNull(HttpURLConnection conn, String header, Class<T> cls) throws ServalDInterfaceException
{ {
String str = conn.getHeaderField(header); String str = conn.getHeaderField(header);
try {
try {
Constructor<T> constructor = cls.getConstructor(String.class);
if (str == null) if (str == null)
return null; return null;
return constructor.newInstance(str);
}
catch (NoSuchMethodException e) {
}
try { try {
return (T) cls.getConstructor(String.class).newInstance(str); 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) { catch (InvocationTargetException e) {
throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e.getTargetException()); throw new ServalDInterfaceException("invalid header field: " + header + ": " + str, e.getTargetException());

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

@ -85,6 +85,9 @@ public class Rhizome {
try { try {
ServalDClient client = new ServerControl().getRestfulClient(); ServalDClient client = new ServerControl().getRestfulClient();
RhizomeManifestBundle bundle = client.rhizomeManifest(bid); RhizomeManifestBundle bundle = client.rhizomeManifest(bid);
if (bundle == null)
System.out.println("not found");
else {
System.out.println( System.out.println(
"_insertTime=" + bundle.insertTime + "\n" + "_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" + "_author=" + bundle.author + "\n" +
@ -95,6 +98,7 @@ public class Rhizome {
out.write(bundle.manifestText()); out.write(bundle.manifestText());
out.close(); out.close();
} }
}
catch (RhizomeException e) { catch (RhizomeException e) {
System.out.println(e.toString()); System.out.println(e.toString());
} }
@ -104,10 +108,17 @@ public class Rhizome {
static void rhizome_payload_raw(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException static void rhizome_payload_raw(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException
{ {
ServalDClient client = new ServerControl().getRestfulClient(); ServalDClient client = new ServerControl().getRestfulClient();
FileOutputStream out = new FileOutputStream(dstpath); FileOutputStream out = null;
try { try {
RhizomePayloadRawBundle bundle = client.rhizomePayloadRaw(bid); RhizomePayloadRawBundle bundle = client.rhizomePayloadRaw(bid);
if (bundle == null)
System.out.println("not found");
else {
InputStream in = bundle.rawPayloadInputStream; InputStream in = bundle.rawPayloadInputStream;
if (in == null)
System.out.println("no payload");
else {
out = new FileOutputStream(dstpath);
byte[] buf = new byte[4096]; byte[] buf = new byte[4096];
int n; int n;
while ((n = in.read(buf)) > 0) while ((n = in.read(buf)) > 0)
@ -115,6 +126,7 @@ public class Rhizome {
in.close(); in.close();
out.close(); out.close();
out = null; out = null;
}
System.out.println( System.out.println(
"_insertTime=" + bundle.insertTime + "\n" + "_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" + "_author=" + bundle.author + "\n" +
@ -122,6 +134,7 @@ public class Rhizome {
manifestFields(bundle.manifest, "\n") + "\n" manifestFields(bundle.manifest, "\n") + "\n"
); );
} }
}
catch (RhizomeException e) { catch (RhizomeException e) {
System.out.println(e.toString()); System.out.println(e.toString());
} }
@ -135,10 +148,17 @@ public class Rhizome {
static void rhizome_payload_decrypted(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException static void rhizome_payload_decrypted(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException
{ {
ServalDClient client = new ServerControl().getRestfulClient(); ServalDClient client = new ServerControl().getRestfulClient();
FileOutputStream out = new FileOutputStream(dstpath); FileOutputStream out = null;
try { try {
RhizomePayloadBundle bundle = client.rhizomePayload(bid); RhizomePayloadBundle bundle = client.rhizomePayload(bid);
if (bundle == null)
System.out.println("not found");
else {
InputStream in = bundle.payloadInputStream; InputStream in = bundle.payloadInputStream;
if (in == null)
System.out.println("no payload");
else {
out = new FileOutputStream(dstpath);
byte[] buf = new byte[4096]; byte[] buf = new byte[4096];
int n; int n;
while ((n = in.read(buf)) > 0) while ((n = in.read(buf)) > 0)
@ -146,6 +166,7 @@ public class Rhizome {
in.close(); in.close();
out.close(); out.close();
out = null; out = null;
}
System.out.println( System.out.println(
"_insertTime=" + bundle.insertTime + "\n" + "_insertTime=" + bundle.insertTime + "\n" +
"_author=" + bundle.author + "\n" + "_author=" + bundle.author + "\n" +
@ -153,6 +174,7 @@ public class Rhizome {
manifestFields(bundle.manifest, "\n") + "\n" manifestFields(bundle.manifest, "\n") + "\n"
); );
} }
}
catch (RhizomeException e) { catch (RhizomeException e) {
System.out.println(e.toString()); System.out.println(e.toString());
} }

View File

@ -708,7 +708,7 @@ static int restful_rhizome_bid_rhm(httpd_request *r, const char *remainder)
if (*remainder) if (*remainder)
return 404; return 404;
if (r->manifest == NULL) if (r->manifest == NULL)
return http_request_rhizome_response(r, 404, NULL, NULL); return http_request_rhizome_response(r, 403, NULL, NULL);
http_request_response_static(&r->http, 200, "rhizome-manifest/text", http_request_response_static(&r->http, 200, "rhizome-manifest/text",
(const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes (const char *)r->manifest->manifestdata, r->manifest->manifest_all_bytes
); );
@ -720,7 +720,7 @@ static int restful_rhizome_bid_raw_bin(httpd_request *r, const char *remainder)
if (*remainder) if (*remainder)
return 404; return 404;
if (r->manifest == NULL) if (r->manifest == NULL)
return http_request_rhizome_response(r, 404, NULL, NULL); return http_request_rhizome_response(r, 403, NULL, NULL);
if (r->manifest->filesize == 0) { if (r->manifest->filesize == 0) {
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0); http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);
return 1; return 1;
@ -737,7 +737,7 @@ static int restful_rhizome_bid_decrypted_bin(httpd_request *r, const char *remai
if (*remainder) if (*remainder)
return 404; return 404;
if (r->manifest == NULL) if (r->manifest == NULL)
return http_request_rhizome_response(r, 404, NULL, NULL); return http_request_rhizome_response(r, 403, NULL, NULL);
if (r->manifest->filesize == 0) { if (r->manifest->filesize == 0) {
// TODO use Content Type from manifest (once it is implemented) // TODO use Content Type from manifest (once it is implemented)
http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0); http_request_response_static(&r->http, 200, CONTENT_TYPE_BLOB, "", 0);

View File

@ -31,6 +31,7 @@ setup() {
executeOk_servald config \ executeOk_servald config \
set log.console.level debug \ set log.console.level debug \
set debug.httpd on set debug.httpd on
set_extra_config
create_identities 4 create_identities 4
start_servald_server start_servald_server
} }
@ -169,6 +170,17 @@ test_RhizomeManifest() {
done 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" doc_RhizomePayloadRaw="Java API fetch Rhizome raw payload"
setup_RhizomePayloadRaw() { setup_RhizomePayloadRaw() {
setup setup
@ -186,6 +198,33 @@ test_RhizomePayloadRaw() {
done 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" doc_RhizomePayloadDecrypted="Java API fetch Rhizome decrypted payload"
setup_RhizomePayloadDecrypted() { setup_RhizomePayloadDecrypted() {
setup setup
@ -203,6 +242,22 @@ test_RhizomePayloadDecrypted() {
done 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" doc_RhizomePayloadDecryptedForeign="Java API cannot fetch foreign Rhizome decrypted payload"
setup_RhizomePayloadDecryptedForeign() { setup_RhizomePayloadDecryptedForeign() {
setup setup

View File

@ -115,7 +115,7 @@ teardown_AuthBasicWrong() {
teardown teardown
} }
doc_RhizomeList="HTTP RESTful list Rhizome bundles as JSON" doc_RhizomeList="HTTP RESTful list 100 Rhizome bundles as JSON"
setup_RhizomeList() { setup_RhizomeList() {
setup setup
NBUNDLES=100 NBUNDLES=100
@ -274,12 +274,12 @@ test_RhizomeManifestNonexist() {
--basic --user harry:potter \ --basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT.rhm" "http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT.rhm"
tfw_cat http.headers http.content tfw_cat http.headers http.content
assertStdoutIs 404 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-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-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-Code:"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:" assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
assertJq http.content 'contains({"http_status_code": 404})' assertJq http.content 'contains({"http_status_code": 403})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})' assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store" assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
} }
@ -322,12 +322,12 @@ test_RhizomePayloadRawNonexistManifest() {
--basic --user harry:potter \ --basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/raw.bin" "http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/raw.bin"
tfw_cat http.headers http.content tfw_cat http.headers http.content
assertStdoutIs 404 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-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-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-Code:"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:" assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
assertJq http.content 'contains({"http_status_code": 404})' assertJq http.content 'contains({"http_status_code": 403})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})' assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store" assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
} }
@ -425,12 +425,12 @@ test_RhizomePayloadDecryptedNonexistManifest() {
--basic --user harry:potter \ --basic --user harry:potter \
"http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/decrypted.bin" "http://$addr_localhost:$PORTA/restful/rhizome/$BID_NONEXISTENT/decrypted.bin"
tfw_cat http.headers http.content tfw_cat http.headers http.content
assertStdoutIs 404 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-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-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-Code:"
assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:" assertGrep --matches=0 --ignore-case http.headers$n "^Serval-Rhizome-Result-Payload-Status-Message:"
assertJq http.content 'contains({"http_status_code": 404})' assertJq http.content 'contains({"http_status_code": 403})'
assertJq http.content 'contains({"rhizome_bundle_status_code": 0})' assertJq http.content 'contains({"rhizome_bundle_status_code": 0})'
assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store" assertJqGrep --ignore-case http.content '.rhizome_bundle_status_message' "bundle new to store"
} }