This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
commit 2ee3a773ce3baf1549ca55dabe1deda5d0fababb Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Nov 9 11:01:32 2024 -0500 Add support to MessageDigestInputStream for setting a consumer for ProxyInputStream.afterRead(int) --- src/changes/changes.xml | 1 + .../commons/io/input/MessageDigestInputStream.java | 35 ++++++++++++--------- .../io/input/MessageDigestInputStreamTest.java | 36 +++++++++++++++++++++- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a8c8b926f..a68ce5623 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -73,6 +73,7 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="add" due-to="Gary Gregory">Add support to ThrottledInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add support to ObservableInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add support to MessageDigestCalculatingInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action> + <action dev="ggregory" type="add" due-to="Gary Gregory">Add support to MessageDigestInputStream for setting a consumer for ProxyInputStream.afterRead(int).</action> <!-- UPDATE --> <action dev="ggregory" type="update" due-to="Gary Gregory">Bump org.apache.commons:commons-parent from 74 to 78 #670, #676, #679, #688.</action> <action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons.bytebuddy.version from 1.15.1 to 1.15.10 #672, #673, #685, #686, #694, #696, #698.</action> diff --git a/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java b/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java index 01b03996f..b3a0082fd 100644 --- a/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java +++ b/src/main/java/org/apache/commons/io/input/MessageDigestInputStream.java @@ -20,10 +20,9 @@ import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Objects; -import org.apache.commons.io.build.AbstractStreamBuilder; - /** * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer}, * which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum. @@ -62,12 +61,18 @@ public final class MessageDigestInputStream extends ObservableInputStream { * <p> * You must specify a message digest algorithm name or instance. * </p> + * <p> + * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> + * </p> * * @see #get() */ // @formatter:on - public static class Builder extends AbstractStreamBuilder<MessageDigestInputStream, Builder> { + public static class Builder extends AbstractBuilder<Builder> { + /** + * No default by design, call MUST set one. + */ private MessageDigest messageDigest; /** @@ -97,16 +102,16 @@ public final class MessageDigestInputStream extends ObservableInputStream { * @throws IOException if an I/O error occurs. * @see #getInputStream() */ - @SuppressWarnings("resource") @Override public MessageDigestInputStream get() throws IOException { - return new MessageDigestInputStream(getInputStream(), messageDigest); + setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest))); + return new MessageDigestInputStream(this); } /** * Sets the message digest. * <p> - * The MD5 cryptographic algorithm is weak and should not be used. + * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> * </p> * * @param messageDigest the message digest. @@ -120,7 +125,7 @@ public final class MessageDigestInputStream extends ObservableInputStream { /** * Sets the name of the name of the message digest algorithm. * <p> - * The MD5 cryptographic algorithm is weak and should not be used. + * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> * </p> * * @param algorithm the name of the algorithm. See the MessageDigest section in the @@ -173,6 +178,9 @@ public final class MessageDigestInputStream extends ObservableInputStream { return new Builder(); } + /** + * A non-null MessageDigest. + */ private final MessageDigest messageDigest; /** @@ -181,23 +189,22 @@ public final class MessageDigestInputStream extends ObservableInputStream { * The MD5 cryptographic algorithm is weak and should not be used. * </p> * - * @param inputStream the stream to calculate the message digest for - * @param messageDigest the message digest to use + * @param builder A builder use to get the stream to calculate the message digest and the message digest to use * @throws NullPointerException if messageDigest is null. */ - private MessageDigestInputStream(final InputStream inputStream, final MessageDigest messageDigest) { - super(inputStream, new MessageDigestMaintainingObserver(messageDigest)); - this.messageDigest = messageDigest; + private MessageDigestInputStream(final Builder builder) throws IOException { + super(builder); + this.messageDigest = Objects.requireNonNull(builder.messageDigest, "builder.messageDigest"); } /** - * Gets the {@link MessageDigest}, which is being used for generating the checksum. + * Gets the {@link MessageDigest}, which is being used for generating the checksum, never null. * <p> * <em>Note</em>: The checksum will only reflect the data, which has been read so far. This is probably not, what you expect. Make sure, that the complete * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. * </p> * - * @return the message digest used + * @return the message digest used, never null. */ public MessageDigest getMessageDigest() { return messageDigest; diff --git a/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java index 8f7208e08..94ca431a6 100644 --- a/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java +++ b/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; @@ -30,10 +31,13 @@ import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.MessageDigestAlgorithms; +import org.apache.commons.io.IOExceptionList; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.test.CustomIOException; import org.junit.jupiter.api.Test; /** @@ -61,6 +65,34 @@ public class MessageDigestInputStreamTest { // @formatter:on } + @Test + public void testAfterReadConsumer() throws Exception { + final AtomicBoolean boolRef = new AtomicBoolean(); + // @formatter:off + try (InputStream bounded = MessageDigestInputStream.builder() + .setMessageDigest(MessageDigest.getInstance(MessageDigestAlgorithms.SHA_512)) + .setCharSequence("Hi") + .setAfterRead(i -> boolRef.set(true)) + .get()) { + IOUtils.consume(bounded); + } + // @formatter:on + assertTrue(boolRef.get()); + // Throwing + final String message = "test exception message"; + // @formatter:off + try (InputStream bounded = MessageDigestInputStream.builder() + .setMessageDigest(MessageDigest.getInstance(MessageDigestAlgorithms.SHA_512)) + .setCharSequence("Hi") + .setAfterRead(i -> { + throw new CustomIOException(message); + }) + .get()) { + assertTrue(assertThrowsExactly(IOExceptionList.class, () -> IOUtils.consume(bounded)).getMessage().contains(message)); + } + // @formatter:on + } + @SuppressWarnings("resource") @Test public void testAvailableAfterClose() throws Exception { @@ -83,7 +115,9 @@ public class MessageDigestInputStreamTest { @Test public void testNoDefault() throws Exception { - assertThrows(IllegalStateException.class, () -> MessageDigestInputStream.builder().get()); + // No default by design, call MUST set a message digest + // Fail-fast, no need to try to process any input origin + assertThrows(NullPointerException.class, () -> MessageDigestInputStream.builder().get()); assertThrows(NullPointerException.class, () -> MessageDigestInputStream.builder().setInputStream(new ByteArrayInputStream(new byte[] { 1 })).get()); }