All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.noelios.restlet.http.ChunkedInputStream Maven / Gradle / Ivy

Go to download

This OSGi bundle wraps org.restlet, and com.noelios.restlet ${pkgVersion} jar files.

The newest version!
/**
 * Copyright 2005-2008 Noelios Technologies.
 * 
 * The contents of this file are subject to the terms of the following open
 * source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 (the "Licenses"). You can
 * select the license that you prefer but you may not use this file except in
 * compliance with one of these Licenses.
 * 
 * You can obtain a copy of the LGPL 3.0 license at
 * http://www.gnu.org/licenses/lgpl-3.0.html
 * 
 * You can obtain a copy of the LGPL 2.1 license at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 * 
 * You can obtain a copy of the CDDL 1.0 license at
 * http://www.sun.com/cddl/cddl.html
 * 
 * See the Licenses for the specific language governing permissions and
 * limitations under the Licenses.
 * 
 * Alternatively, you can obtain a royaltee free commercial license with less
 * limitations, transferable or non-transferable, directly at
 * http://www.noelios.com/products/restlet-engine
 * 
 * Restlet is a registered trademark of Noelios Technologies.
 */

package com.noelios.restlet.http;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;

/**
 * {@link InputStream} to wrap a source {@link InputStream} that has been
 * chunked. See section 3.6.1 of HTTP Protocol for more information on chunked
 * encoding.
 * 
 * @author Kevin Conaway
 * @see HTTP/1.1
 *      Protocol
 */
public class ChunkedInputStream extends InputStream {

    /** Size of the push back buffer. */
    private static final int PUSHBBACK_BUFFER_SIZE = 2;

    /** Size of the current chunk. */
    private volatile long chunkSize;

    /** Indicates if the end of the source stream has been reached. */
    private volatile boolean endReached;

    /** Indicates if the chunked has been properly initialized. */
    private volatile boolean initialized;

    /** Indicates the position inside the current chunk. */
    private volatile long position;

    /** The source input stream to decode. */
    private final PushbackInputStream source;

    /**
     * Constructor.
     * 
     * @param source
     *            Source InputStream to read from
     */
    public ChunkedInputStream(InputStream source) {
        this.source = new PushbackInputStream(source, PUSHBBACK_BUFFER_SIZE);
        this.initialized = false;
        this.endReached = false;
        this.position = 0;
        this.chunkSize = 0;
    }

    /**
     * Indicates if the source stream can be read and prepare it if necessary.
     * 
     * @return True if the source stream can be read.
     * @throws IOException
     */
    private boolean canRead() throws IOException {
        boolean result = false;
        initialize();

        if (!this.endReached) {
            if (!chunkAvailable()) {
                initializeChunk();
            }

            result = !this.endReached;
        }

        return result;
    }

    /**
     * Checkes if the source stream will return a CR+LF sequence next, without
     * actually reading it.
     * 
     * @throws IOException
     */
    private void checkCRLF() throws IOException {
        final int cr = this.source.read();
        final int lf = this.source.read();

        if ((cr != '\r') && (lf != '\n')) {
            this.source.unread(lf);
            this.source.unread(cr);
        }
    }

    /**
     * Indicates if a chunk is available or false if a new one needs to be
     * initialized.
     * 
     * @return True if a chunk is available or false if a new one needs to be
     *         initialized.
     */
    private boolean chunkAvailable() {
        return this.position < this.chunkSize;
    }

    /**
     * Close this input stream but do not close the underlying stream.
     */
    @Override
    public void close() throws IOException {
        super.close();
        this.initialized = true;
        this.endReached = true;
    }

    /**
     * Initializes the stream by reading and discarding a CRLF (if present).
     * 
     * @throws IOException
     */
    private void initialize() throws IOException {
        if (!this.initialized) {
            checkCRLF();
            this.initialized = true;
        }
    }

    /**
     * Initialize the next chunk in the stream.
     * 
     * @throws IOException
     */
    private void initializeChunk() throws IOException {
        this.chunkSize = readChunkSize();
        this.position = 0;

        if (this.chunkSize == 0) {
            this.endReached = true;

            // Read the new line after the optional (unsupported) trailer
            checkCRLF();
        }
    }

    @Override
    public int read() throws IOException {
        int result = -1;

        if (canRead()) {
            result = this.source.read();
            this.position++;
            this.endReached = (result == -1);
        }

        return result;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int result = -1;

        if (canRead()) {
            result = this.source.read(b, off, Math.min(len,
                    (int) (this.chunkSize - this.position)));
            this.position += result;

            if (len - result > 0) {
                int nextResult = read(b, off + result, len - result);

                if (nextResult > 0) {
                    result += nextResult;
                }
            }
        }

        return result;
    }

    /**
     * Reads the chunk size from the current line.
     * 
     * @return The chunk size from the current line.
     * @throws IOException
     *             If the chunk size could not be read or was invalid.
     */
    private long readChunkSize() throws IOException {
        String line = readChunkSizeLine();
        final int index = line.indexOf(';');
        line = index == -1 ? line : line.substring(0, index);

        try {
            return Long.parseLong(line.trim(), 16);
        } catch (NumberFormatException ex) {
            throw new IOException("<" + line + "> is an invalid chunk size");
        }
    }

    /**
     * Reads a line containing a chunk size.
     * 
     * @return A line containing a chunk size.
     * @throws IOException
     */
    private String readChunkSizeLine() throws IOException {
        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        checkCRLF();

        for (;;) {
            final int b = this.source.read();

            if (b == -1) {
                throw new IOException(
                        "Invalid chunk size specified. End of stream reached");
            }

            if (b == '\r') {
                final int lf = this.source.read();

                if (lf == '\n') {
                    break;
                } else {
                    throw new IOException(
                            "Invalid chunk size specified.  Expected crlf, only saw cr");
                }
            }

            buffer.write(b);
        }

        return new String(buffer.toByteArray());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy