/* * Copyright (c) 2004, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.net.www.http; import java.io.*; /** * OutputStream that sends the output to the underlying stream using chunked * encoding as specified in RFC 2068. * * @author Alan Bateman */ public class ChunkedOutputStream extends PrintStream { /* Default chunk size (including chunk header) if not specified */ static final int DEFAULT_CHUNK_SIZE = 4096; /* internal buffer */ private byte buf[]; private int count; /* underlying stream */ private PrintStream out; /* the chunk size we use */ private int preferredChunkSize; /* if the users write buffer is bigger than this size, we * write direct from the users buffer instead of copying */ static final int MAX_BUF_SIZE = 10 * 1024; /* return the size of the header for a particular chunk size */ private int headerSize(int size) { return 2 + (Integer.toHexString(size)).length(); } public ChunkedOutputStream(PrintStream o) { this(o, DEFAULT_CHUNK_SIZE); } public ChunkedOutputStream(PrintStream o, int size) { super(o); out = o; if (size <= 0) { size = DEFAULT_CHUNK_SIZE; } /* Adjust the size to cater for the chunk header - eg: if the * preferred chunk size is 1k this means the chunk size should * be 1019 bytes (differs by 5 from preferred size because of * 3 bytes for chunk size in hex and CRLF). */ if (size > 0) { int adjusted_size = size - headerSize(size); if (adjusted_size + headerSize(adjusted_size) < size) { adjusted_size++; } size = adjusted_size; } if (size > 0) { preferredChunkSize = size; } else { preferredChunkSize = DEFAULT_CHUNK_SIZE - headerSize(DEFAULT_CHUNK_SIZE); } /* start with an initial buffer */ buf = new byte[preferredChunkSize + 32]; } /* * If flushAll is true, then all data is flushed in one chunk. * * If false and the size of the buffer data exceeds the preferred * chunk size then chunks are flushed to the output stream. * If there isn't enough data to make up a complete chunk, * then the method returns. */ private void flush(byte[] buf, boolean flushAll) { flush (buf, flushAll, 0); } private void flush(byte[] buf, boolean flushAll, int offset) { int chunkSize; do { if (count < preferredChunkSize) { if (!flushAll) { break; } chunkSize = count; } else { chunkSize = preferredChunkSize; } byte[] bytes = null; try { bytes = (Integer.toHexString(chunkSize)).getBytes("US-ASCII"); } catch (java.io.UnsupportedEncodingException e) { //This should never happen. throw new InternalError(e.getMessage()); } out.write(bytes, 0, bytes.length); out.write((byte)'\r'); out.write((byte)'\n'); if (chunkSize > 0) { out.write(buf, offset, chunkSize); out.write((byte)'\r'); out.write((byte)'\n'); } out.flush(); if (checkError()) { break; } if (chunkSize > 0) { count -= chunkSize; offset += chunkSize; } } while (count > 0); if (!checkError() && count > 0) { System.arraycopy(buf, offset, this.buf, 0, count); } } public boolean checkError() { return out.checkError(); } /* * Check if we have enough data for a chunk and if so flush to the * underlying output stream. */ private void checkFlush() { if (count >= preferredChunkSize) { flush(buf, false); } } /* Check that the output stream is still open */ private void ensureOpen() { if (out == null) setError(); } public synchronized void write(byte b[], int off, int len) { ensureOpen(); if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } if (len > MAX_BUF_SIZE) { /* first finish the current chunk */ int l = preferredChunkSize - count; if (l > 0) { System.arraycopy(b, off, buf, count, l); count = preferredChunkSize; flush(buf, false); } count = len - l; /* Now write the rest of the data */ flush (b, false, l+off); } else { int newcount = count + len; if (newcount > buf.length) { byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)]; System.arraycopy(buf, 0, newbuf, 0, count); buf = newbuf; } System.arraycopy(b, off, buf, count, len); count = newcount; checkFlush(); } } public synchronized void write(int b) { ensureOpen(); int newcount = count + 1; if (newcount > buf.length) { byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)]; System.arraycopy(buf, 0, newbuf, 0, count); buf = newbuf; } buf[count] = (byte)b; count = newcount; checkFlush(); } public synchronized void reset() { count = 0; } public int size() { return count; } public synchronized void close() { ensureOpen(); /* if we have buffer a chunked send it */ if (count > 0) { flush(buf, true); } /* send a zero length chunk */ flush(buf, true); /* don't close the underlying stream */ out = null; } public synchronized void flush() { ensureOpen(); if (count > 0) { flush(buf, true); } } }