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