Changed archive SSK to include parent version info.
diff --git a/src/fniki/wiki/ArchiveManager.java b/src/fniki/wiki/ArchiveManager.java --- a/src/fniki/wiki/ArchiveManager.java +++ b/src/fniki/wiki/ArchiveManager.java @@ -127,7 +127,10 @@ public class ArchiveManager { public void load(String uri) throws IOException { FreenetIO io = new FreenetIO(mFcpHost, mFcpPort); io.setRequestUri(uri); - mArchive = Archive.load(io); + Archive archive = Archive.load(io); + validateUriHashes(archive, uri, true); + + mArchive = archive; mFileManifest = FileManifest.fromArchiveRootObject(mArchive); mOverlay = new LocalWikiChanges(mArchive, mFileManifest); // DCI: why copy ? mParentUri = uri; @@ -146,12 +149,76 @@ public class ArchiveManager { } } - private String getInsertUri(Archive archive) throws IOException { + // The name of a jfniki archive includes the hash of the + // full archive manifest file, and hashes of the SSK of + // it's parent(s). + // + // use '_' instead of '|' because '|' gets percent escaped. + // <sha1_of_am_file>[<underbar>sha1_of_parent_ssk] + private static String makeUriNamePart(Archive archive) throws IOException { // Generate a unique SSK. LinkDigest digest = archive.getRootObject(RootObjectKind.ARCHIVE_MANIFEST); // The hash of the actual file, not just the chain head SHA. LinkDigest fileHash = IOUtil.getFileDigest(archive.getFile(digest)); - return mPrivateSSK + fileHash.hexDigest(8); + + String parentKeyHashes = ""; + LinkDigest refsDigest = archive.getRootObject(RootObjectKind.PARENT_REFERENCES); + if (!refsDigest.isNullDigest()) { + ExternalRefs refs = ExternalRefs.fromBytes(archive.getFile(refsDigest)); + for (ExternalRefs.Reference ref : refs.mRefs) { + if (ref.mKind != ExternalRefs.KIND_FREENET) { + continue; + } + parentKeyHashes += "_"; + parentKeyHashes += IOUtil.getFileDigest(IOUtil.toStreamAsUtf8(ref.mExternalKey)) + .hexDigest(8); + } + } + + return fileHash.hexDigest(8) + parentKeyHashes; + } + + private static void validateUriHashes(Archive archive, + String uri, + boolean allowNoParents) throws IOException { + String[] fields = uri.split("/"); + if (fields.length != 2) { + throw new IOException("Couldn't parse uri: " + uri); + } + fields = fields[1].split("_"); + if (fields.length < 1) { + throw new IOException("Couldn't parse uri: " + uri); + } + + String[] expected = makeUriNamePart(archive).split("_"); + + for (int index = 0; index < expected.length; index++) { + if (index >= fields.length) { + continue; + } + } + + if (fields.length == 1 && fields[0].equals(expected[0])) { + // LATER: tighten up. + // For now, allow old URIs that don't have parent info hashes. + return; + } + + if (expected.length != fields.length) { + throw new IOException("Hash validation failed(0)! Inserter is lying about contents."); + } + + for (int index = 0; index < expected.length; index++) { + if (!expected[index].equals(fields[index])) { + throw new IOException("Hash validation failed(1)! Inserter is lying about contents."); + } + } + } + + private String getInsertUri(Archive archive) throws IOException { + String uri = mPrivateSSK + makeUriNamePart(archive); + validateUriHashes(archive, uri, false); + return uri; } // DCI: commitAndPushToFreenet() ?