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 {@link Builder}.
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 input that supports {@link #getInputStream()}, otherwise, this method throws an exception.
090         * </p>
091         * <p>
092         * This builder use the following aspects:
093         * </p>
094         * <ul>
095         * <li>{@link #getPath()}</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.
104         * @see #getInputStream()
105         */
106        @Override
107        public MessageDigestCalculatingInputStream get() throws IOException {
108            setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest)));
109            return new MessageDigestCalculatingInputStream(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         */
120        public void setMessageDigest(final MessageDigest messageDigest) {
121            this.messageDigest = messageDigest;
122        }
123
124        /**
125         * Sets the name of the name of the message digest algorithm.
126         * <p>
127         * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
128         * </p>
129         *
130         * @param algorithm the name of the algorithm. See the MessageDigest section in the
131         *                  <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
132         *                  Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
133         * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
134         */
135        public void setMessageDigest(final String algorithm) throws NoSuchAlgorithmException {
136            this.messageDigest = MessageDigest.getInstance(algorithm);
137        }
138
139    }
140
141    /**
142     * Maintains the message digest.
143     */
144    public static class MessageDigestMaintainingObserver extends Observer {
145        private final MessageDigest messageDigest;
146
147        /**
148         * Constructs an MessageDigestMaintainingObserver for the given MessageDigest.
149         *
150         * @param messageDigest the message digest to use
151         * @throws NullPointerException if messageDigest is null.
152         */
153        public MessageDigestMaintainingObserver(final MessageDigest messageDigest) {
154            this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest");
155        }
156
157        @Override
158        public void data(final byte[] input, final int offset, final int length) throws IOException {
159            messageDigest.update(input, offset, length);
160        }
161
162        @Override
163        public void data(final int input) throws IOException {
164            messageDigest.update((byte) input);
165        }
166    }
167
168    /**
169     * The default message digest algorithm {@code "MD5"}.
170     * <p>
171     * The MD5 cryptographic algorithm is weak and should not be used.
172     * </p>
173     */
174    private static final String DEFAULT_ALGORITHM = "MD5";
175
176    /**
177     * Constructs a new {@link Builder}.
178     *
179     * @return a new {@link Builder}.
180     * @since 2.12.0
181     */
182    public static Builder builder() {
183        return new Builder();
184    }
185
186    /**
187     * Gets a MessageDigest object that implements the default digest algorithm {@code "MD5"}.
188     * <p>
189     * The MD5 cryptographic algorithm is weak and should not be used.
190     * </p>
191     *
192     * @return a Message Digest object that implements the default algorithm.
193     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation.
194     * @see Provider
195     */
196    static MessageDigest getDefaultMessageDigest() throws NoSuchAlgorithmException {
197        return MessageDigest.getInstance(DEFAULT_ALGORITHM);
198    }
199
200    private final MessageDigest messageDigest;
201
202    private MessageDigestCalculatingInputStream(final Builder builder) throws IOException {
203        super(builder);
204        this.messageDigest = builder.messageDigest;
205    }
206
207    /**
208     * Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the "MD5" algorithm.
209     * <p>
210     * The MD5 algorithm is weak and should not be used.
211     * </p>
212     *
213     * @param inputStream the stream to calculate the message digest for
214     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
215     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
216     */
217    @Deprecated
218    public MessageDigestCalculatingInputStream(final InputStream inputStream) throws NoSuchAlgorithmException {
219        this(inputStream, getDefaultMessageDigest());
220    }
221
222    /**
223     * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
224     * <p>
225     * The MD5 cryptographic algorithm is weak and should not be used.
226     * </p>
227     *
228     * @param inputStream   the stream to calculate the message digest for
229     * @param messageDigest the message digest to use
230     * @throws NullPointerException if messageDigest is null.
231     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
232     */
233    @Deprecated
234    public MessageDigestCalculatingInputStream(final InputStream inputStream, final MessageDigest messageDigest) {
235        super(inputStream, new MessageDigestMaintainingObserver(messageDigest));
236        this.messageDigest = messageDigest;
237    }
238
239    /**
240     * Constructs a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the given algorithm.
241     * <p>
242     * The MD5 cryptographic algorithm is weak and should not be used.
243     * </p>
244     *
245     * @param inputStream the stream to calculate the message digest for
246     * @param algorithm   the name of the algorithm requested. See the MessageDigest section in the
247     *                    <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
248     *                    Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
249     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
250     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
251     */
252    @Deprecated
253    public MessageDigestCalculatingInputStream(final InputStream inputStream, final String algorithm) throws NoSuchAlgorithmException {
254        this(inputStream, MessageDigest.getInstance(algorithm));
255    }
256
257    /**
258     * Gets the {@link MessageDigest}, which is being used for generating the checksum.
259     * <p>
260     * <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
261     * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}.
262     * </p>
263     *
264     * @return the message digest used
265     */
266    public MessageDigest getMessageDigest() {
267        return messageDigest;
268    }
269}