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.Arrays;
24  import java.util.Objects;
25  
26  /**
27   * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer},
28   * which calculates a checksum using a {@link MessageDigest}, for example, a SHA-512 sum.
29   * <p>
30   * To build an instance, use {@link Builder}.
31   * </p>
32   * <p>
33   * See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java
34   * Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
35   * </p>
36   * <p>
37   * You must specify a message digest algorithm name or instance.
38   * </p>
39   * <p>
40   * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}.
41   * </p>
42   *
43   * @see Builder
44   * @since 2.15.0
45   */
46  public final class MessageDigestInputStream extends ObservableInputStream {
47  
48      // @formatter:off
49      /**
50       * Builds new {@link MessageDigestInputStream}.
51       *
52       * <p>
53       * For example:
54       * </p>
55       * <pre>{@code
56       * MessageDigestInputStream s = MessageDigestInputStream.builder()
57       *   .setPath(path)
58       *   .setMessageDigest("SHA-512")
59       *   .get();}
60       * </pre>
61       * <p>
62       * You must specify a message digest algorithm name or instance.
63       * </p>
64       * <p>
65       * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
66       * </p>
67       *
68       * @see #get()
69       */
70      // @formatter:on
71      public static class Builder extends AbstractBuilder<Builder> {
72  
73          /**
74           * No default by design, call MUST set one.
75           */
76          private MessageDigest messageDigest;
77  
78          /**
79           * Constructs a new builder of {@link MessageDigestInputStream}.
80           */
81          public Builder() {
82              // empty
83          }
84  
85          /**
86           * Builds new {@link MessageDigestInputStream}.
87           * <p>
88           * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception.
89           * </p>
90           * <p>
91           * This builder uses the following aspects:
92           * </p>
93           * <ul>
94           * <li>{@link #getInputStream()} gets the target aspect.</li>
95           * <li>{@link MessageDigest}</li>
96           * </ul>
97           *
98           * @return a new instance.
99           * @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 converting to an {@link InputStream} using {@link #getInputStream()}.
103          * @see #getInputStream()
104          * @see #getUnchecked()
105          */
106         @Override
107         public MessageDigestInputStream get() throws IOException {
108             setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest)));
109             return new MessageDigestInputStream(this);
110         }
111 
112         /**
113          * Sets the message digest.
114          * <p>
115          * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
116          * </p>
117          *
118          * @param messageDigest the message digest.
119          * @return {@code this} instance.
120          */
121         public Builder setMessageDigest(final MessageDigest messageDigest) {
122             this.messageDigest = messageDigest;
123             return this;
124         }
125 
126         /**
127          * Sets the name of the name of the message digest algorithm.
128          * <p>
129          * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
130          * </p>
131          *
132          * @param algorithm the name of the algorithm. See the MessageDigest section in the
133          *                  <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
134          *                  Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
135          * @return {@code this} instance.
136          * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
137          */
138         public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException {
139             this.messageDigest = MessageDigest.getInstance(algorithm);
140             return this;
141         }
142 
143     }
144 
145     /**
146      * Maintains the message digest.
147      */
148     public static class MessageDigestMaintainingObserver extends Observer {
149 
150         private final MessageDigest messageDigest;
151 
152         /**
153          * Constructs an MessageDigestMaintainingObserver for the given MessageDigest.
154          *
155          * @param messageDigest the message digest to use
156          * @throws NullPointerException if messageDigest is null.
157          */
158         public MessageDigestMaintainingObserver(final MessageDigest messageDigest) {
159             this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest");
160         }
161 
162         @Override
163         public void data(final byte[] input, final int offset, final int length) throws IOException {
164             messageDigest.update(input, offset, length);
165         }
166 
167         @Override
168         public void data(final int input) throws IOException {
169             messageDigest.update((byte) input);
170         }
171     }
172 
173     /**
174      * Constructs a new {@link Builder}.
175      *
176      * @return a new {@link Builder}.
177      */
178     public static Builder builder() {
179         return new Builder();
180     }
181 
182     /**
183      * A non-null MessageDigest.
184      */
185     private final MessageDigest messageDigest;
186 
187     /**
188      * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
189      * <p>
190      * The MD5 cryptographic algorithm is weak and should not be used.
191      * </p>
192      *
193      * @param builder A builder use to get the stream to calculate the message digest and the message digest to use
194      * @throws NullPointerException if messageDigest is null.
195      */
196     private MessageDigestInputStream(final Builder builder) throws IOException {
197         super(builder);
198         this.messageDigest = Objects.requireNonNull(builder.messageDigest, "builder.messageDigest");
199     }
200 
201     /**
202      * Gets the {@link MessageDigest}, which is being used for generating the checksum, never null.
203      * <p>
204      * <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
205      * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}.
206      * </p>
207      *
208      * @return the message digest used, never null.
209      */
210     public MessageDigest getMessageDigest() {
211         return messageDigest;
212     }
213 }