Path: blob/master/src/java.base/share/classes/sun/net/www/http/ChunkedInputStream.java
41161 views
/*1* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.net.www.http;2526import java.io.*;27import java.util.concurrent.locks.ReentrantLock;28import sun.net.www.*;29import sun.nio.cs.US_ASCII;3031/**32* A <code>ChunkedInputStream</code> provides a stream for reading a body of33* a http message that can be sent as a series of chunks, each with its own34* size indicator. Optionally the last chunk can be followed by trailers35* containing entity-header fields.36* <p>37* A <code>ChunkedInputStream</code> is also <code>Hurryable</code> so it38* can be hurried to the end of the stream if the bytes are available on39* the underlying stream.40*/41public class ChunkedInputStream extends InputStream implements Hurryable {4243/**44* The underlying stream45*/46private InputStream in;4748/**49* The <code>HttpClient</code> that should be notified when the chunked stream has50* completed.51*/52private HttpClient hc;5354/**55* The <code>MessageHeader</code> that is populated with any optional trailer56* that appear after the last chunk.57*/58private MessageHeader responses;5960/**61* The size, in bytes, of the chunk that is currently being read.62* This size is only valid if the current position in the underlying63* input stream is inside a chunk (ie: state == STATE_READING_CHUNK).64*/65private int chunkSize;6667/**68* The number of bytes read from the underlying stream for the current69* chunk. This value is always in the range <code>0</code> through to70* <code>chunkSize</code>71*/72private int chunkRead;7374/**75* The internal buffer array where chunk data is available for the76* application to read.77*/78private byte chunkData[] = new byte[4096];7980/**81* The current position in the buffer. It contains the index82* of the next byte to read from <code>chunkData</code>83*/84private int chunkPos;8586/**87* The index one greater than the index of the last valid byte in the88* buffer. This value is always in the range <code>0</code> through89* <code>chunkData.length</code>.90*/91private int chunkCount;9293/**94* The internal buffer where bytes from the underlying stream can be95* read. It may contain bytes representing chunk-size, chunk-data, or96* trailer fields.97*/98private byte rawData[] = new byte[32];99100/**101* The current position in the buffer. It contains the index102* of the next byte to read from <code>rawData</code>103*/104private int rawPos;105106/**107* The index one greater than the index of the last valid byte in the108* buffer. This value is always in the range <code>0</code> through109* <code>rawData.length</code>.110*/111private int rawCount;112113/**114* Indicates if an error was encountered when processing the chunked115* stream.116*/117private boolean error;118119/**120* Indicates if the chunked stream has been closed using the121* <code>close</code> method.122*/123private boolean closed;124125private final ReentrantLock readLock = new ReentrantLock();126127/*128* Maximum chunk header size of 2KB + 2 bytes for CRLF129*/130private static final int MAX_CHUNK_HEADER_SIZE = 2050;131132/**133* State to indicate that next field should be :-134* chunk-size [ chunk-extension ] CRLF135*/136static final int STATE_AWAITING_CHUNK_HEADER = 1;137138/**139* State to indicate that we are currently reading the chunk-data.140*/141static final int STATE_READING_CHUNK = 2;142143/**144* Indicates that a chunk has been completely read and the next145* fields to be examine should be CRLF146*/147static final int STATE_AWAITING_CHUNK_EOL = 3;148149/**150* Indicates that all chunks have been read and the next field151* should be optional trailers or an indication that the chunked152* stream is complete.153*/154static final int STATE_AWAITING_TRAILERS = 4;155156/**157* State to indicate that the chunked stream is complete and158* no further bytes should be read from the underlying stream.159*/160static final int STATE_DONE = 5;161162/**163* Indicates the current state.164*/165private int state;166167168/**169* Check to make sure that this stream has not been closed.170*/171private void ensureOpen() throws IOException {172if (closed) {173throw new IOException("stream is closed");174}175}176177178/**179* Ensures there is <code>size</code> bytes available in180* <code>rawData</code>. This requires that we either181* shift the bytes in use to the begining of the buffer182* or allocate a large buffer with sufficient space available.183*/184private void ensureRawAvailable(int size) {185if (rawCount + size > rawData.length) {186int used = rawCount - rawPos;187if (used + size > rawData.length) {188byte tmp[] = new byte[used + size];189if (used > 0) {190System.arraycopy(rawData, rawPos, tmp, 0, used);191}192rawData = tmp;193} else {194if (used > 0) {195System.arraycopy(rawData, rawPos, rawData, 0, used);196}197}198rawCount = used;199rawPos = 0;200}201}202203204/**205* Close the underlying input stream by either returning it to the206* keep alive cache or closing the stream.207* <p>208* As a chunked stream is inheritly persistent (see HTTP 1.1 RFC) the209* underlying stream can be returned to the keep alive cache if the210* stream can be completely read without error.211*/212private void closeUnderlying() throws IOException {213if (in == null) {214return;215}216217if (!error && state == STATE_DONE) {218hc.finished();219} else {220if (!hurry()) {221hc.closeServer();222}223}224225in = null;226}227228/**229* Attempt to read the remainder of a chunk directly into the230* caller's buffer.231* <p>232* Return the number of bytes read.233*/234private int fastRead(byte[] b, int off, int len) throws IOException {235236// assert state == STATE_READING_CHUNKS;237238int remaining = chunkSize - chunkRead;239int cnt = (remaining < len) ? remaining : len;240if (cnt > 0) {241int nread;242try {243nread = in.read(b, off, cnt);244} catch (IOException e) {245error = true;246throw e;247}248if (nread > 0) {249chunkRead += nread;250if (chunkRead >= chunkSize) {251state = STATE_AWAITING_CHUNK_EOL;252}253return nread;254}255error = true;256throw new IOException("Premature EOF");257} else {258return 0;259}260}261262/**263* Process any outstanding bytes that have already been read into264* <code>rawData</code>.265* <p>266* The parsing of the chunked stream is performed as a state machine with267* <code>state</code> representing the current state of the processing.268* <p>269* Returns when either all the outstanding bytes in rawData have been270* processed or there is insufficient bytes available to continue271* processing. When the latter occurs <code>rawPos</code> will not have272* been updated and thus the processing can be restarted once further273* bytes have been read into <code>rawData</code>.274*/275private void processRaw() throws IOException {276int pos;277int i;278279while (state != STATE_DONE) {280281switch (state) {282283/**284* We are awaiting a line with a chunk header285*/286case STATE_AWAITING_CHUNK_HEADER:287/*288* Find \n to indicate end of chunk header. If not found when there is289* insufficient bytes in the raw buffer to parse a chunk header.290*/291pos = rawPos;292while (pos < rawCount) {293if (rawData[pos] == '\n') {294break;295}296pos++;297if ((pos - rawPos) >= MAX_CHUNK_HEADER_SIZE) {298error = true;299throw new IOException("Chunk header too long");300}301}302if (pos >= rawCount) {303return;304}305306/*307* Extract the chunk size from the header (ignoring extensions).308*/309String header = new String(rawData, rawPos, pos-rawPos+1,310US_ASCII.INSTANCE);311for (i=0; i < header.length(); i++) {312if (Character.digit(header.charAt(i), 16) == -1)313break;314}315try {316chunkSize = Integer.parseInt(header, 0, i, 16);317} catch (NumberFormatException e) {318error = true;319throw new IOException("Bogus chunk size");320}321322/*323* Chunk has been parsed so move rawPos to first byte of chunk324* data.325*/326rawPos = pos + 1;327chunkRead = 0;328329/*330* A chunk size of 0 means EOF.331*/332if (chunkSize > 0) {333state = STATE_READING_CHUNK;334} else {335state = STATE_AWAITING_TRAILERS;336}337break;338339340/**341* We are awaiting raw entity data (some may have already been342* read). chunkSize is the size of the chunk; chunkRead is the343* total read from the underlying stream to date.344*/345case STATE_READING_CHUNK :346/* no data available yet */347if (rawPos >= rawCount) {348return;349}350351/*352* Compute the number of bytes of chunk data available in the353* raw buffer.354*/355int copyLen = Math.min( chunkSize-chunkRead, rawCount-rawPos );356357/*358* Expand or compact chunkData if needed.359*/360if (chunkData.length < chunkCount + copyLen) {361int cnt = chunkCount - chunkPos;362if (chunkData.length < cnt + copyLen) {363byte tmp[] = new byte[cnt + copyLen];364System.arraycopy(chunkData, chunkPos, tmp, 0, cnt);365chunkData = tmp;366} else {367System.arraycopy(chunkData, chunkPos, chunkData, 0, cnt);368}369chunkPos = 0;370chunkCount = cnt;371}372373/*374* Copy the chunk data into chunkData so that it's available375* to the read methods.376*/377System.arraycopy(rawData, rawPos, chunkData, chunkCount, copyLen);378rawPos += copyLen;379chunkCount += copyLen;380chunkRead += copyLen;381382/*383* If all the chunk has been copied into chunkData then the next384* token should be CRLF.385*/386if (chunkSize - chunkRead <= 0) {387state = STATE_AWAITING_CHUNK_EOL;388} else {389return;390}391break;392393394/**395* Awaiting CRLF after the chunk396*/397case STATE_AWAITING_CHUNK_EOL:398/* not available yet */399if (rawPos + 1 >= rawCount) {400return;401}402403if (rawData[rawPos] != '\r') {404error = true;405throw new IOException("missing CR");406}407if (rawData[rawPos+1] != '\n') {408error = true;409throw new IOException("missing LF");410}411rawPos += 2;412413/*414* Move onto the next chunk415*/416state = STATE_AWAITING_CHUNK_HEADER;417break;418419420/**421* Last chunk has been read so not we're waiting for optional422* trailers.423*/424case STATE_AWAITING_TRAILERS:425426/*427* Do we have an entire line in the raw buffer?428*/429pos = rawPos;430while (pos < rawCount) {431if (rawData[pos] == '\n') {432break;433}434pos++;435}436if (pos >= rawCount) {437return;438}439440if (pos == rawPos) {441error = true;442throw new IOException("LF should be proceeded by CR");443}444if (rawData[pos-1] != '\r') {445error = true;446throw new IOException("LF should be proceeded by CR");447}448449/*450* Stream done so close underlying stream.451*/452if (pos == (rawPos + 1)) {453454state = STATE_DONE;455closeUnderlying();456457return;458}459460/*461* Extract any tailers and append them to the message462* headers.463*/464String trailer = new String(rawData, rawPos, pos-rawPos,465US_ASCII.INSTANCE);466i = trailer.indexOf(':');467if (i == -1) {468throw new IOException("Malformed tailer - format should be key:value");469}470String key = (trailer.substring(0, i)).trim();471String value = (trailer.substring(i+1, trailer.length())).trim();472473responses.add(key, value);474475/*476* Move onto the next trailer.477*/478rawPos = pos+1;479break;480481} /* switch */482}483}484485486/**487* Reads any available bytes from the underlying stream into488* <code>rawData</code> and returns the number of bytes of489* chunk data available in <code>chunkData</code> that the490* application can read.491*/492private int readAheadNonBlocking() throws IOException {493494/*495* If there's anything available on the underlying stream then we read496* it into the raw buffer and process it. Processing ensures that any497* available chunk data is made available in chunkData.498*/499int avail = in.available();500if (avail > 0) {501502/* ensure that there is space in rawData to read the available */503ensureRawAvailable(avail);504505int nread;506try {507nread = in.read(rawData, rawCount, avail);508} catch (IOException e) {509error = true;510throw e;511}512if (nread < 0) {513error = true; /* premature EOF ? */514return -1;515}516rawCount += nread;517518/*519* Process the raw bytes that have been read.520*/521processRaw();522}523524/*525* Return the number of chunked bytes available to read526*/527return chunkCount - chunkPos;528}529530/**531* Reads from the underlying stream until there is chunk data532* available in <code>chunkData</code> for the application to533* read.534*/535private int readAheadBlocking() throws IOException {536537do {538/*539* All of chunked response has been read to return EOF.540*/541if (state == STATE_DONE) {542return -1;543}544545/*546* We must read into the raw buffer so make sure there is space547* available. We use a size of 32 to avoid too much chunk data548* being read into the raw buffer.549*/550ensureRawAvailable(32);551int nread;552try {553nread = in.read(rawData, rawCount, rawData.length-rawCount);554} catch (IOException e) {555error = true;556throw e;557}558559/**560* If we hit EOF it means there's a problem as we should never561* attempt to read once the last chunk and trailers have been562* received.563*/564if (nread < 0) {565error = true;566throw new IOException("Premature EOF");567}568569/**570* Process the bytes from the underlying stream571*/572rawCount += nread;573processRaw();574575} while (chunkCount <= 0);576577/*578* Return the number of chunked bytes available to read579*/580return chunkCount - chunkPos;581}582583/**584* Read ahead in either blocking or non-blocking mode. This method585* is typically used when we run out of available bytes in586* <code>chunkData</code> or we need to determine how many bytes587* are available on the input stream.588*/589private int readAhead(boolean allowBlocking) throws IOException {590591/*592* Last chunk already received - return EOF593*/594if (state == STATE_DONE) {595return -1;596}597598/*599* Reset position/count if data in chunkData is exhausted.600*/601if (chunkPos >= chunkCount) {602chunkCount = 0;603chunkPos = 0;604}605606/*607* Read ahead blocking or non-blocking608*/609if (allowBlocking) {610return readAheadBlocking();611} else {612return readAheadNonBlocking();613}614}615616/**617* Creates a <code>ChunkedInputStream</code> and saves its arguments, for618* later use.619*620* @param in the underlying input stream.621* @param hc the HttpClient622* @param responses the MessageHeader that should be populated with optional623* trailers.624*/625public ChunkedInputStream(InputStream in, HttpClient hc, MessageHeader responses) throws IOException {626627/* save arguments */628this.in = in;629this.responses = responses;630this.hc = hc;631632/*633* Set our initial state to indicate that we are first starting to634* look for a chunk header.635*/636state = STATE_AWAITING_CHUNK_HEADER;637}638639/**640* See641* the general contract of the <code>read</code>642* method of <code>InputStream</code>.643*644* @return the next byte of data, or <code>-1</code> if the end of the645* stream is reached.646* @exception IOException if an I/O error occurs.647* @see java.io.FilterInputStream#in648*/649public int read() throws IOException {650readLock.lock();651try {652ensureOpen();653if (chunkPos >= chunkCount) {654if (readAhead(true) <= 0) {655return -1;656}657}658return chunkData[chunkPos++] & 0xff;659} finally {660readLock.unlock();661}662}663664665/**666* Reads bytes from this stream into the specified byte array, starting at667* the given offset.668*669* @param b destination buffer.670* @param off offset at which to start storing bytes.671* @param len maximum number of bytes to read.672* @return the number of bytes read, or <code>-1</code> if the end of673* the stream has been reached.674* @exception IOException if an I/O error occurs.675*/676public int read(byte b[], int off, int len)677throws IOException678{679readLock.lock();680try {681ensureOpen();682if ((off < 0) || (off > b.length) || (len < 0) ||683((off + len) > b.length) || ((off + len) < 0)) {684throw new IndexOutOfBoundsException();685} else if (len == 0) {686return 0;687}688689int avail = chunkCount - chunkPos;690if (avail <= 0) {691/*692* Optimization: if we're in the middle of the chunk read693* directly from the underlying stream into the caller's694* buffer695*/696if (state == STATE_READING_CHUNK) {697return fastRead(b, off, len);698}699700/*701* We're not in the middle of a chunk so we must read ahead702* until there is some chunk data available.703*/704avail = readAhead(true);705if (avail < 0) {706return -1; /* EOF */707}708}709int cnt = (avail < len) ? avail : len;710System.arraycopy(chunkData, chunkPos, b, off, cnt);711chunkPos += cnt;712713return cnt;714} finally {715readLock.unlock();716}717}718719/**720* Returns the number of bytes that can be read from this input721* stream without blocking.722*723* @return the number of bytes that can be read from this input724* stream without blocking.725* @exception IOException if an I/O error occurs.726* @see java.io.FilterInputStream#in727*/728public int available() throws IOException {729readLock.lock();730try {731ensureOpen();732733int avail = chunkCount - chunkPos;734if (avail > 0) {735return avail;736}737738avail = readAhead(false);739740if (avail < 0) {741return 0;742} else {743return avail;744}745} finally {746readLock.unlock();747}748}749750/**751* Close the stream by either returning the connection to the752* keep alive cache or closing the underlying stream.753* <p>754* If the chunked response hasn't been completely read we755* try to "hurry" to the end of the response. If this is756* possible (without blocking) then the connection can be757* returned to the keep alive cache.758*759* @exception IOException if an I/O error occurs.760*/761public void close() throws IOException {762if (closed) return;763readLock.lock();764try {765if (closed) {766return;767}768closeUnderlying();769closed = true;770} finally {771readLock.unlock();772}773}774775/**776* Hurry the input stream by reading everything from the underlying777* stream. If the last chunk (and optional trailers) can be read without778* blocking then the stream is considered hurried.779* <p>780* Note that if an error has occurred or we can't get to last chunk781* without blocking then this stream can't be hurried and should be782* closed.783*/784public boolean hurry() {785readLock.lock();786try {787if (in == null || error) {788return false;789}790791try {792readAhead(false);793} catch (Exception e) {794return false;795}796797if (error) {798return false;799}800801return (state == STATE_DONE);802} finally {803readLock.unlock();804}805}806807}808809810