com.signalfx.shaded.apache.commons.io.input.ChecksumInputStream Maven / Gradle / Ivy
Show all versions of signalfx-codahale Show documentation
/*
* 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 com.signalfx.shaded.apache.commons.io.input;
import static com.signalfx.shaded.apache.commons.io.IOUtils.EOF;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.CheckedInputStream;
import java.util.zip.Checksum;
import com.signalfx.shaded.apache.commons.io.build.AbstractStreamBuilder;
/**
* Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached.
*
* If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an
* {@link IOException}.
*
*
* If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}.
*
*
* To build an instance, use {@link Builder}.
*
*
* @see Builder
* @since 2.16.0
*/
public final class ChecksumInputStream extends CountingInputStream {
// @formatter:off
/**
* Builds a new {@link ChecksumInputStream}.
*
*
* There is no default {@link Checksum}; you MUST provide one.
*
* Using NIO
* {@code
* ChecksumInputStream s = ChecksumInputStream.builder()
* .setPath(Paths.get("MyFile.xml"))
* .setChecksum(new CRC32())
* .setExpectedChecksumValue(12345)
* .get();
* }
* Using IO
* {@code
* ChecksumInputStream s = ChecksumInputStream.builder()
* .setFile(new File("MyFile.xml"))
* .setChecksum(new CRC32())
* .setExpectedChecksumValue(12345)
* .get();
* }
* Validating only part of an InputStream
*
* The following validates the first 100 bytes of the given input.
*
* {@code
* ChecksumInputStream s = ChecksumInputStream.builder()
* .setPath(Paths.get("MyFile.xml"))
* .setChecksum(new CRC32())
* .setExpectedChecksumValue(12345)
* .setCountThreshold(100)
* .get();
* }
*
* To validate input after the beginning of a stream, build an instance with an InputStream starting where you want to validate.
*
* {@code
* InputStream inputStream = ...;
* inputStream.read(...);
* inputStream.skip(...);
* ChecksumInputStream s = ChecksumInputStream.builder()
* .setInputStream(inputStream)
* .setChecksum(new CRC32())
* .setExpectedChecksumValue(12345)
* .setCountThreshold(100)
* .get();
* }
*
* @see #get()
*/
// @formatter:on
public static class Builder extends AbstractStreamBuilder {
/**
* There is no default 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.
*
* By default, all input updates the {@link Checksum}.
*
*/
private long countThreshold = -1;
/**
* The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached.
*/
private long expectedChecksumValue;
/**
* Builds a new {@link ChecksumInputStream}.
*
* You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception.
*
*
* This builder use the following aspects:
*
*
* - {@link #getInputStream()}
* - {@link Checksum}
* - expectedChecksumValue
* - countThreshold
*
*
* @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.
* @see #getInputStream()
*/
@SuppressWarnings("resource")
@Override
public ChecksumInputStream get() throws IOException {
return new ChecksumInputStream(getInputStream(), checksum, expectedChecksumValue, countThreshold);
}
/**
* Sets the Checksum.
*
* @param checksum the Checksum.
* @return this.
*/
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.
*
* By default, all input updates the {@link Checksum}.
*
*
* @param countThreshold the count threshold. A negative number means the threshold is unbound.
* @return this.
*/
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 this.
*/
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.
*
* By default, all input updates the {@link Checksum}.
*
*/
private final long countThreshold;
/**
* Constructs a new instance.
*
* @param in the stream to wrap.
* @param checksum a Checksum implementation.
* @param expectedChecksumValue the expected checksum.
* @param countThreshold the count threshold to limit how much input is consumed, a negative number means the
* threshold is unbound.
*/
private ChecksumInputStream(final InputStream in, final Checksum checksum, final long expectedChecksumValue,
final long countThreshold) {
super(new CheckedInputStream(in, checksum));
this.countThreshold = countThreshold;
this.expectedChecksumValue = 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();
}
}