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