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.zip.CheckedInputStream; 024import java.util.zip.Checksum; 025 026import org.apache.commons.io.build.AbstractStreamBuilder; 027 028/** 029 * Automatically verifies a {@link Checksum} value once the stream is exhausted or the count threshold is reached. 030 * <p> 031 * If the {@link Checksum} does not meet the expected value when exhausted, then the input stream throws an 032 * {@link IOException}. 033 * </p> 034 * <p> 035 * If you do not need the verification or threshold feature, then use a plain {@link CheckedInputStream}. 036 * </p> 037 * <p> 038 * To build an instance, use {@link Builder}. 039 * </p> 040 * 041 * @see Builder 042 * @since 2.16.0 043 */ 044public final class ChecksumInputStream extends CountingInputStream { 045 046 // @formatter:off 047 /** 048 * Builds a new {@link ChecksumInputStream}. 049 * 050 * <p> 051 * There is no default {@link Checksum}; you MUST provide one. 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 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}