001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.io.input; 018 019import static org.apache.commons.io.IOUtils.EOF; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.Objects; 024import java.util.zip.CheckedInputStream; 025import java.util.zip.Checksum; 026 027/** 028 * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached. 029 * <p> 030 * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an 031 * {@link IOException}. 032 * </p> 033 * <p> 034 * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}. 035 * </p> 036 * <p> 037 * To build an instance, use {@link Builder}. 038 * </p> 039 * 040 * @see Builder 041 * @since 2.16.0 042 */ 043public final class ChecksumInputStream extends CountingInputStream { 044 045 // @formatter:off 046 /** 047 * Builds a new {@link ChecksumInputStream}. 048 * 049 * <p> 050 * There is no default {@link Checksum}; you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure 051 * in the future. 052 * </p> 053 * <h2>Using NIO</h2> 054 * <pre>{@code 055 * ChecksumInputStream s = ChecksumInputStream.builder() 056 * .setPath(Paths.get("MyFile.xml")) 057 * .setChecksum(new CRC32()) 058 * .setExpectedChecksumValue(12345) 059 * .get(); 060 * }</pre> 061 * <h2>Using IO</h2> 062 * <pre>{@code 063 * ChecksumInputStream s = ChecksumInputStream.builder() 064 * .setFile(new File("MyFile.xml")) 065 * .setChecksum(new CRC32()) 066 * .setExpectedChecksumValue(12345) 067 * .get(); 068 * }</pre> 069 * <h2>Validating only part of an InputStream</h2> 070 * <p> 071 * The following validates the first 100 bytes of the given input. 072 * </p> 073 * <pre>{@code 074 * ChecksumInputStream s = ChecksumInputStream.builder() 075 * .setPath(Paths.get("MyFile.xml")) 076 * .setChecksum(new CRC32()) 077 * .setExpectedChecksumValue(12345) 078 * .setCountThreshold(100) 079 * .get(); 080 * }</pre> 081 * <p> 082 * To validate input <em>after</em> the beginning of a stream, build an instance with an InputStream starting where you want to validate. 083 * </p> 084 * <pre>{@code 085 * InputStream inputStream = ...; 086 * inputStream.read(...); 087 * inputStream.skip(...); 088 * ChecksumInputStream s = ChecksumInputStream.builder() 089 * .setInputStream(inputStream) 090 * .setChecksum(new CRC32()) 091 * .setExpectedChecksumValue(12345) 092 * .setCountThreshold(100) 093 * .get(); 094 * }</pre> 095 * 096 * @see #get() 097 */ 098 // @formatter:on 099 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}