diff --git a/java/org/servalproject/servaldna/ServalDClient.java b/java/org/servalproject/servaldna/ServalDClient.java index 1788d2fb..780e1f68 100644 --- a/java/org/servalproject/servaldna/ServalDClient.java +++ b/java/org/servalproject/servaldna/ServalDClient.java @@ -38,6 +38,7 @@ import org.servalproject.servaldna.rhizome.RhizomeCommon; import org.servalproject.servaldna.rhizome.RhizomeBundleList; import org.servalproject.servaldna.rhizome.RhizomeManifestBundle; +import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle; import org.servalproject.servaldna.meshms.MeshMSCommon; import org.servalproject.servaldna.meshms.MeshMSConversationList; import org.servalproject.servaldna.meshms.MeshMSMessageList; @@ -74,6 +75,11 @@ public RhizomeManifestBundle rhizomeManifest(BundleId bid) throws ServalDInterfa return RhizomeCommon.rhizomeManifest(this, bid); } + public RhizomePayloadRawBundle rhizomePayloadRaw(BundleId bid) throws ServalDInterfaceException, IOException + { + return RhizomeCommon.rhizomePayloadRaw(this, bid); + } + public MeshMSConversationList meshmsListConversations(SubscriberId sid) throws ServalDInterfaceException, IOException, MeshMSException { MeshMSConversationList list = new MeshMSConversationList(this, sid); diff --git a/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java b/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java index 4c7b3cd9..30eb9279 100644 --- a/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java +++ b/java/org/servalproject/servaldna/rhizome/RhizomeCommon.java @@ -20,16 +20,20 @@ package org.servalproject.servaldna.rhizome; +import java.lang.StringBuilder; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.List; import java.io.IOException; +import java.io.PrintStream; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import org.servalproject.json.JSONTokeniser; import org.servalproject.json.JSONInputException; import org.servalproject.servaldna.BundleId; +import org.servalproject.servaldna.FileHash; +import org.servalproject.servaldna.BundleKey; import org.servalproject.servaldna.SubscriberId; import org.servalproject.servaldna.BundleSecret; import org.servalproject.servaldna.ServalDHttpConnectionFactory; @@ -117,18 +121,52 @@ public static RhizomeManifestBundle rhizomeManifest(ServalDHttpConnectionFactory finally { in.close(); } - Map> headers = conn.getHeaderFields(); - for (Map.Entry> e: headers.entrySet()) { - for (String v: e.getValue()) { - System.err.println("received header " + e.getKey() + ": " + v); - } - } + dumpHeaders(conn, System.err); long insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime"); 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 + { + HttpURLConnection conn = connector.newServalDHttpConnection("/restful/rhizome/" + bid.toHex() + "/raw.bin"); + conn.connect(); + InputStream in = RhizomeCommon.receiveResponse(conn, HttpURLConnection.HTTP_OK); + if (!conn.getContentType().equals("application/octet-stream")) + throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType()); + dumpHeaders(conn, System.err); + RhizomeManifest manifest = manifestFromHeaders(conn); + long insertTime = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Inserttime"); + SubscriberId author = header(conn, "Serval-Rhizome-Bundle-Author", SubscriberId.class); + BundleSecret secret = header(conn, "Serval-Rhizome-Bundle-Secret", BundleSecret.class); + return new RhizomePayloadRawBundle(manifest, in, insertTime, author, secret); + } + + private static void dumpHeaders(HttpURLConnection conn, PrintStream out) + { + for (Map.Entry> e: conn.getHeaderFields().entrySet()) + for (String v: e.getValue()) + out.println("received header " + e.getKey() + ": " + v); + } + + private static RhizomeManifest manifestFromHeaders(HttpURLConnection conn) throws ServalDInterfaceException + { + BundleId id = header(conn, "Serval-Rhizome-Bundle-Id", BundleId.class); + long version = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Version"); + long filesize = headerUnsignedLong(conn, "Serval-Rhizome-Bundle-Filesize"); + FileHash filehash = filesize == 0 ? null : header(conn, "Serval-Rhizome-Bundle-Filehash", FileHash.class); + SubscriberId sender = headerOrNull(conn, "Serval-Rhizome-Bundle-Sender", SubscriberId.class); + SubscriberId recipient = headerOrNull(conn, "Serval-Rhizome-Bundle-Recipient", SubscriberId.class); + BundleKey BK = headerOrNull(conn, "Serval-Rhizome-Bundle-BK", BundleKey.class); + Integer crypt = headerIntegerOrNull(conn, "Serval-Rhizome-Bundle-Crypt"); + Long tail = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Tail"); + Long date = headerUnsignedLongOrNull(conn, "Serval-Rhizome-Bundle-Date"); + String service = conn.getHeaderField("Serval-Rhizome-Bundle-Service"); + String name = headerQuotedStringOrNull(conn, "Serval-Rhizome-Bundle-Name"); + return new RhizomeManifest(id, version, filesize, filehash, sender, recipient, BK, crypt, tail, date, service, name); + } + private static String headerString(HttpURLConnection conn, String header) throws ServalDInterfaceException { String str = conn.getHeaderField(header); @@ -137,22 +175,58 @@ private static String headerString(HttpURLConnection conn, String header) throws return str; } - private static int headerInteger(HttpURLConnection conn, String header) throws ServalDInterfaceException + private static String headerQuotedStringOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException + { + String quoted = conn.getHeaderField(header); + if (quoted == null) + return null; + if (quoted.length() == 0 || quoted.charAt(0) != '"') + throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at start of quoted-string"); + boolean slosh = false; + boolean end = false; + StringBuilder b = new StringBuilder(quoted.length()); + for (int i = 1; i < quoted.length(); ++i) { + char c = quoted.charAt(i); + if (end) + throw new ServalDInterfaceException("malformed header field: " + header + ": spurious character after quoted-string"); + if (c < ' ' || c > '~') + throw new ServalDInterfaceException("malformed header field: " + header + ": invalid character in quoted-string"); + if (slosh) { + b.append(c); + slosh = false; + } + else if (c == '"') + end = true; + else if (c == '\\') + slosh = true; + else + b.append(c); + } + if (!end) + throw new ServalDInterfaceException("malformed header field: " + header + ": missing quote at end of quoted-string"); + return b.toString(); + } + + private static Integer headerIntegerOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException { - String str = headerString(conn, header); + String str = conn.getHeaderField(header); + if (str == null) + return null; try { - return Integer.parseInt(str); + return Integer.valueOf(str); } catch (NumberFormatException e) { } throw new ServalDInterfaceException("invalid header field: " + header + ": " + str); } - private static long headerUnsignedLong(HttpURLConnection conn, String header) throws ServalDInterfaceException + private static Long headerUnsignedLongOrNull(HttpURLConnection conn, String header) throws ServalDInterfaceException { - String str = headerString(conn, header); + String str = conn.getHeaderField(header); + if (str == null) + return null; try { - long value = Long.parseLong(str); + Long value = Long.valueOf(str); if (value >= 0) return value; } @@ -161,9 +235,19 @@ private static long headerUnsignedLong(HttpURLConnection conn, String header) th throw new ServalDInterfaceException("invalid header field: " + header + ": " + str); } - private static T header(HttpURLConnection conn, String header, Class cls) throws ServalDInterfaceException + private static long headerUnsignedLong(HttpURLConnection conn, String header) throws ServalDInterfaceException + { + Long value = headerUnsignedLongOrNull(conn, header); + if (value == null) + throw new ServalDInterfaceException("missing header field: " + header); + return value; + } + + private static T headerOrNull(HttpURLConnection conn, String header, Class cls) throws ServalDInterfaceException { - String str = headerString(conn, header); + String str = conn.getHeaderField(header); + if (str == null) + return null; try { return (T) cls.getConstructor(String.class).newInstance(str); } @@ -175,4 +259,12 @@ private static T header(HttpURLConnection conn, String header, Class cls) } } + private static T header(HttpURLConnection conn, String header, Class cls) throws ServalDInterfaceException + { + T value = headerOrNull(conn, header, cls); + if (value == null) + throw new ServalDInterfaceException("missing header field: " + header); + return value; + } + } diff --git a/java/org/servalproject/servaldna/rhizome/RhizomePayloadRawBundle.java b/java/org/servalproject/servaldna/rhizome/RhizomePayloadRawBundle.java new file mode 100644 index 00000000..ca00792d --- /dev/null +++ b/java/org/servalproject/servaldna/rhizome/RhizomePayloadRawBundle.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2014 Serval Project Inc. + * + * This file is part of Serval Software (http://www.servalproject.org) + * + * Serval Software is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this source code; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.servalproject.servaldna.rhizome; + +import java.io.InputStream; +import org.servalproject.servaldna.SubscriberId; +import org.servalproject.servaldna.BundleSecret; +import org.servalproject.servaldna.ServalDInterfaceException; + +public class RhizomePayloadRawBundle { + + public final RhizomeManifest manifest; + public final InputStream rawPayloadInputStream; + public final long insertTime; + public final SubscriberId author; + public final BundleSecret secret; + + protected RhizomePayloadRawBundle(RhizomeManifest manifest, + InputStream rawPayloadInputStream, + long insertTime, + SubscriberId author, + BundleSecret secret) + + { + this.rawPayloadInputStream = rawPayloadInputStream; + this.manifest = manifest; + this.insertTime = insertTime; + this.author = author; + this.secret = secret; + } + +} diff --git a/java/org/servalproject/test/Rhizome.java b/java/org/servalproject/test/Rhizome.java index 5538c13a..0e572191 100644 --- a/java/org/servalproject/test/Rhizome.java +++ b/java/org/servalproject/test/Rhizome.java @@ -32,6 +32,7 @@ import org.servalproject.servaldna.rhizome.RhizomeListBundle; import org.servalproject.servaldna.rhizome.RhizomeBundleList; import org.servalproject.servaldna.rhizome.RhizomeManifestBundle; +import org.servalproject.servaldna.rhizome.RhizomePayloadRawBundle; public class Rhizome { @@ -90,6 +91,34 @@ static void rhizome_manifest(BundleId bid, String dstpath) throws ServalDInterfa System.exit(0); } + static void rhizome_payload_raw(BundleId bid, String dstpath) throws ServalDInterfaceException, IOException, InterruptedException + { + ServalDClient client = new ServerControl().getRestfulClient(); + FileOutputStream out = new FileOutputStream(dstpath); + try { + RhizomePayloadRawBundle bundle = client.rhizomePayloadRaw(bid); + InputStream in = bundle.rawPayloadInputStream; + byte[] buf = new byte[4096]; + int n; + while ((n = in.read(buf)) > 0) + out.write(buf, 0, n); + in.close(); + out.close(); + out = null; + System.out.println( + "_insertTime=" + bundle.insertTime + "\n" + + "_author=" + bundle.author + "\n" + + "_secret=" + bundle.secret + "\n" + + manifestFields(bundle.manifest, "\n") + "\n" + ); + } + finally { + if (out != null) + out.close(); + } + System.exit(0); + } + public static void main(String... args) { if (args.length < 1) @@ -100,6 +129,8 @@ public static void main(String... args) rhizome_list(); else if (methodName.equals("rhizome-manifest")) rhizome_manifest(new BundleId(args[1]), args[2]); + else if (methodName.equals("rhizome-payload-raw")) + rhizome_payload_raw(new BundleId(args[1]), args[2]); } catch (Exception e) { e.printStackTrace(); System.exit(1); diff --git a/rhizome_restful.c b/rhizome_restful.c index 4c5647b8..f55a9639 100644 --- a/rhizome_restful.c +++ b/rhizome_restful.c @@ -764,8 +764,24 @@ static void render_manifest_headers(struct http_request *hr, strbuf sb) strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filesize: %"PRIu64"\r\n", m->filesize); if (m->filesize != 0) strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Filehash: %s\r\n", alloca_tohex_rhizome_filehash_t(m->filehash)); + if (m->has_sender) + strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Sender: %s\r\n", alloca_tohex_sid_t(m->sender)); + if (m->has_recipient) + strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Recipient: %s\r\n", alloca_tohex_sid_t(m->recipient)); if (m->has_bundle_key) strbuf_sprintf(sb, "Serval-Rhizome-Bundle-BK: %s\r\n", alloca_tohex_rhizome_bk_t(m->bundle_key)); + switch (m->payloadEncryption) { + case PAYLOAD_CRYPT_UNKNOWN: + break; + case PAYLOAD_CLEAR: + strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 0\r\n"); + break; + case PAYLOAD_ENCRYPTED: + strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Crypt: 1\r\n"); + break; + } + if (m->is_journal) + strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Tail: %"PRIu64"\r\n", m->tail); if (m->has_date) strbuf_sprintf(sb, "Serval-Rhizome-Bundle-Date: %"PRIu64"\r\n", m->date); if (m->name) { diff --git a/testdefs_rhizome.sh b/testdefs_rhizome.sh index 98a4ac91..c3e3ff38 100644 --- a/testdefs_rhizome.sh +++ b/testdefs_rhizome.sh @@ -525,7 +525,7 @@ rhizome_add_bundles() { if $encrypted; then echo "crypt=1" >file$n.manifest fi - executeOk_servald rhizome add file $SID file$n file$n.manifest + executeOk_servald rhizome add file "$SID" file$n file$n.manifest extract_stdout_manifestid BID[$n] extract_stdout_version VERSION[$n] extract_stdout_filesize SIZE[$n] diff --git a/tests/rhizomejava b/tests/rhizomejava index ea0494b5..0b199221 100755 --- a/tests/rhizomejava +++ b/tests/rhizomejava @@ -161,7 +161,7 @@ setup_RhizomeManifest() { } test_RhizomeManifest() { for n in 0 1 2; do - executeJavaOk org.servalproject.test.Rhizome rhizome-manifest ${BID[$n]} bundle$n.rhm + executeJavaOk org.servalproject.test.Rhizome rhizome-manifest "${BID[$n]}" bundle$n.rhm tfw_cat --stdout --stderr assert_metadata $n tfw_cat -v file$n.manifest -v bundle$n.rhm @@ -169,4 +169,19 @@ test_RhizomeManifest() { done } +doc_RhizomePayloadRaw="Java API fetch Rhizome raw payload" +setup_RhizomePayloadRaw() { + setup + rhizome_add_bundles $SIDA1 0 1 + rhizome_add_bundles --encrypted $SIDA1 2 3 +} +test_RhizomePayloadRaw() { + for n in 0 1 2 3; do + executeJavaOk org.servalproject.test.Rhizome rhizome-payload-raw "${BID[$n]}" raw.bin$n + tfw_cat --stdout --stderr + assert_metadata $n + assert cmp raw$n raw.bin$n + done +} + runTests "$@"