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 * 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 @Override 143 public ChecksumInputStream get() throws IOException { 144 return new ChecksumInputStream(this); 145 } 146 147 /** 148 * Sets the Checksum. There is no default {@link Checksum}, you MUST provide one. This avoids any issue with a default {@link Checksum} being proven 149 * deficient or insecure in the future. 150 * 151 * @param checksum the Checksum. 152 * @return {@code this} instance. 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 {@code this} instance. 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 {@code this} instance. 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 builder build parameters. 212 */ 213 @SuppressWarnings("resource") 214 private ChecksumInputStream(final Builder builder) throws IOException { 215 super(new CheckedInputStream(builder.getInputStream(), Objects.requireNonNull(builder.checksum, "builder.checksum")), builder); 216 this.countThreshold = builder.countThreshold; 217 this.expectedChecksumValue = builder.expectedChecksumValue; 218 } 219 220 @Override 221 protected synchronized void afterRead(final int n) throws IOException { 222 super.afterRead(n); 223 if ((countThreshold > 0 && getByteCount() >= countThreshold || n == EOF) 224 && expectedChecksumValue != getChecksum().getValue()) { 225 // Validate when past the threshold or at EOF 226 throw new IOException("Checksum verification failed."); 227 } 228 } 229 230 /** 231 * Gets the current checksum value. 232 * 233 * @return the current checksum value. 234 */ 235 private Checksum getChecksum() { 236 return ((CheckedInputStream) in).getChecksum(); 237 } 238 239 /** 240 * Gets the byte count remaining to read. 241 * 242 * @return bytes remaining to read, a negative number means the threshold is unbound. 243 */ 244 public long getRemaining() { 245 return countThreshold - getByteCount(); 246 } 247 248}