ChecksumInputStream.java

  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. import static org.apache.commons.io.IOUtils.EOF;

  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.util.Objects;
  22. import java.util.zip.CheckedInputStream;
  23. import java.util.zip.Checksum;

  24. /**
  25.  * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached.
  26.  * <p>
  27.  * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an
  28.  * {@link IOException}.
  29.  * </p>
  30.  * <p>
  31.  * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}.
  32.  * </p>
  33.  * <p>
  34.  * To build an instance, use {@link Builder}.
  35.  * </p>
  36.  *
  37.  * @see Builder
  38.  * @since 2.16.0
  39.  */
  40. public final class ChecksumInputStream extends CountingInputStream {

  41.     // @formatter:off
  42.     /**
  43.      * Builds a new {@link ChecksumInputStream}.
  44.      *
  45.      * <p>
  46.      * There is no default {@link Checksum}; you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
  47.      * in the future.
  48.      * </p>
  49.      * <h2>Using NIO</h2>
  50.      * <pre>{@code
  51.      * ChecksumInputStream s = ChecksumInputStream.builder()
  52.      *   .setPath(Paths.get("MyFile.xml"))
  53.      *   .setChecksum(new CRC32())
  54.      *   .setExpectedChecksumValue(12345)
  55.      *   .get();
  56.      * }</pre>
  57.      * <h2>Using IO</h2>
  58.      * <pre>{@code
  59.      * ChecksumInputStream s = ChecksumInputStream.builder()
  60.      *   .setFile(new File("MyFile.xml"))
  61.      *   .setChecksum(new CRC32())
  62.      *   .setExpectedChecksumValue(12345)
  63.      *   .get();
  64.      * }</pre>
  65.      * <h2>Validating only part of an InputStream</h2>
  66.      * <p>
  67.      * The following validates the first 100 bytes of the given input.
  68.      * </p>
  69.      * <pre>{@code
  70.      * ChecksumInputStream s = ChecksumInputStream.builder()
  71.      *   .setPath(Paths.get("MyFile.xml"))
  72.      *   .setChecksum(new CRC32())
  73.      *   .setExpectedChecksumValue(12345)
  74.      *   .setCountThreshold(100)
  75.      *   .get();
  76.      * }</pre>
  77.      * <p>
  78.      * To validate input <em>after</em> the beginning of a stream, build an instance with an InputStream starting where you want to validate.
  79.      * </p>
  80.      * <pre>{@code
  81.      * InputStream inputStream = ...;
  82.      * inputStream.read(...);
  83.      * inputStream.skip(...);
  84.      * ChecksumInputStream s = ChecksumInputStream.builder()
  85.      *   .setInputStream(inputStream)
  86.      *   .setChecksum(new CRC32())
  87.      *   .setExpectedChecksumValue(12345)
  88.      *   .setCountThreshold(100)
  89.      *   .get();
  90.      * }</pre>
  91.      *
  92.      * @see #get()
  93.      */
  94.     // @formatter:on
  95.     public static class Builder extends AbstractBuilder<ChecksumInputStream, Builder> {

  96.         /**
  97.          * There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure
  98.          * in the future.
  99.          */
  100.         private Checksum checksum;

  101.         /**
  102.          * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input
  103.          * stream validates its value.
  104.          * <p>
  105.          * By default, all input updates the {@link Checksum}.
  106.          * </p>
  107.          */
  108.         private long countThreshold = -1;

  109.         /**
  110.          * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
  111.          */
  112.         private long expectedChecksumValue;

  113.         /**
  114.          * Constructs a new builder of {@link ChecksumInputStream}.
  115.          */
  116.         public Builder() {
  117.             // empty
  118.         }

  119.         /**
  120.          * Builds a new {@link ChecksumInputStream}.
  121.          * <p>
  122.          * You must set an aspect that supports {@link #getInputStream()}, otherwise, this method throws an exception.
  123.          * </p>
  124.          * <p>
  125.          * This builder uses the following aspects:
  126.          * </p>
  127.          * <ul>
  128.          * <li>{@link #getInputStream()} gets the target aspect.</li>
  129.          * <li>{@link Checksum}</li>
  130.          * <li>expectedChecksumValue</li>
  131.          * <li>countThreshold</li>
  132.          * </ul>
  133.          *
  134.          * @return a new instance.
  135.          * @throws IllegalStateException         if the {@code origin} is {@code null}.
  136.          * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}.
  137.          * @throws IOException                   if an I/O error occurs converting to an {@link InputStream} using {@link #getInputStream()}.
  138.          * @see #getInputStream()
  139.          * @see #getUnchecked()
  140.          */
  141.         @Override
  142.         public ChecksumInputStream get() throws IOException {
  143.             return new ChecksumInputStream(this);
  144.         }

  145.         /**
  146.          * Sets the Checksum. There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven
  147.          * deficient or insecure in the future.
  148.          *
  149.          * @param checksum the Checksum.
  150.          * @return {@code this} instance.
  151.          */
  152.         public Builder setChecksum(final Checksum checksum) {
  153.             this.checksum = checksum;
  154.             return this;
  155.         }

  156.         /**
  157.          * Sets the count threshold to limit how much input is consumed to update the {@link Checksum} before the input
  158.          * stream validates its value.
  159.          * <p>
  160.          * By default, all input updates the {@link Checksum}.
  161.          * </p>
  162.          *
  163.          * @param countThreshold the count threshold. A negative number means the threshold is unbound.
  164.          * @return {@code this} instance.
  165.          */
  166.         public Builder setCountThreshold(final long countThreshold) {
  167.             this.countThreshold = countThreshold;
  168.             return this;
  169.         }

  170.         /**
  171.          * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
  172.          *
  173.          * @param expectedChecksumValue The expected Checksum value.
  174.          * @return {@code this} instance.
  175.          */
  176.         public Builder setExpectedChecksumValue(final long expectedChecksumValue) {
  177.             this.expectedChecksumValue = expectedChecksumValue;
  178.             return this;
  179.         }

  180.     }

  181.     /**
  182.      * Constructs a new {@link Builder}.
  183.      *
  184.      * @return a new {@link Builder}.
  185.      */
  186.     public static Builder builder() {
  187.         return new Builder();
  188.     }

  189.     /** The expected checksum. */
  190.     private final long expectedChecksumValue;

  191.     /**
  192.      * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input stream
  193.      * validates its value.
  194.      * <p>
  195.      * By default, all input updates the {@link Checksum}.
  196.      * </p>
  197.      */
  198.     private final long countThreshold;

  199.     /**
  200.      * Constructs a new instance.
  201.      *
  202.      * @param builder build parameters.
  203.      */
  204.     @SuppressWarnings("resource")
  205.     private ChecksumInputStream(final Builder builder) throws IOException {
  206.         super(new CheckedInputStream(builder.getInputStream(), Objects.requireNonNull(builder.checksum, "builder.checksum")), builder);
  207.         this.countThreshold = builder.countThreshold;
  208.         this.expectedChecksumValue = builder.expectedChecksumValue;
  209.     }

  210.     @Override
  211.     protected synchronized void afterRead(final int n) throws IOException {
  212.         super.afterRead(n);
  213.         if ((countThreshold > 0 && getByteCount() >= countThreshold || n == EOF)
  214.                 && expectedChecksumValue != getChecksum().getValue()) {
  215.             // Validate when past the threshold or at EOF
  216.             throw new IOException("Checksum verification failed.");
  217.         }
  218.     }

  219.     /**
  220.      * Gets the current checksum value.
  221.      *
  222.      * @return the current checksum value.
  223.      */
  224.     private Checksum getChecksum() {
  225.         return ((CheckedInputStream) in).getChecksum();
  226.     }

  227.     /**
  228.      * Gets the byte count remaining to read.
  229.      *
  230.      * @return bytes remaining to read, a negative number means the threshold is unbound.
  231.      */
  232.     public long getRemaining() {
  233.         return countThreshold - getByteCount();
  234.     }

  235. }