View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.io.input;
18  
19  import static org.apache.commons.io.IOUtils.EOF;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.util.Objects;
24  import java.util.zip.CheckedInputStream;
25  import java.util.zip.Checksum;
26  
27  /**
28   * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached.
29   * <p>
30   * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an
31   * {@link IOException}.
32   * </p>
33   * <p>
34   * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}.
35   * </p>
36   * <p>
37   * To build an instance, use {@link Builder}.
38   * </p>
39   *
40   * @see Builder
41   * @since 2.16.0
42   */
43  public final class ChecksumInputStream extends CountingInputStream {
44  
45      // @formatter:off
46      /**
47       * Builds a new {@link ChecksumInputStream}.
48       *
49       * <p>
50       * There is no default {@link Checksum}; you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
51       * in the future.
52       * </p>
53       * <h2>Using NIO</h2>
54       * <pre>{@code
55       * ChecksumInputStream s = ChecksumInputStream.builder()
56       *   .setPath(Paths.get("MyFile.xml"))
57       *   .setChecksum(new CRC32())
58       *   .setExpectedChecksumValue(12345)
59       *   .get();
60       * }</pre>
61       * <h2>Using IO</h2>
62       * <pre>{@code
63       * ChecksumInputStream s = ChecksumInputStream.builder()
64       *   .setFile(new File("MyFile.xml"))
65       *   .setChecksum(new CRC32())
66       *   .setExpectedChecksumValue(12345)
67       *   .get();
68       * }</pre>
69       * <h2>Validating only part of an InputStream</h2>
70       * <p>
71       * The following validates the first 100 bytes of the given input.
72       * </p>
73       * <pre>{@code
74       * ChecksumInputStream s = ChecksumInputStream.builder()
75       *   .setPath(Paths.get("MyFile.xml"))
76       *   .setChecksum(new CRC32())
77       *   .setExpectedChecksumValue(12345)
78       *   .setCountThreshold(100)
79       *   .get();
80       * }</pre>
81       * <p>
82       * To validate input <em>after</em> the beginning of a stream, build an instance with an InputStream starting where you want to validate.
83       * </p>
84       * <pre>{@code
85       * InputStream inputStream = ...;
86       * inputStream.read(...);
87       * inputStream.skip(...);
88       * ChecksumInputStream s = ChecksumInputStream.builder()
89       *   .setInputStream(inputStream)
90       *   .setChecksum(new CRC32())
91       *   .setExpectedChecksumValue(12345)
92       *   .setCountThreshold(100)
93       *   .get();
94       * }</pre>
95       *
96       * @see #get()
97       */
98      // @formatter:on
99      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 }