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