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.Objects; 24 25 import org.apache.commons.io.build.AbstractStreamBuilder; 26 27 /** 28 * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer}, 29 * which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum. 30 * <p> 31 * To build an instance, use {@link Builder}. 32 * </p> 33 * <p> 34 * See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java 35 * Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 36 * </p> 37 * <p> 38 * You must specify a message digest algorithm name or instance. 39 * </p> 40 * <p> 41 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}. 42 * </p> 43 * 44 * @see Builder 45 * @since 2.15.0 46 */ 47 public final class MessageDigestInputStream extends ObservableInputStream { 48 49 // @formatter:off 50 /** 51 * Builds new {@link MessageDigestInputStream}. 52 * 53 * <p> 54 * For example: 55 * </p> 56 * <pre>{@code 57 * MessageDigestInputStream s = MessageDigestInputStream.builder() 58 * .setPath(path) 59 * .setMessageDigest("SHA-512") 60 * .get();} 61 * </pre> 62 * <p> 63 * You must specify a message digest algorithm name or instance. 64 * </p> 65 * 66 * @see #get() 67 */ 68 // @formatter:on 69 public static class Builder extends AbstractStreamBuilder<MessageDigestInputStream, Builder> { 70 71 private MessageDigest messageDigest; 72 73 /** 74 * Constructs a new {@link Builder}. 75 */ 76 public Builder() { 77 // empty 78 } 79 80 /** 81 * Builds new {@link MessageDigestInputStream}. 82 * <p> 83 * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception. 84 * </p> 85 * <p> 86 * This builder use the following aspects: 87 * </p> 88 * <ul> 89 * <li>{@link #getPath()}</li> 90 * <li>{@link MessageDigest}</li> 91 * </ul> 92 * 93 * @return a new instance. 94 * @throws NullPointerException if messageDigest is null. 95 * @throws IllegalStateException if the {@code origin} is {@code null}. 96 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}. 97 * @throws IOException if an I/O error occurs. 98 * @see #getInputStream() 99 */ 100 @SuppressWarnings("resource") 101 @Override 102 public MessageDigestInputStream get() throws IOException { 103 return new MessageDigestInputStream(getInputStream(), messageDigest); 104 } 105 106 /** 107 * Sets the message digest. 108 * <p> 109 * The MD5 cryptographic algorithm is weak and should not be used. 110 * </p> 111 * 112 * @param messageDigest the message digest. 113 * @return this 114 */ 115 public Builder setMessageDigest(final MessageDigest messageDigest) { 116 this.messageDigest = messageDigest; 117 return this; 118 } 119 120 /** 121 * Sets the name of the name of the message digest algorithm. 122 * <p> 123 * The MD5 cryptographic algorithm is weak and should not be used. 124 * </p> 125 * 126 * @param algorithm the name of the algorithm. See the MessageDigest section in the 127 * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography 128 * Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 129 * @return this 130 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 131 */ 132 public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException { 133 this.messageDigest = MessageDigest.getInstance(algorithm); 134 return this; 135 } 136 137 } 138 139 /** 140 * Maintains the message digest. 141 */ 142 public static class MessageDigestMaintainingObserver extends Observer { 143 144 private final MessageDigest messageDigest; 145 146 /** 147 * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. 148 * 149 * @param messageDigest the message digest to use 150 * @throws NullPointerException if messageDigest is null. 151 */ 152 public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { 153 this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest"); 154 } 155 156 @Override 157 public void data(final byte[] input, final int offset, final int length) throws IOException { 158 messageDigest.update(input, offset, length); 159 } 160 161 @Override 162 public void data(final int input) throws IOException { 163 messageDigest.update((byte) input); 164 } 165 } 166 167 /** 168 * Constructs a new {@link Builder}. 169 * 170 * @return a new {@link Builder}. 171 */ 172 public static Builder builder() { 173 return new Builder(); 174 } 175 176 private final MessageDigest messageDigest; 177 178 /** 179 * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}. 180 * <p> 181 * The MD5 cryptographic algorithm is weak and should not be used. 182 * </p> 183 * 184 * @param inputStream the stream to calculate the message digest for 185 * @param messageDigest the message digest to use 186 * @throws NullPointerException if messageDigest is null. 187 */ 188 private MessageDigestInputStream(final InputStream inputStream, final MessageDigest messageDigest) { 189 super(inputStream, new MessageDigestMaintainingObserver(messageDigest)); 190 this.messageDigest = messageDigest; 191 } 192 193 /** 194 * Gets the {@link MessageDigest}, which is being used for generating the checksum. 195 * <p> 196 * <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 197 * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. 198 * </p> 199 * 200 * @return the message digest used 201 */ 202 public MessageDigest getMessageDigest() { 203 return messageDigest; 204 } 205 }