001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io.input; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.security.MessageDigest; 022import java.security.NoSuchAlgorithmException; 023import java.security.Provider; 024 025import org.apache.commons.io.build.AbstractStreamBuilder; 026 027/** 028 * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer}, 029 * which calculates a checksum using a MessageDigest, for example an MD5 sum. 030 * <p> 031 * To build an instance, see {@link Builder}. 032 * </p> 033 * <p> 034 * See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java 035 * Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 036 * </p> 037 * <p> 038 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe. So is {@link MessageDigestCalculatingInputStream}. 039 * </p> 040 * <p> 041 * TODO Rename to MessageDigestInputStream in 3.0. 042 * </p> 043 */ 044public class MessageDigestCalculatingInputStream extends ObservableInputStream { 045 046 /** 047 * Builds a new {@link MessageDigestCalculatingInputStream} instance. 048 * <p> 049 * For example: 050 * </p> 051 * <pre>{@code 052 * MessageDigestCalculatingInputStream s = MessageDigestCalculatingInputStream.builder() 053 * .setPath(path) 054 * .setMessageDigest("SHA-512") 055 * .get();} 056 * </pre> 057 * 058 * @since 2.12.0 059 */ 060 public static class Builder extends AbstractStreamBuilder<MessageDigestCalculatingInputStream, Builder> { 061 062 private MessageDigest messageDigest; 063 064 public Builder() { 065 try { 066 this.messageDigest = getDefaultMessageDigest(); 067 } catch (final NoSuchAlgorithmException e) { 068 // Should not happen. 069 throw new IllegalStateException(e); 070 } 071 } 072 073 /** 074 * Constructs a new instance. 075 * <p> 076 * This builder use the aspects InputStream, OpenOption[], and MessageDigest. 077 * </p> 078 * <p> 079 * You must provide an origin that can be converted to an InputStream by this builder, otherwise, this call will throw an 080 * {@link UnsupportedOperationException}. 081 * </p> 082 * 083 * @return a new instance. 084 * @throws UnsupportedOperationException if the origin cannot provide an InputStream. 085 * @see #getInputStream() 086 */ 087 @SuppressWarnings("resource") 088 @Override 089 public MessageDigestCalculatingInputStream get() throws IOException { 090 return new MessageDigestCalculatingInputStream(getInputStream(), messageDigest); 091 } 092 093 /** 094 * Sets the message digest. 095 * 096 * @param messageDigest the message digest. 097 */ 098 public void setMessageDigest(final MessageDigest messageDigest) { 099 this.messageDigest = messageDigest; 100 } 101 102 /** 103 * Sets the name of the name of the message digest algorithm. 104 * 105 * @param algorithm the name of the algorithm. See the MessageDigest section in the 106 * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography 107 * Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 108 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 109 */ 110 public void setMessageDigest(final String algorithm) throws NoSuchAlgorithmException { 111 this.messageDigest = MessageDigest.getInstance(algorithm); 112 } 113 114 } 115 116 /** 117 * Maintains the message digest. 118 */ 119 public static class MessageDigestMaintainingObserver extends Observer { 120 private final MessageDigest messageDigest; 121 122 /** 123 * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. 124 * 125 * @param messageDigest the message digest to use 126 */ 127 public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { 128 this.messageDigest = messageDigest; 129 } 130 131 @Override 132 public void data(final byte[] input, final int offset, final int length) throws IOException { 133 messageDigest.update(input, offset, length); 134 } 135 136 @Override 137 public void data(final int input) throws IOException { 138 messageDigest.update((byte) input); 139 } 140 } 141 142 /** 143 * The default message digest algorithm. 144 * <p> 145 * The MD5 cryptographic algorithm is weak and should not be used. 146 * </p> 147 */ 148 private static final String DEFAULT_ALGORITHM = "MD5"; 149 150 /** 151 * Constructs a new {@link Builder}. 152 * 153 * @return a new {@link Builder}. 154 * @since 2.12.0 155 */ 156 public static Builder builder() { 157 return new Builder(); 158 } 159 160 /** 161 * Gets a MessageDigest object that implements the default digest algorithm. 162 * 163 * @return a Message Digest object that implements the default algorithm. 164 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation. 165 * @see Provider 166 */ 167 static MessageDigest getDefaultMessageDigest() throws NoSuchAlgorithmException { 168 return MessageDigest.getInstance(DEFAULT_ALGORITHM); 169 } 170 171 private final MessageDigest messageDigest; 172 173 /** 174 * Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the "MD5" algorithm. 175 * <p> 176 * The MD5 algorithm is weak and should not be used. 177 * </p> 178 * 179 * @param inputStream the stream to calculate the message digest for 180 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 181 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 182 */ 183 @Deprecated 184 public MessageDigestCalculatingInputStream(final InputStream inputStream) throws NoSuchAlgorithmException { 185 this(inputStream, getDefaultMessageDigest()); 186 } 187 188 /** 189 * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}. 190 * 191 * @param inputStream the stream to calculate the message digest for 192 * @param messageDigest the message digest to use 193 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 194 */ 195 @Deprecated 196 public MessageDigestCalculatingInputStream(final InputStream inputStream, final MessageDigest messageDigest) { 197 super(inputStream, new MessageDigestMaintainingObserver(messageDigest)); 198 this.messageDigest = messageDigest; 199 } 200 201 /** 202 * Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the given algorithm. 203 * 204 * @param inputStream the stream to calculate the message digest for 205 * @param algorithm the name of the algorithm requested. See the MessageDigest section in the 206 * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography 207 * Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 208 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 209 * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}. 210 */ 211 @Deprecated 212 public MessageDigestCalculatingInputStream(final InputStream inputStream, final String algorithm) throws NoSuchAlgorithmException { 213 this(inputStream, MessageDigest.getInstance(algorithm)); 214 } 215 216 /** 217 * Gets the {@link MessageDigest}, which is being used for generating the checksum. 218 * <p> 219 * <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 220 * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. 221 * </p> 222 * 223 * @return the message digest used 224 */ 225 public MessageDigest getMessageDigest() { 226 return messageDigest; 227 } 228}