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 static org.apache.commons.io.IOUtils.EOF;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.Objects;
024import java.util.zip.CheckedInputStream;
025import java.util.zip.Checksum;
026
027/**
028 * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached.
029 * <p>
030 * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an
031 * {@link IOException}.
032 * </p>
033 * <p>
034 * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}.
035 * </p>
036 * <p>
037 * To build an instance, use {@link Builder}.
038 * </p>
039 *
040 * @see Builder
041 * @since 2.16.0
042 */
043public final class ChecksumInputStream extends CountingInputStream {
044
045    // @formatter:off
046    /**
047     * Builds a new {@link ChecksumInputStream}.
048     *
049     * <p>
050     * There is no default {@link Checksum}; you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
051     * in the future.
052     * </p>
053     * <h2>Using NIO</h2>
054     * <pre>{@code
055     * ChecksumInputStream s = ChecksumInputStream.builder()
056     *   .setPath(Paths.get("MyFile.xml"))
057     *   .setChecksum(new CRC32())
058     *   .setExpectedChecksumValue(12345)
059     *   .get();
060     * }</pre>
061     * <h2>Using IO</h2>
062     * <pre>{@code
063     * ChecksumInputStream s = ChecksumInputStream.builder()
064     *   .setFile(new File("MyFile.xml"))
065     *   .setChecksum(new CRC32())
066     *   .setExpectedChecksumValue(12345)
067     *   .get();
068     * }</pre>
069     * <h2>Validating only part of an InputStream</h2>
070     * <p>
071     * The following validates the first 100 bytes of the given input.
072     * </p>
073     * <pre>{@code
074     * ChecksumInputStream s = ChecksumInputStream.builder()
075     *   .setPath(Paths.get("MyFile.xml"))
076     *   .setChecksum(new CRC32())
077     *   .setExpectedChecksumValue(12345)
078     *   .setCountThreshold(100)
079     *   .get();
080     * }</pre>
081     * <p>
082     * To validate input <em>after</em> the beginning of a stream, build an instance with an InputStream starting where you want to validate.
083     * </p>
084     * <pre>{@code
085     * InputStream inputStream = ...;
086     * inputStream.read(...);
087     * inputStream.skip(...);
088     * ChecksumInputStream s = ChecksumInputStream.builder()
089     *   .setInputStream(inputStream)
090     *   .setChecksum(new CRC32())
091     *   .setExpectedChecksumValue(12345)
092     *   .setCountThreshold(100)
093     *   .get();
094     * }</pre>
095     *
096     * @see #get()
097     */
098    // @formatter:on
099    public static class Builder extends AbstractBuilder<ChecksumInputStream, Builder> {
100
101        /**
102         * There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
103         * in the future.
104         */
105        private Checksum checksum;
106
107        /**
108         * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input
109         * stream validates its value.
110         * <p>
111         * By default, all input updates the {@link Checksum}.
112         * </p>
113         */
114        private long countThreshold = -1;
115
116        /**
117         * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
118         */
119        private long expectedChecksumValue;
120
121        /**
122         * Constructs a new builder of {@link ChecksumInputStream}.
123         */
124        public Builder() {
125            // empty
126        }
127
128        /**
129         * Builds a new {@link ChecksumInputStream}.
130         * <p>
131         * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception.
132         * </p>
133         * <p>
134         * This builder uses the following aspects:
135         * </p>
136         * <ul>
137         * <li>{@link #getInputStream()} gets the target aspect.</li>
138         * <li>{@link Checksum}</li>
139         * <li>expectedChecksumValue</li>
140         * <li>countThreshold</li>
141         * </ul>
142         *
143         * @return a new instance.
144         * @throws IllegalStateException         if the {@code origin} is {@code null}.
145         * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
146         * @throws IOException                   if an I/O error occurs converting to an {@link InputStream} using {@link #getInputStream()}.
147         * @see #getInputStream()
148         * @see #getUnchecked()
149         */
150        @Override
151        public ChecksumInputStream get() throws IOException {
152            return new ChecksumInputStream(this);
153        }
154
155        /**
156         * Sets the Checksum. There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven
157         * deficient or insecure in the future.
158         *
159         * @param checksum the Checksum.
160         * @return {@code this} instance.
161         */
162        public Builder setChecksum(final Checksum checksum) {
163            this.checksum = checksum;
164            return this;
165        }
166
167        /**
168         * Sets the count threshold to limit how much input is consumed to update the {@link Checksum} before the input
169         * stream validates its value.
170         * <p>
171         * By default, all input updates the {@link Checksum}.
172         * </p>
173         *
174         * @param countThreshold the count threshold. A negative number means the threshold is unbound.
175         * @return {@code this} instance.
176         */
177        public Builder setCountThreshold(final long countThreshold) {
178            this.countThreshold = countThreshold;
179            return this;
180        }
181
182        /**
183         * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
184         *
185         * @param expectedChecksumValue The expected Checksum value.
186         * @return {@code this} instance.
187         */
188        public Builder setExpectedChecksumValue(final long expectedChecksumValue) {
189            this.expectedChecksumValue = expectedChecksumValue;
190            return this;
191        }
192
193    }
194
195    /**
196     * Constructs a new {@link Builder}.
197     *
198     * @return a new {@link Builder}.
199     */
200    public static Builder builder() {
201        return new Builder();
202    }
203
204    /** The expected checksum. */
205    private final long expectedChecksumValue;
206
207    /**
208     * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input stream
209     * validates its value.
210     * <p>
211     * By default, all input updates the {@link Checksum}.
212     * </p>
213     */
214    private final long countThreshold;
215
216    /**
217     * Constructs a new instance.
218     *
219     * @param builder build parameters.
220     */
221    @SuppressWarnings("resource")
222    private ChecksumInputStream(final Builder builder) throws IOException {
223        super(new CheckedInputStream(builder.getInputStream(), Objects.requireNonNull(builder.checksum, "builder.checksum")), builder);
224        this.countThreshold = builder.countThreshold;
225        this.expectedChecksumValue = builder.expectedChecksumValue;
226    }
227
228    @Override
229    protected synchronized void afterRead(final int n) throws IOException {
230        super.afterRead(n);
231        if ((countThreshold > 0 && getByteCount() >= countThreshold || n == EOF)
232                && expectedChecksumValue != getChecksum().getValue()) {
233            // Validate when past the threshold or at EOF
234            throw new IOException("Checksum verification failed.");
235        }
236    }
237
238    /**
239     * Gets the current checksum value.
240     *
241     * @return the current checksum value.
242     */
243    private Checksum getChecksum() {
244        return ((CheckedInputStream) in).getChecksum();
245    }
246
247    /**
248     * Gets the byte count remaining to read.
249     *
250     * @return bytes remaining to read, a negative number means the threshold is unbound.
251     */
252    public long getRemaining() {
253        return countThreshold - getByteCount();
254    }
255
256}