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, use {@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 * @see Builder 045 * @since 2.15.0 046 */ 047public final class MessageDigestInputStream extends ObservableInputStream { 048 049 // @formatter:off 050 /** 051 * Builds new {@link MessageDigestInputStream}. 052 * 053 * <p> 054 * For example: 055 * </p> 056 * <pre>{@code 057 * MessageDigestInputStream s = MessageDigestInputStream.builder() 058 * .setPath(path) 059 * .setMessageDigest("SHA-512") 060 * .get();} 061 * </pre> 062 * <p> 063 * You must specify a message digest algorithm name or instance. 064 * </p> 065 * 066 * @see #get() 067 */ 068 // @formatter:on 069 public static class Builder extends AbstractStreamBuilder<MessageDigestInputStream, Builder> { 070 071 private MessageDigest messageDigest; 072 073 /** 074 * Constructs a new {@link Builder}. 075 */ 076 public Builder() { 077 // empty 078 } 079 080 /** 081 * Builds new {@link MessageDigestInputStream}. 082 * <p> 083 * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception. 084 * </p> 085 * <p> 086 * This builder use the following aspects: 087 * </p> 088 * <ul> 089 * <li>{@link #getPath()}</li> 090 * <li>{@link MessageDigest}</li> 091 * </ul> 092 * 093 * @return a new instance. 094 * @throws NullPointerException if messageDigest is null. 095 * @throws IllegalStateException if the {@code origin} is {@code null}. 096 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}. 097 * @throws IOException if an I/O error occurs. 098 * @see #getInputStream() 099 */ 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 {@code this} instance. 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 {@code this} instance. 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}