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