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 }