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.Arrays; 024import java.util.Objects; 025 026/** 027 * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer}, 028 * which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum. 029 * <p> 030 * To build an instance, use {@link Builder}. 031 * </p> 032 * <p> 033 * See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java 034 * Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 035 * </p> 036 * <p> 037 * You must specify a message digest algorithm name or instance. 038 * </p> 039 * <p> 040 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}. 041 * </p> 042 * 043 * @see Builder 044 * @since 2.15.0 045 */ 046public final class MessageDigestInputStream extends ObservableInputStream { 047 048 // @formatter:off 049 /** 050 * Builds new {@link MessageDigestInputStream}. 051 * 052 * <p> 053 * For example: 054 * </p> 055 * <pre>{@code 056 * MessageDigestInputStream s = MessageDigestInputStream.builder() 057 * .setPath(path) 058 * .setMessageDigest("SHA-512") 059 * .get();} 060 * </pre> 061 * <p> 062 * You must specify a message digest algorithm name or instance. 063 * </p> 064 * <p> 065 * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> 066 * </p> 067 * 068 * @see #get() 069 */ 070 // @formatter:on 071 public static class Builder extends AbstractBuilder<Builder> { 072 073 /** 074 * No default by design, call MUST set one. 075 */ 076 private MessageDigest messageDigest; 077 078 /** 079 * Constructs a new {@link Builder}. 080 */ 081 public Builder() { 082 // empty 083 } 084 085 /** 086 * Builds new {@link MessageDigestInputStream}. 087 * <p> 088 * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception. 089 * </p> 090 * <p> 091 * This builder use the following aspects: 092 * </p> 093 * <ul> 094 * <li>{@link #getPath()}</li> 095 * <li>{@link MessageDigest}</li> 096 * </ul> 097 * 098 * @return a new instance. 099 * @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. 103 * @see #getInputStream() 104 */ 105 @Override 106 public MessageDigestInputStream get() throws IOException { 107 setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest))); 108 return new MessageDigestInputStream(this); 109 } 110 111 /** 112 * Sets the message digest. 113 * <p> 114 * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> 115 * </p> 116 * 117 * @param messageDigest the message digest. 118 * @return {@code this} instance. 119 */ 120 public Builder setMessageDigest(final MessageDigest messageDigest) { 121 this.messageDigest = messageDigest; 122 return this; 123 } 124 125 /** 126 * Sets the name of the name of the message digest algorithm. 127 * <p> 128 * <em>The MD5 cryptographic algorithm is weak and should not be used.</em> 129 * </p> 130 * 131 * @param algorithm the name of the algorithm. See the MessageDigest section in the 132 * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography 133 * Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names. 134 * @return {@code this} instance. 135 * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm. 136 */ 137 public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException { 138 this.messageDigest = MessageDigest.getInstance(algorithm); 139 return this; 140 } 141 142 } 143 144 /** 145 * Maintains the message digest. 146 */ 147 public static class MessageDigestMaintainingObserver extends Observer { 148 149 private final MessageDigest messageDigest; 150 151 /** 152 * Constructs an MessageDigestMaintainingObserver for the given MessageDigest. 153 * 154 * @param messageDigest the message digest to use 155 * @throws NullPointerException if messageDigest is null. 156 */ 157 public MessageDigestMaintainingObserver(final MessageDigest messageDigest) { 158 this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest"); 159 } 160 161 @Override 162 public void data(final byte[] input, final int offset, final int length) throws IOException { 163 messageDigest.update(input, offset, length); 164 } 165 166 @Override 167 public void data(final int input) throws IOException { 168 messageDigest.update((byte) input); 169 } 170 } 171 172 /** 173 * Constructs a new {@link Builder}. 174 * 175 * @return a new {@link Builder}. 176 */ 177 public static Builder builder() { 178 return new Builder(); 179 } 180 181 /** 182 * A non-null MessageDigest. 183 */ 184 private final MessageDigest messageDigest; 185 186 /** 187 * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}. 188 * <p> 189 * The MD5 cryptographic algorithm is weak and should not be used. 190 * </p> 191 * 192 * @param builder A builder use to get the stream to calculate the message digest and the message digest to use 193 * @throws NullPointerException if messageDigest is null. 194 */ 195 private MessageDigestInputStream(final Builder builder) throws IOException { 196 super(builder); 197 this.messageDigest = Objects.requireNonNull(builder.messageDigest, "builder.messageDigest"); 198 } 199 200 /** 201 * Gets the {@link MessageDigest}, which is being used for generating the checksum, never null. 202 * <p> 203 * <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 204 * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}. 205 * </p> 206 * 207 * @return the message digest used, never null. 208 */ 209 public MessageDigest getMessageDigest() { 210 return messageDigest; 211 } 212}