View Javadoc
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 }