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.fileupload.util;
018
019import java.io.FilterInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022
023/**
024 * An input stream, which limits its data size. This stream is
025 * used, if the content length is unknown.
026 */
027public abstract class LimitedInputStream extends FilterInputStream implements Closeable {
028
029    /**
030     * The maximum size of an item, in bytes.
031     */
032    private final long sizeMax;
033
034    /**
035     * The current number of bytes.
036     */
037    private long count;
038
039    /**
040     * Whether this stream is already closed.
041     */
042    private boolean closed;
043
044    /**
045     * Creates a new instance.
046     *
047     * @param inputStream The input stream, which shall be limited.
048     * @param sizeMax The limit; no more than this number of bytes
049     *   shall be returned by the source stream.
050     */
051    public LimitedInputStream(final InputStream inputStream, final long sizeMax) {
052        super(inputStream);
053        this.sizeMax = sizeMax;
054    }
055
056    /**
057     * Called to check, whether the input streams
058     * limit is reached.
059     *
060     * @throws IOException The given limit is exceeded.
061     */
062    private void checkLimit() throws IOException {
063        if (count > sizeMax) {
064            raiseError(sizeMax, count);
065        }
066    }
067
068    /**
069     * Closes this input stream and releases any system resources
070     * associated with the stream.
071     * This
072     * method simply performs {@code in.close()}.
073     *
074     * @throws  IOException  if an I/O error occurs.
075     * @see        java.io.FilterInputStream#in
076     */
077    @Override
078    public void close() throws IOException {
079        closed = true;
080        super.close();
081    }
082
083    /**
084     * Returns, whether this stream is already closed.
085     *
086     * @return True, if the stream is closed, otherwise false.
087     * @throws IOException An I/O error occurred.
088     */
089    @Override
090    public boolean isClosed() throws IOException {
091        return closed;
092    }
093
094    /**
095     * Called to indicate, that the input streams limit has
096     * been exceeded.
097     *
098     * @param sizeMax The input streams limit, in bytes.
099     * @param count The actual number of bytes.
100     * @throws IOException The called method is expected
101     *   to raise an IOException.
102     */
103    protected abstract void raiseError(long sizeMax, long count) throws IOException;
104
105    /**
106     * Reads the next byte of data from this input stream. The value
107     * byte is returned as an {@code int} in the range
108     * {@code 0} to {@code 255}. If no byte is available
109     * because the end of the stream has been reached, the value
110     * {@code -1} is returned. This method blocks until input data
111     * is available, the end of the stream is detected, or an exception
112     * is thrown.
113     * <p>
114     * This method
115     * simply performs {@code in.read()} and returns the result.
116     * </p>
117     *
118     * @return     the next byte of data, or {@code -1} if the end of the
119     *             stream is reached.
120     * @throws  IOException  if an I/O error occurs.
121     * @see        java.io.FilterInputStream#in
122     */
123    @Override
124    public int read() throws IOException {
125        final int res = super.read();
126        if (res != -1) {
127            count++;
128            checkLimit();
129        }
130        return res;
131    }
132
133    /**
134     * Reads up to {@code len} bytes of data from this input stream
135     * into an array of bytes. If {@code len} is not zero, the method
136     * blocks until some input is available; otherwise, no
137     * bytes are read and {@code 0} is returned.
138     * <p>
139     * This method simply performs {@code in.read(b, off, len)}
140     * and returns the result.
141     * </p>
142     *
143     * @param      b     the buffer into which the data is read.
144     * @param      off   The start offset in the destination array
145     *                   {@code b}.
146     * @param      len   the maximum number of bytes read.
147     * @return     the total number of bytes read into the buffer, or
148     *             {@code -1} if there is no more data because the end of
149     *             the stream has been reached.
150     * @throws  NullPointerException If {@code b} is {@code null}.
151     * @throws  IndexOutOfBoundsException If {@code off} is negative,
152     * {@code len} is negative, or {@code len} is greater than
153     * {@code b.length - off}
154     * @throws  IOException  if an I/O error occurs.
155     * @see        java.io.FilterInputStream#in
156     */
157    @Override
158    public int read(final byte[] b, final int off, final int len) throws IOException {
159        final int res = super.read(b, off, len);
160        if (res > 0) {
161            count += res;
162            checkLimit();
163        }
164        return res;
165    }
166
167}