ChecksumInputStream.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.io.input;
- import static org.apache.commons.io.IOUtils.EOF;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.Objects;
- import java.util.zip.CheckedInputStream;
- import java.util.zip.Checksum;
- /**
- * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached.
- * <p>
- * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an
- * {@link IOException}.
- * </p>
- * <p>
- * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}.
- * </p>
- * <p>
- * To build an instance, use {@link Builder}.
- * </p>
- *
- * @see Builder
- * @since 2.16.0
- */
- public final class ChecksumInputStream extends CountingInputStream {
- // @formatter:off
- /**
- * Builds a new {@link ChecksumInputStream}.
- *
- * <p>
- * There is no default {@link Checksum}; you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
- * in the future.
- * </p>
- * <h2>Using NIO</h2>
- * <pre>{@code
- * ChecksumInputStream s = ChecksumInputStream.builder()
- * .setPath(Paths.get("MyFile.xml"))
- * .setChecksum(new CRC32())
- * .setExpectedChecksumValue(12345)
- * .get();
- * }</pre>
- * <h2>Using IO</h2>
- * <pre>{@code
- * ChecksumInputStream s = ChecksumInputStream.builder()
- * .setFile(new File("MyFile.xml"))
- * .setChecksum(new CRC32())
- * .setExpectedChecksumValue(12345)
- * .get();
- * }</pre>
- * <h2>Validating only part of an InputStream</h2>
- * <p>
- * The following validates the first 100 bytes of the given input.
- * </p>
- * <pre>{@code
- * ChecksumInputStream s = ChecksumInputStream.builder()
- * .setPath(Paths.get("MyFile.xml"))
- * .setChecksum(new CRC32())
- * .setExpectedChecksumValue(12345)
- * .setCountThreshold(100)
- * .get();
- * }</pre>
- * <p>
- * To validate input <em>after</em> the beginning of a stream, build an instance with an InputStream starting where you want to validate.
- * </p>
- * <pre>{@code
- * InputStream inputStream = ...;
- * inputStream.read(...);
- * inputStream.skip(...);
- * ChecksumInputStream s = ChecksumInputStream.builder()
- * .setInputStream(inputStream)
- * .setChecksum(new CRC32())
- * .setExpectedChecksumValue(12345)
- * .setCountThreshold(100)
- * .get();
- * }</pre>
- *
- * @see #get()
- */
- // @formatter:on
- public static class Builder extends AbstractBuilder<ChecksumInputStream, Builder> {
- /**
- * There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
- * in the future.
- */
- private Checksum checksum;
- /**
- * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input
- * stream validates its value.
- * <p>
- * By default, all input updates the {@link Checksum}.
- * </p>
- */
- private long countThreshold = -1;
- /**
- * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
- */
- private long expectedChecksumValue;
- /**
- * Constructs a new builder of {@link ChecksumInputStream}.
- */
- public Builder() {
- // empty
- }
- /**
- * Builds a new {@link ChecksumInputStream}.
- * <p>
- * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception.
- * </p>
- * <p>
- * This builder uses the following aspects:
- * </p>
- * <ul>
- * <li>{@link #getInputStream()} gets the target aspect.</li>
- * <li>{@link Checksum}</li>
- * <li>expectedChecksumValue</li>
- * <li>countThreshold</li>
- * </ul>
- *
- * @return a new instance.
- * @throws IllegalStateException if the {@code origin} is {@code null}.
- * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
- * @throws IOException if an I/O error occurs converting to an {@link InputStream} using {@link #getInputStream()}.
- * @see #getInputStream()
- * @see #getUnchecked()
- */
- @Override
- public ChecksumInputStream get() throws IOException {
- return new ChecksumInputStream(this);
- }
- /**
- * Sets the Checksum. There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven
- * deficient or insecure in the future.
- *
- * @param checksum the Checksum.
- * @return {@code this} instance.
- */
- public Builder setChecksum(final Checksum checksum) {
- this.checksum = checksum;
- return this;
- }
- /**
- * Sets the count threshold to limit how much input is consumed to update the {@link Checksum} before the input
- * stream validates its value.
- * <p>
- * By default, all input updates the {@link Checksum}.
- * </p>
- *
- * @param countThreshold the count threshold. A negative number means the threshold is unbound.
- * @return {@code this} instance.
- */
- public Builder setCountThreshold(final long countThreshold) {
- this.countThreshold = countThreshold;
- return this;
- }
- /**
- * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
- *
- * @param expectedChecksumValue The expected Checksum value.
- * @return {@code this} instance.
- */
- public Builder setExpectedChecksumValue(final long expectedChecksumValue) {
- this.expectedChecksumValue = expectedChecksumValue;
- return this;
- }
- }
- /**
- * Constructs a new {@link Builder}.
- *
- * @return a new {@link Builder}.
- */
- public static Builder builder() {
- return new Builder();
- }
- /** The expected checksum. */
- private final long expectedChecksumValue;
- /**
- * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input stream
- * validates its value.
- * <p>
- * By default, all input updates the {@link Checksum}.
- * </p>
- */
- private final long countThreshold;
- /**
- * Constructs a new instance.
- *
- * @param builder build parameters.
- */
- @SuppressWarnings("resource")
- private ChecksumInputStream(final Builder builder) throws IOException {
- super(new CheckedInputStream(builder.getInputStream(), Objects.requireNonNull(builder.checksum, "builder.checksum")), builder);
- this.countThreshold = builder.countThreshold;
- this.expectedChecksumValue = builder.expectedChecksumValue;
- }
- @Override
- protected synchronized void afterRead(final int n) throws IOException {
- super.afterRead(n);
- if ((countThreshold > 0 && getByteCount() >= countThreshold || n == EOF)
- && expectedChecksumValue != getChecksum().getValue()) {
- // Validate when past the threshold or at EOF
- throw new IOException("Checksum verification failed.");
- }
- }
- /**
- * Gets the current checksum value.
- *
- * @return the current checksum value.
- */
- private Checksum getChecksum() {
- return ((CheckedInputStream) in).getChecksum();
- }
- /**
- * Gets the byte count remaining to read.
- *
- * @return bytes remaining to read, a negative number means the threshold is unbound.
- */
- public long getRemaining() {
- return countThreshold - getByteCount();
- }
- }