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.util.Objects; 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 {@link MessageDigest}, for example, a SHA-512 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 * You must specify a message digest algorithm name or instance. 039 * </p> 040 * <p> 041 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}. 042 * </p> 043 * 044 * @since 2.15.0 045 */ 046public final class MessageDigestInputStream extends ObservableInputStream { 047 048 /** 049 * Builds new {@link MessageDigestInputStream} instances. 050 * <p> 051 * For example: 052 * </p> 053 * <pre>{@code 054 * MessageDigestInputStream s = MessageDigestInputStream.builder() 055 * .setPath(path) 056 * .setMessageDigest("SHA-512") 057 * .get();} 058 * </pre> 059 * <p> 060 * You must specify a message digest algorithm name or instance. 061 * </p> 062 */ 063 public static class Builder extends AbstractStreamBuilder<MessageDigestInputStream, Builder> { 064 065 private MessageDigest messageDigest; 066 067 /** 068 * Constructs a new Builder. 069 */ 070 public Builder() { 071 // empty 072 } 073 074 /** 075 * Constructs a new instance. 076 * <p> 077 * This builder use the aspects InputStream, OpenOption[], and MessageDigest. 078 * </p> 079 * <p> 080 * You must provide an origin that can be converted to an InputStream by this builder, otherwise, this call will throw an 081 * {@link UnsupportedOperationException}. 082 * </p> 083 * 084 * @return a new instance. 085 * @throws UnsupportedOperationException if the origin cannot provide an InputStream. 086 * @see #getInputStream() 087 */ 088 @SuppressWarnings("resource") 089 @Override 090 public MessageDigestInputStream get() throws IOException { 091 return new MessageDigestInputStream(getInputStream(), messageDigest); 092 } 093 094 /** 095 * Sets the message digest. 096 * <p> 097 * The MD5 cryptographic algorithm is weak and should not be used. 098 * </p> 099 * 100 * @param messageDigest the message digest. 101 * @return this 102 */ 103 public Builder setMessageDigest(final MessageDigest messageDigest) { 104 this.messageDigest = messageDigest; 105 return this; 106 } 107 108 /** 109 * Sets the name of the name of the message digest algorithm. 110 * <p> 111 * The MD5 cryptographic algorithm is weak and should not be used. 112 * </p> 113 * 114 * @param algorithm the name of the algorithm. See the MessageDigest section in the 115 * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography 116 * Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 117 * @return this 118 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 119 */ 120 public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException { 121 this.messageDigest = MessageDigest.getInstance(algorithm); 122 return this; 123 } 124 125 } 126 127 /** 128 * Maintains the message digest. 129 */ 130 public static class MessageDigestMaintainingObserver extends Observer { 131 132 private final MessageDigest messageDigest; 133 134 /** 135 * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. 136 * 137 * @param messageDigest the message digest to use 138 * @throws NullPointerException if messageDigest is null. 139 */ 140 public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { 141 this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest"); 142 } 143 144 @Override 145 public void data(final byte[] input, final int offset, final int length) throws IOException { 146 messageDigest.update(input, offset, length); 147 } 148 149 @Override 150 public void data(final int input) throws IOException { 151 messageDigest.update((byte) input); 152 } 153 } 154 155 /** 156 * Constructs a new {@link Builder}. 157 * 158 * @return a new {@link Builder}. 159 */ 160 public static Builder builder() { 161 return new Builder(); 162 } 163 164 private final MessageDigest messageDigest; 165 166 /** 167 * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}. 168 * <p> 169 * The MD5 cryptographic algorithm is weak and should not be used. 170 * </p> 171 * 172 * @param inputStream the stream to calculate the message digest for 173 * @param messageDigest the message digest to use 174 * @throws NullPointerException if messageDigest is null. 175 */ 176 private MessageDigestInputStream(final InputStream inputStream, final MessageDigest messageDigest) { 177 super(inputStream, new MessageDigestMaintainingObserver(messageDigest)); 178 this.messageDigest = messageDigest; 179 } 180 181 /** 182 * Gets the {@link MessageDigest}, which is being used for generating the checksum. 183 * <p> 184 * <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 185 * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. 186 * </p> 187 * 188 * @return the message digest used 189 */ 190 public MessageDigest getMessageDigest() { 191 return messageDigest; 192 } 193}