@@ -40,6 +40,8 @@ public class ChunkedInputStream extends InputStream {
4040
4141 private int bufferLength ;
4242
43+ private int chunkBytesRead ;
44+
4345 private int chunkBytesRemaining ;
4446
4547 private int chunkSize ;
@@ -52,119 +54,127 @@ public ChunkedInputStream(PushbackInputStream delegate, int bufferSize) {
5254 }
5355
5456 @ Override
55- public int read (byte [] b , int off , int len ) throws IOException {
56- return processChunk (b , off , len );
57- }
58-
59- @ Override
60- public int read () throws IOException {
61- var read = read (b1 );
62- if (read <= 0 ) {
63- return read ;
64- }
65-
66- return b1 [0 ] & 0xFF ;
67- }
68-
69- private int processChunk (byte [] destination , int offset , int length ) throws IOException {
70- int totalRead = 0 ;
71- while (totalRead < length ) {
72- // Bail early if the state machine is done
73- if (state == ChunkedBodyState .Complete ) {
74- // We need to push back any remaining bytes to the InputStream since we may have read more bytes than we needed.
75- int leftOver = bufferLength - bufferIndex ;
76- if (leftOver > 0 ) {
77- delegate .push (buffer , bufferIndex , leftOver );
78- }
79-
80- return -1 ;
57+ public int read (byte [] destination , int dOff , int dLen ) throws IOException {
58+ int dIndex = dOff ;
59+ while (dIndex < dLen ) {
60+ if (state == ChunkedInputStream .ChunkedBodyState .Complete ) {
61+ pushBackOverReadBytes ();
62+ break ;
8163 }
8264
8365 // Read some more if we are out of bytes
8466 if (bufferIndex >= bufferLength ) {
8567 bufferIndex = 0 ;
8668 bufferLength = delegate .read (buffer );
87- if (bufferLength > 0 ) {
88- totalRead += bufferLength ;
89- }
9069 }
9170
92- for (; bufferIndex < bufferLength ; bufferIndex ++) {
71+ // Process the buffer
72+ while (bufferIndex < bufferLength ) {
9373 ChunkedBodyState nextState ;
9474 try {
95- nextState = state .next (buffer [bufferIndex ], chunkSize , chunkSize - chunkBytesRemaining );
75+ nextState = state .next (buffer [bufferIndex ], chunkSize , chunkBytesRead );
9676 } catch (ParseException e ) {
9777 // This allows us to add the index to the exception. Useful for debugging.
9878 e .setIndex (bufferIndex );
9979 throw e ;
10080 }
10181
102- // We are DONE!
103- if (nextState == ChunkedBodyState .Complete ) {
82+ // We have reached the end of the encoded payload. Push back any additional bytes read.
83+ if (state == ChunkedBodyState .Complete ) {
10484 state = nextState ;
105- bufferIndex ++;
106- int leftOver = bufferLength - bufferIndex ;
107- if (leftOver > 0 ) {
108- delegate .push (buffer , bufferIndex , leftOver );
109- }
110-
111- return -1 ;
85+ pushBackOverReadBytes ();
86+ break ;
11287 }
11388
114- // Record the size hex digit
89+ // Capture the character to calculate the next chunk size
11590 if (nextState == ChunkedBodyState .ChunkSize ) {
11691 headerSizeHex .appendCodePoint (buffer [bufferIndex ]);
11792 state = nextState ;
93+ bufferIndex ++;
11894 continue ;
11995 }
12096
121- // Capture the chunk size
97+ // We have found the chunk, this means we can now convert the captured chunk size bytes and then try and read the chunk.
12298 if (state != ChunkedBodyState .Chunk && nextState == ChunkedBodyState .Chunk ) {
123- // This means we finished reading the size and are ready to start processing
12499 if (headerSizeHex .isEmpty ()) {
125100 throw new ChunkException ("Chunk size is missing" );
126101 }
127102
128103 // This is the start of a chunk, so set the size and counter and reset the size hex string
129104 chunkSize = (int ) Long .parseLong (headerSizeHex , 0 , headerSizeHex .length (), 16 );
130105
106+ chunkBytesRead = 0 ;
131107 chunkBytesRemaining = chunkSize ;
132108 headerSizeHex .delete (0 , headerSizeHex .length ());
133109
134- // AF\r1234 i=3 length=42 read=7 chunkSize=175(AF)
110+ // A chunk size of 0 indicates this is the terminating chunk. Continue and we will expect the state machine
111+ // to process the final CRLF and hit the Complete state.
135112 if (chunkSize == 0 ) {
136113 state = nextState ;
137- return 0 ;
114+ bufferIndex ++;
115+ continue ;
138116 }
139117 }
140118
141- int remainingInBuffer = bufferLength - bufferIndex ;
142- int lengthToCopy = Math .min (Math .min (chunkBytesRemaining , remainingInBuffer ), length ); // That's an ugly baby!
119+ int lengthToCopy ;
120+ if (chunkBytesRemaining > 0 ) {
121+ int remainingInBuffer = bufferLength - bufferIndex ;
122+ lengthToCopy = Math .min (Math .min (chunkBytesRemaining , remainingInBuffer ), dLen - dIndex ); // That's an ugly baby!
143123
144- // If we don't have anything to copy, continue.
145- if (lengthToCopy == 0 ) {
124+ // This means we don't have room in the destination buffer
125+ if (lengthToCopy == 0 ) {
126+ state = nextState ;
127+ bufferIndex ++;
128+ return dIndex - dOff ;
129+ }
130+ } else {
131+ // Nothing to do, continue to the next state.
146132 state = nextState ;
133+ bufferIndex ++;
147134 continue ;
148135 }
149136
150- // TODO : Daniel : If we wanted to handle more than one chunk at a time, I think we would potentially continue here
151- // and see if we can get more than one chunk completed before copying back to the destination.
152- // Discuss with Brian.
153- // Should we try this right now, or is this ok? Load testing with a small body,
154- // Chunked is slower than fixed length, I suppose this makes sense. In practice I don't think browsers
155- // and such use chunked for small requests.
156-
157- System .arraycopy (buffer , bufferIndex , destination , offset , lengthToCopy );
137+ // Copy 'lengthToCopy' to the destination buffer
138+ System .arraycopy (buffer , bufferIndex , destination , dIndex , lengthToCopy );
158139 bufferIndex += lengthToCopy ;
140+ chunkBytesRead += lengthToCopy ;
159141 chunkBytesRemaining -= lengthToCopy ;
142+ dIndex += lengthToCopy ;
160143 state = nextState ;
161- return lengthToCopy ;
144+
145+ // If we have bytes to copy, we are at the correct location in the state machine, If we don't have room, break.
146+ // - This will break the while loop, and return at the end of this method the total bytes we have written to the destination buffer.
147+ if (dIndex == dLen ) {
148+ break ;
149+ }
162150 }
163151 }
164152
165- return 0 ;
153+ int total = dIndex - dOff ;
154+ return total == 0 ? -1 : total ;
155+ }
156+
157+ @ Override
158+ public int read () throws IOException {
159+ var read = read (b1 );
160+ if (read <= 0 ) {
161+ return read ;
162+ }
163+
164+ return b1 [0 ] & 0xFF ;
165+ }
166+
167+ private void pushBackOverReadBytes () {
168+ int leftOver = bufferLength - bufferIndex ;
169+ if (leftOver > 0 ) {
170+ delegate .push (buffer , bufferIndex , leftOver );
171+
172+ // Move the pointer to the end of the buffer, We have used up the bytes by pushing them back.
173+ bufferIndex = bufferLength ;
174+ }
166175 }
167176
177+
168178 public enum ChunkedBodyState {
169179 ChunkExtensionStart {
170180 @ Override
@@ -266,17 +276,18 @@ public ChunkedBodyState next(byte ch, long length, long bytesRead) {
266276 return Chunk ;
267277 }
268278 },
269-
270279 Chunk {
271280 @ Override
272281 public ChunkedBodyState next (byte ch , long length , long bytesRead ) {
273- if (bytesRead < length ) {
274- return Chunk ;
282+ if (length == 0 ) {
283+ return Complete ;
275284 } else if (bytesRead == length && ch == '\r' ) {
276285 return ChunkCR ;
286+ } else if (bytesRead < length ) {
287+ return Chunk ;
288+ } else {
289+ throw makeParseException (ch , this );
277290 }
278-
279- throw makeParseException (ch , this );
280291 }
281292 },
282293
@@ -311,6 +322,6 @@ public ChunkedBodyState next(byte ch, long length, long bytesRead) {
311322 }
312323 };
313324
314- public abstract ChunkedBodyState next (byte ch , long length , long bytesRead );
325+ public abstract ChunkedInputStream . ChunkedBodyState next (byte ch , long length , long bytesRead );
315326 }
316327}
0 commit comments