    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;
    023
    024/**
    025 * This is a stream that will only supply bytes up to a certain length - if its
    026 * position goes above that, it will stop.
    027 * <p>
    028 * This is useful to wrap ServletInputStreams. The ServletInputStream will block
    029 * if you try to read content from it that isn't there, because it doesn't know
    030 * whether the content hasn't arrived yet or whether the content has finished.
    031 * So, one of these, initialized with the Content-length sent in the
    032 * ServletInputStream's header, will stop it blocking, providing it's been sent
    033 * with a correct content length.
    034 * </p>
    035 *
    036 * @since 2.0
    037 */
    038public class BoundedInputStream extends InputStream {
    039
    040    /** the wrapped input stream */
    041    private final InputStream in;
    042
    043    /** the max length to provide */
    044    private final long max;
    045
    046    /** the number of bytes already returned */
    047    private long pos;
    048
    049    /** the marked position */
    050    private long mark = EOF;
    051
    052    /** flag if close should be propagated */
    053    private boolean propagateClose = true;
    054
    055    /**
    056     * Creates a new {@code BoundedInputStream} that wraps the given input
    057     * stream and limits it to a certain size.
    058     *
    059     * @param in The wrapped input stream
    060     * @param size The maximum number of bytes to return
    061     */
    062    public BoundedInputStream(final InputStream in, final long size) {
    063        // Some badly designed methods - eg the servlet API - overload length
    064        // such that "-1" means stream finished
    065        this.max = size;
    066        this.in = in;
    067    }
    068
    069    /**
    070     * Creates a new {@code BoundedInputStream} that wraps the given input
    071     * stream and is unlimited.
    072     *
    073     * @param in The wrapped input stream
    074     */
    075    public BoundedInputStream(final InputStream in) {
    076        this(in, EOF);
    077    }
    078
    079    /**
    080     * Invokes the delegate's {@code read()} method if
    081     * the current position is less than the limit.
    082     * @return the byte read or -1 if the end of stream or
    083     * the limit has been reached.
    084     * @throws IOException if an I/O error occurs.
    085     */
    086    @Override
    087    public int read() throws IOException {
    088        if (max >= 0 && pos >= max) {
    089            return EOF;
    090        }
    091        final int result = in.read();
    092        pos++;
    093        return result;
    094    }
    095
    096    /**
    097     * Invokes the delegate's {@code read(byte[])} method.
    098     * @param b the buffer to read the bytes into
    099     * @return the number of bytes read or -1 if the end of stream or
    100     * the limit has been reached.
    101     * @throws IOException if an I/O error occurs.
    102     */
    103    @Override
    104    public int read(final byte[] b) throws IOException {
    105        return this.read(b, 0, b.length);
    106    }
    107
    108    /**
    109     * Invokes the delegate's {@code read(byte[], int, int)} method.
    110     * @param b the buffer to read the bytes into
    111     * @param off The start offset
    112     * @param len The number of bytes to read
    113     * @return the number of bytes read or -1 if the end of stream or
    114     * the limit has been reached.
    115     * @throws IOException if an I/O error occurs.
    116     */
    117    @Override
    118    public int read(final byte[] b, final int off, final int len) throws IOException {
    119        if (max>=0 && pos>=max) {
    120            return EOF;
    121        }
    122        final long maxRead = max>=0 ? Math.min(len, max-pos) : len;
    123        final int bytesRead = in.read(b, off, (int)maxRead);
    124
    125        if (bytesRead==EOF) {
    126            return EOF;
    127        }
    128
    129        pos+=bytesRead;
    130        return bytesRead;
    131    }
    132
    133    /**
    134     * Invokes the delegate's {@code skip(long)} method.
    135     * @param n the number of bytes to skip
    136     * @return the actual number of bytes skipped
    137     * @throws IOException if an I/O error occurs.
    138     */
    139    @Override
    140    public long skip(final long n) throws IOException {
    141        final long toSkip = max>=0 ? Math.min(n, max-pos) : n;
    142        final long skippedBytes = in.skip(toSkip);
    143        pos+=skippedBytes;
    144        return skippedBytes;
    145    }
    146
    147    /**
    148     * {@inheritDoc}
    149     */
    150    @Override
    151    public int available() throws IOException {
    152        if (max>=0 && pos>=max) {
    153            return 0;
    154        }
    155        return in.available();
    156    }
    157
    158    /**
    159     * Invokes the delegate's {@code toString()} method.
    160     * @return the delegate's {@code toString()}
    161     */
    162    @Override
    163    public String toString() {
    164        return in.toString();
    165    }
    166
    167    /**
    168     * Invokes the delegate's {@code close()} method
    169     * if {@link #isPropagateClose()} is {@code true}.
    170     * @throws IOException if an I/O error occurs.
    171     */
    172    @Override
    173    public void close() throws IOException {
    174        if (propagateClose) {
    175            in.close();
    176        }
    177    }
    178
    179    /**
    180     * Invokes the delegate's {@code reset()} method.
    181     * @throws IOException if an I/O error occurs.
    182     */
    183    @Override
    184    public synchronized void reset() throws IOException {
    185        in.reset();
    186        pos = mark;
    187    }
    188
    189    /**
    190     * Invokes the delegate's {@code mark(int)} method.
    191     * @param readlimit read ahead limit
    192     */
    193    @Override
    194    public synchronized void mark(final int readlimit) {
    195        in.mark(readlimit);
    196        mark = pos;
    197    }
    198
    199    /**
    200     * Invokes the delegate's {@code markSupported()} method.
    201     * @return true if mark is supported, otherwise false
    202     */
    203    @Override
    204    public boolean markSupported() {
    205        return in.markSupported();
    206    }
    207
    208    /**
    209     * Indicates whether the {@link #close()} method
    210     * should propagate to the underling {@link InputStream}.
    211     *
    212     * @return {@code true} if calling {@link #close()}
    213     * propagates to the {@code close()} method of the
    214     * underlying stream or {@code false} if it does not.
    215     */
    216    public boolean isPropagateClose() {
    217        return propagateClose;
    218    }
    219
    220    /**
    221     * Set whether the {@link #close()} method
    222     * should propagate to the underling {@link InputStream}.
    223     *
    224     * @param propagateClose {@code true} if calling
    225     * {@link #close()} propagates to the {@code close()}
    226     * method of the underlying stream or
    227     * {@code false} if it does not.
    228     */
    229    public void setPropagateClose(final boolean propagateClose) {
    230        this.propagateClose = propagateClose;
    231    }
    232}