    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.compress.utils;
    018
    019import java.io.FilterInputStream;
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.util.zip.Checksum;
    023
    024/**
    025 * Verifies the checksum of the data read once the stream is
    026 * exhausted.
    027 *
    028 * @NotThreadSafe
    029 * @since 1.7
    030 */
    031public class ChecksumVerifyingInputStream extends FilterInputStream {
    032
    033    private long bytesRemaining;
    034    private final long expectedChecksum;
    035    private final Checksum checksum;
    036
    037    /**
    038     * Constructs a new instance.
    039     *
    040     * @param checksum Checksum implementation.
    041     * @param in the stream to wrap
    042     * @param size the of the stream's content
    043     * @param expectedChecksum the expected checksum
    044     */
    045    public ChecksumVerifyingInputStream(final Checksum checksum, final InputStream in,
    046                                        final long size, final long expectedChecksum) {
    047        super(in);
    048        this.checksum = checksum;
    049        this.expectedChecksum = expectedChecksum;
    050        this.bytesRemaining = size;
    051    }
    052
    053    /**
    054     * @return bytes remaining to read
    055     * @since 1.21
    056     */
    057    public long getBytesRemaining() {
    058        return bytesRemaining;
    059    }
    060
    061    /**
    062     * Reads a single byte from the stream
    063     * @throws IOException if the underlying stream throws or the
    064     * stream is exhausted and the Checksum doesn't match the expected
    065     * value
    066     */
    067    @Override
    068    public int read() throws IOException {
    069        if (bytesRemaining <= 0) {
    070            return -1;
    071        }
    072        final int ret = in.read();
    073        if (ret >= 0) {
    074            checksum.update(ret);
    075            --bytesRemaining;
    076        }
    077        verify();
    078        return ret;
    079    }
    080
    081    /**
    082     * Reads from the stream into a byte array.
    083     * @throws IOException if the underlying stream throws or the
    084     * stream is exhausted and the Checksum doesn't match the expected
    085     * value
    086     */
    087    @Override
    088    public int read(final byte[] b, final int off, final int len) throws IOException {
    089        if (len == 0) {
    090            return 0;
    091        }
    092        final int ret = in.read(b, off, len);
    093        if (ret >= 0) {
    094            checksum.update(b, off, ret);
    095            bytesRemaining -= ret;
    096        }
    097        verify();
    098        return ret;
    099    }
    100
    101    @Override
    102    public long skip(final long n) throws IOException {
    103        // Can't really skip, we have to hash everything to verify the checksum
    104        return read() >= 0 ? 1 : 0;
    105    }
    106
    107    private void verify() throws IOException {
    108        if (bytesRemaining <= 0 && expectedChecksum != checksum.getValue()) {
    109            throw new IOException("Checksum verification failed");
    110        }
    111    }
    112}