    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}