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