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 * https://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 }