Skip to content

Commit

Permalink
feat (core): Improve error message of new ?integrity=... generic URL …
Browse files Browse the repository at this point in the history
…query parameter
  • Loading branch information
vorburger committed Jan 4, 2025
1 parent a3f5da1 commit 78b92af
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@
import dev.enola.common.io.iri.URIs;
import dev.enola.common.io.resource.*;

import io.ipfs.multibase.Multibase;
import io.ipfs.multihash.Multihash;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
Expand All @@ -46,15 +43,16 @@ public Resource getResource(URI uri) {
if (original == null) return null;
var integrity = URIs.getQueryMap(uri).get("integrity");
if (integrity == null) return original;
var multihash = Multihash.decode(integrity);
var multihash = MultihashWithMultibase.decode(integrity);
return new IntegrityValidatingDelegatingResource(original, multihash);
}
}

private final Multihash expectedHash;
private final MultihashWithMultibase expectedHash;
private boolean validated = false;

public IntegrityValidatingDelegatingResource(Resource delegate, Multihash expectedHash) {
public IntegrityValidatingDelegatingResource(
Resource delegate, MultihashWithMultibase expectedHash) {
super(delegate);
this.expectedHash = expectedHash;
}
Expand All @@ -75,7 +73,7 @@ private synchronized void validate() {
if (validated) return;

var delegateByteSource = delegate.byteSource();
var hashFunction = Multihashes.toGuavaHashFunction(expectedHash);
var hashFunction = Multihashes.toGuavaHashFunction(expectedHash.multihash());

Hasher hasher;
var optSize = delegateByteSource.sizeIfKnown();
Expand All @@ -93,16 +91,12 @@ private synchronized void validate() {
}
var hashCode = hasher.hash();
var actualBytes = hashCode.asBytes();
var actualMultihash = new Multihash(expectedHash.getType(), actualBytes);
var actualHash = expectedHash.copy(actualBytes);

// TODO It would be useful if Multihash had an equalsTo() method to avoid byte array copy
if (!expectedHash.equals(actualMultihash)) {
// TODO Fix that this looses the "original" Base from ?integrity=..
var expectedHashString = Multihashes.toString(expectedHash, Multibase.Base.Base64);
var actualMultihashString =
Multihashes.toString(actualMultihash, Multibase.Base.Base64);
if (!expectedHash.equals(actualHash)) {
throw new IntegrityViolationException(
"Expected " + expectedHashString + " but got " + actualMultihashString);
"Expected " + expectedHash + " but got " + actualHash);
}

validated = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@
*/
package dev.enola.common.io.hashbrown;

import static com.google.common.truth.Truth.assertThat;

import static dev.enola.common.context.testlib.SingletonRule.$;

import static org.junit.Assert.assertThrows;

import dev.enola.common.context.testlib.SingletonRule;
import dev.enola.common.io.mediatype.MediaTypeProviders;
import dev.enola.common.io.resource.*;
Expand All @@ -33,26 +37,33 @@ public class IntegrityValidatingDelegatingResourceTest {

public @Rule SingletonRule r1 = $(MediaTypeProviders.set());

@Test(expected = IntegrityViolationException.class)
@Test
public void example() {
// This is useful "to see it", e.g. when adding new tests
// throw new RuntimeException(
// Multihashes.example(Multihash.Type.sha2_512, Multibase.Base.Base58BTC));
}

@Test
public void bad() {
rp.get("classpath:/test.png?integrity=m1QEQAAAAAAAAAAAAAAAAAAAAAA").charSource();
// TODO Validate that IntegrityViolationException contains m1... and not fz...

// TODO Remove...
/*
throw new RuntimeException(
Multihashes.toString(
new Multihash(
Multihash.Type.md5,
new byte[] {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}),
Multibase.Base.Base64));
*/
IntegrityViolationException thrown =
assertThrows(
IntegrityViolationException.class,
() -> {
check(
"z8VsnXyGnRwJpnrQXB8KcLstvgFYGZ2f5BCm3DVndcNZ8NswtkCqsut69e7yd1FKNtettjgy669GNVt8VSTGxkAiJaB");
});
assertThat(thrown).hasMessageThat().contains("z8VsnXy"); // actual
assertThat(thrown).hasMessageThat().contains("z8Vw9J6"); // expected
}

@Test
public void good() {
rp.get("classpath:/test.png?integrity=m1QEQtoy0Os8CMvMKItSdcFkRow").byteSource();
check(
"z8Vw9J6ZbuvzUV7wuau1uws8hw2QTZUeFfgwdyre5LmC1yFUoR2b7WyR2M8CaDR9Z6A2FafkPjmETcLKetbBr5d2Qv7");
}

void check(String hash) {
rp.get("classpath:/test.png?integrity=" + hash).byteSource();
}
}
76 changes: 76 additions & 0 deletions java/dev/enola/common/io/hashbrown/MultihashWithMultibase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2025 The Enola <https://enola.dev> Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.enola.common.io.hashbrown;

import io.ipfs.multibase.Multibase;
import io.ipfs.multihash.Multihash;

import java.util.Objects;

/**
* An alternative (wrapper, actually) over {@link io.ipfs.multihash.Multihash} which "remembers" its
* encoding base.
*/
public final class MultihashWithMultibase {
// TODO extends Multihash ?

private final Multibase.Base multibase;
private final Multihash multihash;

public static MultihashWithMultibase decode(String encoded) {
Multibase.Base base;
if (encoded.length() == 46 && encoded.startsWith("Qm"))
// TODO Base58BTC or Base58Flickr ?
base = Multibase.Base.Base58BTC;
else base = Multibase.Base.lookup(encoded.charAt(0));
return new MultihashWithMultibase(base, Multihash.decode(encoded));
}

private MultihashWithMultibase(Multibase.Base multibase, Multihash decode) {
this.multibase = multibase;
this.multihash = decode;
}

// TODO Give this method a better name...
public MultihashWithMultibase copy(byte[] bytes) {
var newMultihash = new Multihash(multihash.getType(), bytes);
return new MultihashWithMultibase(multibase, newMultihash);
}

public Multihash multihash() {
return multihash;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof MultihashWithMultibase other)) return false;
return multihash.equals(other.multihash) && multibase.equals(other.multibase);
}

@Override
public int hashCode() {
return Objects.hash(multihash, multibase);
}

// TODO equals & hashCode

@Override
public String toString() {
return Multihashes.toString(multihash, multibase);
}
}
18 changes: 16 additions & 2 deletions java/dev/enola/common/io/hashbrown/Multihashes.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ public final class Multihashes {
public static HashFunction toGuavaHashFunction(Multihash multihash) {
var type = multihash.getType();
return switch (type) {
case md5 -> Hashing.md5();
// TODO Add all other supported types...
// @Deprecated case md5 -> Hashing.md5();
// @Deprecated case sha1 -> Hashing.sha1();
case sha2_256 -> Hashing.sha256();
case sha2_512 -> Hashing.sha512();
// Not suitable, as it's tiny: case murmur3 -> Hashing.murmur3_32_fixed();

// TODO What about all other supported types?!
// See https://github.com/multiformats/java-multihash/issues/41 ...

default -> throw new IllegalArgumentException("Unsupported Multihash type: " + type);
};
}
Expand All @@ -39,5 +46,12 @@ public static String toString(Multihash multihash, Multibase.Base base) {
return Multibase.encode(base, multihash.toBytes());
}

public static String example(Multihash.Type type, Multibase.Base base) {
var bytes = new byte[type.length];
// for (int i = 0; i < type.length; i++) bytes[i] = (byte) 7;
var multihash = new Multihash(type, bytes);
return toString(multihash, base);
}

private Multihashes() {}
}

0 comments on commit 78b92af

Please sign in to comment.