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.zip.CheckedInputStream; 24 import java.util.zip.Checksum; 25 26 import org.apache.commons.io.build.AbstractStreamBuilder; 27 28 /** 29 * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached. 30 * <p> 31 * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an 32 * {@link IOException}. 33 * </p> 34 * <p> 35 * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}. 36 * </p> 37 * <p> 38 * To build an instance, use {@link Builder}. 39 * </p> 40 * 41 * @see Builder 42 * @since 2.16.0 43 */ 44 public final class ChecksumInputStream extends CountingInputStream { 45 46 // @formatter:off 47 /** 48 * Builds a new {@link ChecksumInputStream}. 49 * 50 * <p> 51 * There is no default {@link Checksum}; you MUST provide one. 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 AbstractStreamBuilder<ChecksumInputStream, Builder> { 100 101 /** 102 * There is no default checksum, you MUST provide one. This avoids any issue with a default {@link Checksum} 103 * being proven deficient or insecure 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 * Builds a new {@link ChecksumInputStream}. 123 * <p> 124 * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception. 125 * </p> 126 * <p> 127 * This builder use the following aspects: 128 * </p> 129 * <ul> 130 * <li>{@link #getInputStream()}</li> 131 * <li>{@link Checksum}</li> 132 * <li>expectedChecksumValue</li> 133 * <li>countThreshold</li> 134 * </ul> 135 * 136 * @return a new instance. 137 * @throws IllegalStateException if the {@code origin} is {@code null}. 138 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}. 139 * @throws IOException if an I/O error occurs. 140 * @see #getInputStream() 141 */ 142 @SuppressWarnings("resource") 143 @Override 144 public ChecksumInputStream get() throws IOException { 145 return new ChecksumInputStream(getInputStream(), checksum, expectedChecksumValue, countThreshold); 146 } 147 148 /** 149 * Sets the Checksum. 150 * 151 * @param checksum the Checksum. 152 * @return this. 153 */ 154 public Builder setChecksum(final Checksum checksum) { 155 this.checksum = checksum; 156 return this; 157 } 158 159 /** 160 * Sets the count threshold to limit how much input is consumed to update the {@link Checksum} before the input 161 * stream validates its value. 162 * <p> 163 * By default, all input updates the {@link Checksum}. 164 * </p> 165 * 166 * @param countThreshold the count threshold. A negative number means the threshold is unbound. 167 * @return this. 168 */ 169 public Builder setCountThreshold(final long countThreshold) { 170 this.countThreshold = countThreshold; 171 return this; 172 } 173 174 /** 175 * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached. 176 * 177 * @param expectedChecksumValue The expected Checksum value. 178 * @return this. 179 */ 180 public Builder setExpectedChecksumValue(final long expectedChecksumValue) { 181 this.expectedChecksumValue = expectedChecksumValue; 182 return this; 183 } 184 185 } 186 187 /** 188 * Constructs a new {@link Builder}. 189 * 190 * @return a new {@link Builder}. 191 */ 192 public static Builder builder() { 193 return new Builder(); 194 } 195 196 /** The expected checksum. */ 197 private final long expectedChecksumValue; 198 199 /** 200 * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input stream 201 * validates its value. 202 * <p> 203 * By default, all input updates the {@link Checksum}. 204 * </p> 205 */ 206 private final long countThreshold; 207 208 /** 209 * Constructs a new instance. 210 * 211 * @param in the stream to wrap. 212 * @param checksum a Checksum implementation. 213 * @param expectedChecksumValue the expected checksum. 214 * @param countThreshold the count threshold to limit how much input is consumed, a negative number means the 215 * threshold is unbound. 216 */ 217 private ChecksumInputStream(final InputStream in, final Checksum checksum, final long expectedChecksumValue, 218 final long countThreshold) { 219 super(new CheckedInputStream(in, checksum)); 220 this.countThreshold = countThreshold; 221 this.expectedChecksumValue = expectedChecksumValue; 222 } 223 224 @Override 225 protected synchronized void afterRead(final int n) throws IOException { 226 super.afterRead(n); 227 if ((countThreshold > 0 && getByteCount() >= countThreshold || n == EOF) 228 && expectedChecksumValue != getChecksum().getValue()) { 229 // Validate when past the threshold or at EOF 230 throw new IOException("Checksum verification failed."); 231 } 232 } 233 234 /** 235 * Gets the current checksum value. 236 * 237 * @return the current checksum value. 238 */ 239 private Checksum getChecksum() { 240 return ((CheckedInputStream) in).getChecksum(); 241 } 242 243 /** 244 * Gets the byte count remaining to read. 245 * 246 * @return bytes remaining to read, a negative number means the threshold is unbound. 247 */ 248 public long getRemaining() { 249 return countThreshold - getByteCount(); 250 } 251 252 }