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.security.Provider;
024import java.util.Arrays;
025import java.util.Objects;
026
027/**
028 * 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 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestCalculatingInputStream}.
038 * </p>
039 *
040 * @see Builder
041 * @deprecated Use {@link MessageDigestInputStream}.
042 */
043@Deprecated
044public class MessageDigestCalculatingInputStream extends ObservableInputStream {
045
046    // @formatter:off
047    /**
048     * Builds a new {@link MessageDigestCalculatingInputStream}.
049     *
050     * <p>
051     * For example:
052     * </p>
053     * <pre>{@code
054     * MessageDigestCalculatingInputStream s = MessageDigestCalculatingInputStream.builder()
055     *   .setPath(path)
056     *   .setMessageDigest("SHA-512")
057     *   .get();}
058     * </pre>
059     * <p>
060     * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
061     * </p>
062     *
063     * @see #get()
064     * @since 2.12.0
065     */
066    // @formatter:on
067    public static class Builder extends AbstractBuilder<Builder> {
068
069        private MessageDigest messageDigest;
070
071        /**
072         * Constructs a new builder of {@link MessageDigestCalculatingInputStream}.
073         * <p>
074         * The default for compatibility is the MD5 cryptographic algorithm which is weak and should not be used.
075         * </p>
076         */
077        public Builder() {
078            try {
079                this.messageDigest = getDefaultMessageDigest();
080            } catch (final NoSuchAlgorithmException e) {
081                // Should not happen.
082                throw new IllegalStateException(e);
083            }
084        }
085
086        /**
087         * Builds a new {@link MessageDigestCalculatingInputStream}.
088         * <p>
089         * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception.
090         * </p>
091         * <p>
092         * This builder uses the following aspects:
093         * </p>
094         * <ul>
095         * <li>{@link #getInputStream()} gets the target aspect.</li>
096         * <li>{@link MessageDigest}</li>
097         * </ul>
098         *
099         * @return a new instance.
100         * @throws NullPointerException if messageDigest is null.
101         * @throws IllegalStateException         if the {@code origin} is {@code null}.
102         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
103         * @throws IOException                   if an I/O error occurs converting to an {@link InputStream} using {@link #getInputStream()}.
104         * @see #getInputStream()
105         * @see #getUnchecked()
106         */
107        @Override
108        public MessageDigestCalculatingInputStream get() throws IOException {
109            setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest)));
110            return new MessageDigestCalculatingInputStream(this);
111        }
112
113        /**
114         * Sets the message digest.
115         * <p>
116         * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
117         * </p>
118         *
119         * @param messageDigest the message digest.
120         */
121        public void setMessageDigest(final MessageDigest messageDigest) {
122            this.messageDigest = messageDigest;
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         * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
135         */
136        public void setMessageDigest(final String algorithm) throws NoSuchAlgorithmException {
137            this.messageDigest = MessageDigest.getInstance(algorithm);
138        }
139
140    }
141
142    /**
143     * Maintains the message digest.
144     */
145    public static class MessageDigestMaintainingObserver extends Observer {
146        private final MessageDigest messageDigest;
147
148        /**
149         * Constructs an MessageDigestMaintainingObserver for the given MessageDigest.
150         *
151         * @param messageDigest the message digest to use
152         * @throws NullPointerException if messageDigest is null.
153         */
154        public MessageDigestMaintainingObserver(final MessageDigest messageDigest) {
155            this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest");
156        }
157
158        @Override
159        public void data(final byte[] input, final int offset, final int length) throws IOException {
160            messageDigest.update(input, offset, length);
161        }
162
163        @Override
164        public void data(final int input) throws IOException {
165            messageDigest.update((byte) input);
166        }
167    }
168
169    /**
170     * The default message digest algorithm {@code "MD5"}.
171     * <p>
172     * The MD5 cryptographic algorithm is weak and should not be used.
173     * </p>
174     */
175    private static final String DEFAULT_ALGORITHM = "MD5";
176
177    /**
178     * Constructs a new {@link Builder}.
179     *
180     * @return a new {@link Builder}.
181     * @since 2.12.0
182     */
183    public static Builder builder() {
184        return new Builder();
185    }
186
187    /**
188     * Gets a MessageDigest object that implements the default digest algorithm {@code "MD5"}.
189     * <p>
190     * The MD5 cryptographic algorithm is weak and should not be used.
191     * </p>
192     *
193     * @return a Message Digest object that implements the default algorithm.
194     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation.
195     * @see Provider
196     */
197    static MessageDigest getDefaultMessageDigest() throws NoSuchAlgorithmException {
198        return MessageDigest.getInstance(DEFAULT_ALGORITHM);
199    }
200
201    private final MessageDigest messageDigest;
202
203    private MessageDigestCalculatingInputStream(final Builder builder) throws IOException {
204        super(builder);
205        this.messageDigest = builder.messageDigest;
206    }
207
208    /**
209     * Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the "MD5" algorithm.
210     * <p>
211     * The MD5 algorithm is weak and should not be used.
212     * </p>
213     *
214     * @param inputStream the stream to calculate the message digest for
215     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
216     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
217     */
218    @Deprecated
219    public MessageDigestCalculatingInputStream(final InputStream inputStream) throws NoSuchAlgorithmException {
220        this(inputStream, getDefaultMessageDigest());
221    }
222
223    /**
224     * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
225     * <p>
226     * The MD5 cryptographic algorithm is weak and should not be used.
227     * </p>
228     *
229     * @param inputStream   the stream to calculate the message digest for
230     * @param messageDigest the message digest to use
231     * @throws NullPointerException if messageDigest is null.
232     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
233     */
234    @Deprecated
235    public MessageDigestCalculatingInputStream(final InputStream inputStream, final MessageDigest messageDigest) {
236        super(inputStream, new MessageDigestMaintainingObserver(messageDigest));
237        this.messageDigest = messageDigest;
238    }
239
240    /**
241     * Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the given algorithm.
242     * <p>
243     * The MD5 cryptographic algorithm is weak and should not be used.
244     * </p>
245     *
246     * @param inputStream the stream to calculate the message digest for
247     * @param algorithm   the name of the algorithm requested. See the MessageDigest section in the
248     *                    <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
249     *                    Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
250     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
251     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
252     */
253    @Deprecated
254    public MessageDigestCalculatingInputStream(final InputStream inputStream, final String algorithm) throws NoSuchAlgorithmException {
255        this(inputStream, MessageDigest.getInstance(algorithm));
256    }
257
258    /**
259     * Gets the {@link MessageDigest}, which is being used for generating the checksum.
260     * <p>
261     * <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
262     * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}.
263     * </p>
264     *
265     * @return the message digest used
266     */
267    public MessageDigest getMessageDigest() {
268        return messageDigest;
269    }
270}