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 027import org.apache.commons.io.build.AbstractStreamBuilder; 028 029/** 030 * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached. 031 * <p> 032 * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an 033 * {@link IOException}. 034 * </p> 035 * <p> 036 * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}. 037 * </p> 038 * <p> 039 * To build an instance, use {@link Builder}. 040 * </p> 041 * 042 * @see Builder 043 * @since 2.16.0 044 */ 045public final class ChecksumInputStream extends CountingInputStream { 046 047 // @formatter:off 048 /** 049 * Builds a new {@link ChecksumInputStream}. 050 * 051 * <p> 052 * There is no default {@link Checksum}; you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure 053 * in the future. 054 * </p> 055 * <h2>Using NIO</h2> 056 * <pre>{@code 057 * ChecksumInputStream s = ChecksumInputStream.builder() 058 * .setPath(Paths.get("MyFile.xml")) 059 * .setChecksum(new CRC32()) 060 * .setExpectedChecksumValue(12345) 061 * .get(); 062 * }</pre> 063 * <h2>Using IO</h2> 064 * <pre>{@code 065 * ChecksumInputStream s = ChecksumInputStream.builder() 066 * .setFile(new File("MyFile.xml")) 067 * .setChecksum(new CRC32()) 068 * .setExpectedChecksumValue(12345) 069 * .get(); 070 * }</pre> 071 * <h2>Validating only part of an InputStream</h2> 072 * <p> 073 * The following validates the first 100 bytes of the given input. 074 * </p> 075 * <pre>{@code 076 * ChecksumInputStream s = ChecksumInputStream.builder() 077 * .setPath(Paths.get("MyFile.xml")) 078 * .setChecksum(new CRC32()) 079 * .setExpectedChecksumValue(12345) 080 * .setCountThreshold(100) 081 * .get(); 082 * }</pre> 083 * <p> 084 * To validate input <em>after</em> the beginning of a stream, build an instance with an InputStream starting where you want to validate. 085 * </p> 086 * <pre>{@code 087 * InputStream inputStream = ...; 088 * inputStream.read(...); 089 * inputStream.skip(...); 090 * ChecksumInputStream s = ChecksumInputStream.builder() 091 * .setInputStream(inputStream) 092 * .setChecksum(new CRC32()) 093 * .setExpectedChecksumValue(12345) 094 * .setCountThreshold(100) 095 * .get(); 096 * }</pre> 097 * 098 * @see #get() 099 */ 100 // @formatter:on 101 public static class Builder extends AbstractStreamBuilder<ChecksumInputStream, Builder> { 102 103 /** 104 * There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven deficient or insecure 105 * in the future. 106 */ 107 private Checksum checksum; 108 109 /** 110 * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input 111 * stream validates its value. 112 * <p> 113 * By default, all input updates the {@link Checksum}. 114 * </p> 115 */ 116 private long countThreshold = -1; 117 118 /** 119 * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached. 120 */ 121 private long expectedChecksumValue; 122 123 /** 124 * Builds a new {@link ChecksumInputStream}. 125 * <p> 126 * You must set input that supports {@link #getInputStream()}, otherwise, this method throws an exception. 127 * </p> 128 * <p> 129 * This builder use the following aspects: 130 * </p> 131 * <ul> 132 * <li>{@link #getInputStream()}</li> 133 * <li>{@link Checksum}</li> 134 * <li>expectedChecksumValue</li> 135 * <li>countThreshold</li> 136 * </ul> 137 * 138 * @return a new instance. 139 * @throws IllegalStateException if the {@code origin} is {@code null}. 140 * @throws UnsupportedOperationException if the origin cannot be converted to an {@link InputStream}. 141 * @throws IOException if an I/O error occurs. 142 * @see #getInputStream() 143 */ 144 @SuppressWarnings("resource") 145 @Override 146 public ChecksumInputStream get() throws IOException { 147 return new ChecksumInputStream(getInputStream(), checksum, expectedChecksumValue, countThreshold); 148 } 149 150 /** 151 * Sets the Checksum. There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven 152 * deficient or insecure in the future. 153 * 154 * @param checksum the Checksum. 155 * @return {@code this} instance. 156 */ 157 public Builder setChecksum(final Checksum checksum) { 158 this.checksum = checksum; 159 return this; 160 } 161 162 /** 163 * Sets the count threshold to limit how much input is consumed to update the {@link Checksum} before the input 164 * stream validates its value. 165 * <p> 166 * By default, all input updates the {@link Checksum}. 167 * </p> 168 * 169 * @param countThreshold the count threshold. A negative number means the threshold is unbound. 170 * @return {@code this} instance. 171 */ 172 public Builder setCountThreshold(final long countThreshold) { 173 this.countThreshold = countThreshold; 174 return this; 175 } 176 177 /** 178 * The expected {@link Checksum} value once the stream is exhausted or the count threshold is reached. 179 * 180 * @param expectedChecksumValue The expected Checksum value. 181 * @return {@code this} instance. 182 */ 183 public Builder setExpectedChecksumValue(final long expectedChecksumValue) { 184 this.expectedChecksumValue = expectedChecksumValue; 185 return this; 186 } 187 188 } 189 190 /** 191 * Constructs a new {@link Builder}. 192 * 193 * @return a new {@link Builder}. 194 */ 195 public static Builder builder() { 196 return new Builder(); 197 } 198 199 /** The expected checksum. */ 200 private final long expectedChecksumValue; 201 202 /** 203 * The count threshold to limit how much input is consumed to update the {@link Checksum} before the input stream 204 * validates its value. 205 * <p> 206 * By default, all input updates the {@link Checksum}. 207 * </p> 208 */ 209 private final long countThreshold; 210 211 /** 212 * Constructs a new instance. 213 * 214 * @param in the stream to wrap. 215 * @param checksum a Checksum implementation. 216 * @param expectedChecksumValue the expected checksum. 217 * @param countThreshold the count threshold to limit how much input is consumed, a negative number means the 218 * threshold is unbound. 219 */ 220 private ChecksumInputStream(final InputStream in, final Checksum checksum, final long expectedChecksumValue, 221 final long countThreshold) { 222 super(new CheckedInputStream(in, Objects.requireNonNull(checksum, "checksum"))); 223 this.countThreshold = countThreshold; 224 this.expectedChecksumValue = expectedChecksumValue; 225 } 226 227 @Override 228 protected synchronized void afterRead(final int n) throws IOException { 229 super.afterRead(n); 230 if ((countThreshold > 0 && getByteCount() >= countThreshold || n == EOF) 231 && expectedChecksumValue != getChecksum().getValue()) { 232 // Validate when past the threshold or at EOF 233 throw new IOException("Checksum verification failed."); 234 } 235 } 236 237 /** 238 * Gets the current checksum value. 239 * 240 * @return the current checksum value. 241 */ 242 private Checksum getChecksum() { 243 return ((CheckedInputStream) in).getChecksum(); 244 } 245 246 /** 247 * Gets the byte count remaining to read. 248 * 249 * @return bytes remaining to read, a negative number means the threshold is unbound. 250 */ 251 public long getRemaining() { 252 return countThreshold - getByteCount(); 253 } 254 255}