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.Arrays;
024import java.util.Objects;
025
026/**
027 * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer},
028 * which 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 * You must specify a message digest algorithm name or instance.
038 * </p>
039 * <p>
040 * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe, so is {@link MessageDigestInputStream}.
041 * </p>
042 *
043 * @see Builder
044 * @since 2.15.0
045 */
046public final class MessageDigestInputStream extends ObservableInputStream {
047
048    // @formatter:off
049    /**
050     * Builds new {@link MessageDigestInputStream}.
051     *
052     * <p>
053     * For example:
054     * </p>
055     * <pre>{@code
056     * MessageDigestInputStream s = MessageDigestInputStream.builder()
057     *   .setPath(path)
058     *   .setMessageDigest("SHA-512")
059     *   .get();}
060     * </pre>
061     * <p>
062     * You must specify a message digest algorithm name or instance.
063     * </p>
064     * <p>
065     * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
066     * </p>
067     *
068     * @see #get()
069     */
070    // @formatter:on
071    public static class Builder extends AbstractBuilder<Builder> {
072
073        /**
074         * No default by design, call MUST set one.
075         */
076        private MessageDigest messageDigest;
077
078        /**
079         * Constructs a new {@link Builder}.
080         */
081        public Builder() {
082            // empty
083        }
084
085        /**
086         * Builds new {@link MessageDigestInputStream}.
087         * <p>
088         * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception.
089         * </p>
090         * <p>
091         * This builder use the following aspects:
092         * </p>
093         * <ul>
094         * <li>{@link #getPath()}</li>
095         * <li>{@link MessageDigest}</li>
096         * </ul>
097         *
098         * @return a new instance.
099         * @throws NullPointerException if messageDigest is null.
100         * @throws IllegalStateException         if the {@code origin} is {@code null}.
101         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
102         * @throws IOException                   if an I/O error occurs.
103         * @see #getInputStream()
104         */
105        @Override
106        public MessageDigestInputStream get() throws IOException {
107            setObservers(Arrays.asList(new MessageDigestMaintainingObserver(messageDigest)));
108            return new MessageDigestInputStream(this);
109        }
110
111        /**
112         * Sets the message digest.
113         * <p>
114         * <em>The MD5 cryptographic algorithm is weak and should not be used.</em>
115         * </p>
116         *
117         * @param messageDigest the message digest.
118         * @return {@code this} instance.
119         */
120        public Builder setMessageDigest(final MessageDigest messageDigest) {
121            this.messageDigest = messageDigest;
122            return this;
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         * @return {@code this} instance.
135         * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
136         */
137        public Builder setMessageDigest(final String algorithm) throws NoSuchAlgorithmException {
138            this.messageDigest = MessageDigest.getInstance(algorithm);
139            return this;
140        }
141
142    }
143
144    /**
145     * Maintains the message digest.
146     */
147    public static class MessageDigestMaintainingObserver extends Observer {
148
149        private final MessageDigest messageDigest;
150
151        /**
152         * Constructs an MessageDigestMaintainingObserver for the given MessageDigest.
153         *
154         * @param messageDigest the message digest to use
155         * @throws NullPointerException if messageDigest is null.
156         */
157        public MessageDigestMaintainingObserver(final MessageDigest messageDigest) {
158            this.messageDigest = Objects.requireNonNull(messageDigest, "messageDigest");
159        }
160
161        @Override
162        public void data(final byte[] input, final int offset, final int length) throws IOException {
163            messageDigest.update(input, offset, length);
164        }
165
166        @Override
167        public void data(final int input) throws IOException {
168            messageDigest.update((byte) input);
169        }
170    }
171
172    /**
173     * Constructs a new {@link Builder}.
174     *
175     * @return a new {@link Builder}.
176     */
177    public static Builder builder() {
178        return new Builder();
179    }
180
181    /**
182     * A non-null MessageDigest.
183     */
184    private final MessageDigest messageDigest;
185
186    /**
187     * Constructs a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
188     * <p>
189     * The MD5 cryptographic algorithm is weak and should not be used.
190     * </p>
191     *
192     * @param builder A builder use to get the stream to calculate the message digest and the message digest to use
193     * @throws NullPointerException if messageDigest is null.
194     */
195    private MessageDigestInputStream(final Builder builder) throws IOException {
196        super(builder);
197        this.messageDigest = Objects.requireNonNull(builder.messageDigest, "builder.messageDigest");
198    }
199
200    /**
201     * Gets the {@link MessageDigest}, which is being used for generating the checksum, never null.
202     * <p>
203     * <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
204     * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}.
205     * </p>
206     *
207     * @return the message digest used, never null.
208     */
209    public MessageDigest getMessageDigest() {
210        return messageDigest;
211    }
212}