net.matuschek.util.ChunkedInputStream.java Source code

Java tutorial

Introduction

Here is the source code for net.matuschek.util.ChunkedInputStream.java

Source

package net.matuschek.util;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Vector;
// ChunkedInputStream - an InputStream that implements HTTP/1.1 chunking
//
// Copyright (C) 1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/

/** 
 * Modifications done by Daniel Matuschek (daniel@matuschek.net)
 * - modified JavaDoc documentation
 * - adapted to Java 1.2, removed deprecated DataInputStream.readLine() method
 * - replaced DataInputStream by InputStream (there was no need for a
 *   DatainputStream, not idea why this was used in the original version)
 * - fixed a bug (there is an CRLF after every the data block)
 */

/** 
 * An InputStream that implements HTTP/1.1 chunking.
 * <P>
 * This class lets a Servlet read its request data as an HTTP/1.1 chunked
 * stream.  Chunked streams are a way to send arbitrary-length data without
 * having to know beforehand how much you're going to send.  They are
 * introduced by a "Transfer-Encoding: chunked" header, so if such a header
 * appears in an HTTP request you should use this class to read any data.
 * <P>
 * Sample usage:
 * <BLOCKQUOTE><PRE><CODE>
 * InputStream in = req.getInputStream();
 * if ( "chunked".equals( req.getHeader( "Transfer-Encoding" ) ) )
 *     in = new ChunkedInputStream( in );
 * </CODE></PRE></BLOCKQUOTE>
 * <P>
 * Because it would be impolite to make the authors of every Servlet include
 * the above code, this is general done at the server level so that it
 * happens automatically.  Servlet authors will generally not create
 * ChunkedInputStreams.  This is in contrast with ChunkedOutputStream,
 * which Servlets have to call themselves if they want to use it.
 * <P>
 * <A HREF="/resources/classes/Acme/Serve/servlet/http/ChunkedInputStream.java">Fetch the software.</A><BR>
 * <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
 *
 * @author Jef Poskanzer
 * @author Daniel Matuschek
 * @version $Id: ChunkedInputStream.java,v 1.6 2002/05/31 14:45:56 matuschd Exp $
 */
public class ChunkedInputStream extends FilterInputStream {

    private int contentLength;
    private byte[] b1 = new byte[1];

    /** number of bytes available in the current chunk */
    private int chunkCount = 0;

    private Vector<String> footerNames = null;
    private Vector<String> footerValues = null;

    /**
     * Make a ChunkedInputStream.
     */
    public ChunkedInputStream(InputStream in) {
        super(in);
        contentLength = 0;
    }

    /**
     * The FilterInputStream implementation of the single-byte read()
     * method just reads directly from the underlying stream.  We want
     * to go through our own read-block method, so we have to override.
     * Seems like FilterInputStream really ought to do this itself.
     */
    public int read() throws IOException {
        if (read(b1, 0, 1) == -1) {
            return -1;
        }

        return b1[0];
    }

    /**
     * Reads into an array of bytes.
     * @param b the buffer into which the data is read
     * @param off the start offset of the data
     * @param len the maximum number of bytes read
     * @return the actual number of bytes read, or -1 on EOF
     * @exception IOException if an I/O error has occurred
     */
    public int read(byte[] b, int off, int len) throws IOException {
        if (chunkCount == 0) {
            startChunk();
            if (chunkCount == 0) {
                return -1;
            }
        }
        int toRead = Math.min(chunkCount, len);
        int r = in.read(b, off, toRead);

        if (r != -1) {
            chunkCount -= r;
        }
        return r;
    }

    /**
     * Reads the start of a chunk.
     */
    private void startChunk() throws IOException {
        String line = readLine();
        if (line.equals("")) {
            line = readLine();
        }

        try {
            chunkCount = Integer.parseInt(line.trim(), 16);
        } catch (NumberFormatException e) {
            throw new IOException("malformed chunk (" + line + ")");
        }
        contentLength += chunkCount;
        if (chunkCount == 0) {
            readFooters();
        }

    }

    /** 
     * Reads any footers.
     */
    private void readFooters() throws IOException {
        footerNames = new Vector<String>();
        footerValues = new Vector<String>();
        String line;
        while (true) {
            line = readLine();
            if (line.length() == 0)
                break;
            int colon = line.indexOf(':');
            if (colon != -1) {
                String name = line.substring(0, colon).toLowerCase();
                String value = line.substring(colon + 1).trim();
                footerNames.addElement(name.toLowerCase());
                footerValues.addElement(value);
            }
        }
    }

    /**
     * Returns the value of a footer field, or null if not known.
     * Footers come at the end of a chunked stream, so trying to
     * retrieve them before the stream has given an EOF will return
     * only nulls.
     * @param name the footer field name
     */
    public String getFooter(String name) {
        if (!isDone())
            return null;
        int i = footerNames.indexOf(name.toLowerCase());
        if (i == -1)
            return null;
        return (String) footerValues.elementAt(i);
    }

    /**
     * Returns an Enumeration of the footer names.
     */
    public Enumeration getFooters() {
        if (!isDone())
            return null;
        return footerNames.elements();
    }

    /**
     * Returns the size of the request entity data, or -1 if not known.
     */
    public int getContentLength() {
        if (!isDone()) {
            return -1;
        }
        return contentLength;
    }

    /** 
     * Tells whether the stream has gotten to its end yet.  Remembering
     * whether you've gotten an EOF works fine too, but this is a convenient
     * predicate.  java.io.InputStream should probably have its own isEof()
     * predicate.
     */
    public boolean isDone() {
        return footerNames != null;
    }

    /**
     * ChunkedInputStream used DataInputStream.readLine() before. This method
     * is deprecated, therefore we will it replace by our own method.
     * Because the chunk lines only use 7bit ASCII, we can use the 
     * system default encoding
     * The data lines itself will not be read using this readLine method
     * but by a block read
     */
    protected String readLine() throws IOException {
        final byte CR = 13;
        final byte LF = 10;

        ByteBuffer buff = new ByteBuffer();
        byte b = 0;

        int i = 0;
        do {
            b = (byte) this.in.read();
            if (b != LF) {
                buff.append(b);
            }
            i++;
        } while ((b != LF));

        // according to the RFC there must be a CR before the LF, but some
        // web servers don't do this :-(
        byte[] byteBuff = buff.getContent();

        if (byteBuff.length == 0) {
            return "";
        }

        if (byteBuff[byteBuff.length - 1] != CR) {
            return new String(byteBuff);
        } else {
            return new String(byteBuff, 0, byteBuff.length - 1);
        }
    }

}

/*********************************************
Copyright (c) 2001 by Daniel Matuschek
*********************************************/

/**
 * A ByteBuffer implements a growable byte array. You can simple
 * add bytes like you do it using a Vector, but internally the buffer
 * is implemented as a real array of bytes. This increases memory usage.
 *
 * @author Daniel Matuschek
 * @version $Id $
 */
class ByteBuffer {

    protected final int INITIALSIZE = 1024;

    protected int used = 0;
    protected int size = 0;
    protected byte[] buff = null;

    /**
     * Initializes a new ByteBuffer object and creates
     * a temporary buffer array of a predefined initial size.
     * If you want to set your own initial size, use the <code>setSize</code>
     * method after initializing the object.
     * 
     */
    public ByteBuffer() {
        size = INITIALSIZE;
        buff = new byte[INITIALSIZE];
    }

    /**
     * Appends a byte to the end of the buffer
     *
     * If the currently reserved memory is used, the size of the 
     * internal buffer will be doubled.
     * In this case the memory usage will temprary increase by factor 3
     * because it need a temporary storage for the old data.
     *
     * Be sure that you have enough heap memory !
     *
     * @param b byte to append
     */
    public void append(byte b) {
        if (used >= size) {
            doubleBuffer();
        }

        buff[used] = b;
        used++;
    }

    /**
     * @return the number of bytes stored in the buffer
     */
    public int length() {
        return used;
    }

    /**
     * @return the buffer contents as a byte array
     */
    public byte[] getContent() {
        byte[] b = new byte[used];
        for (int i = 0; i < used; i++) {
            b[i] = buff[i];
        }
        return b;
    }

    /**
     * removes all contents in the buffer
     */
    public void clean() {
        used = 0;
    }

    /**
     * Sets the size of the internal buffer to
     * the given value. This is useful, if the size of the
     * data that should be stored is known.
     * @param size size of the buffer in Bytes
     */
    public void setSize(int size) {

        // if we have already used more data, ignore it !
        if (size < used) {
            return;
        }

        this.size = size;

        // create a new (larger) array
        byte[] newBuff = new byte[size];

        // copy contents
        for (int i = 0; i < used; i++) {
            newBuff[i] = buff[i];
        }

        buff = newBuff;
    }

    /**
     * Print the buffer content as a String (use it for debugging only !)
     * @return a String containing every byte in the buffer as a character
     */
    public String toString() {
        StringBuffer sb = new StringBuffer(buff.length);
        for (int i = 0; i < used; i++) {
            sb.append(buff[i]);
        }
        return sb.toString();
    }

    /**
     * doubles the size of the internal buffer
     */
    protected void doubleBuffer() {
        // increase size
        setSize(size * 2);
    }

}