1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.io.input; 18 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.security.MessageDigest; 22 import java.security.NoSuchAlgorithmException; 23 import java.util.Arrays; 24 import java.util.Objects; 25 26 /** 27 * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer}, 28 * which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum. 29 * <p> 30 * To build an instance, use {@link Builder}. 31 * </p> 32 * <p> 33 * See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java 34 * Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 35 * </p> 36 * <p> 37 * You must specify a message digest algorithm name or instance. 38 * </p> 39 * <p> 40 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}. 41 * </p> 42 * 43 * @see Builder 44 * @since 2.15.0 45 */ 46 public final class MessageDigestInputStream extends ObservableInputStream { 47 48 // @formatter:off 49 /** 50 * Builds new {@link MessageDigestInputStream}. 51 * 52 * <p> 53 * For example: 54 * </p> 55 * <pre>{@code 56 * MessageDigestInputStream s = MessageDigestInputStream.builder() 57 * .setPath(path) 58 * .setMessageDigest("SHA-512") 59 * .get();} 60 * </pre> 61 * <p> 62 * You must specify a message digest algorithm name or instance. 63 * </p> 64 * <p> 65 * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> 66 * </p> 67 * 68 * @see #get() 69 */ 70 // @formatter:on 71 public static class Builder extends AbstractBuilder<Builder> { 72 73 /** 74 * No default by design, call MUST set one. 75 */ 76 private MessageDigest messageDigest; 77 78 /** 79 * Constructs a new builder of {@link MessageDigestInputStream}. 80 */ 81 public Builder() { 82 // empty 83 } 84 85 /** 86 * Builds new {@link MessageDigestInputStream}. 87 * <p> 88 * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception. 89 * </p> 90 * <p> 91 * This builder uses the following aspects: 92 * </p> 93 * <ul> 94 * <li>{@link #getInputStream()} gets the target aspect.</li> 95 * <li>{@link MessageDigest}</li> 96 * </ul> 97 * 98 * @return a new instance. 99 * @throws NullPointerException if messageDigest is null. 100 * @throws IllegalStateException if the {@code origin} is {@code null}. 101 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}. 102 * @throws IOException if an I/O error occurs converting to an {@link InputStream} using {@link #getInputStream()}. 103 * @see #getInputStream() 104 * @see #getUnchecked() 105 */ 106 @Override 107 public MessageDigestInputStream get() throws IOException { 108 setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest))); 109 return new MessageDigestInputStream(this); 110 } 111 112 /** 113 * Sets the message digest. 114 * <p> 115 * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> 116 * </p> 117 * 118 * @param messageDigest the message digest. 119 * @return {@code this} instance. 120 */ 121 public Builder setMessageDigest(final MessageDigest messageDigest) { 122 this.messageDigest = messageDigest; 123 return this; 124 } 125 126 /** 127 * Sets the name of the name of the message digest algorithm. 128 * <p> 129 * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> 130 * </p> 131 * 132 * @param algorithm the name of the algorithm. See the MessageDigest section in the 133 * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography 134 * Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 135 * @return {@code this} instance. 136 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 137 */ 138 public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException { 139 this.messageDigest = MessageDigest.getInstance(algorithm); 140 return this; 141 } 142 143 } 144 145 /** 146 * Maintains the message digest. 147 */ 148 public static class MessageDigestMaintainingObserver extends Observer { 149 150 private final MessageDigest messageDigest; 151 152 /** 153 * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. 154 * 155 * @param messageDigest the message digest to use 156 * @throws NullPointerException if messageDigest is null. 157 */ 158 public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { 159 this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest"); 160 } 161 162 @Override 163 public void data(final byte[] input, final int offset, final int length) throws IOException { 164 messageDigest.update(input, offset, length); 165 } 166 167 @Override 168 public void data(final int input) throws IOException { 169 messageDigest.update((byte) input); 170 } 171 } 172 173 /** 174 * Constructs a new {@link Builder}. 175 * 176 * @return a new {@link Builder}. 177 */ 178 public static Builder builder() { 179 return new Builder(); 180 } 181 182 /** 183 * A non-null MessageDigest. 184 */ 185 private final MessageDigest messageDigest; 186 187 /** 188 * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}. 189 * <p> 190 * The MD5 cryptographic algorithm is weak and should not be used. 191 * </p> 192 * 193 * @param builder A builder use to get the stream to calculate the message digest and the message digest to use 194 * @throws NullPointerException if messageDigest is null. 195 */ 196 private MessageDigestInputStream(final Builder builder) throws IOException { 197 super(builder); 198 this.messageDigest = Objects.requireNonNull(builder.messageDigest, "builder.messageDigest"); 199 } 200 201 /** 202 * Gets the {@link MessageDigest}, which is being used for generating the checksum, never null. 203 * <p> 204 * <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 205 * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. 206 * </p> 207 * 208 * @return the message digest used, never null. 209 */ 210 public MessageDigest getMessageDigest() { 211 return messageDigest; 212 } 213 }