@@ -40,6 +40,8 @@ public class ChunkedInputStream extends InputStream {
40
40
41
41
private int bufferLength ;
42
42
43
+ private int chunkBytesRead ;
44
+
43
45
private int chunkBytesRemaining ;
44
46
45
47
private int chunkSize ;
@@ -52,119 +54,127 @@ public ChunkedInputStream(PushbackInputStream delegate, int bufferSize) {
52
54
}
53
55
54
56
@ 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 ;
81
63
}
82
64
83
65
// Read some more if we are out of bytes
84
66
if (bufferIndex >= bufferLength ) {
85
67
bufferIndex = 0 ;
86
68
bufferLength = delegate .read (buffer );
87
- if (bufferLength > 0 ) {
88
- totalRead += bufferLength ;
89
- }
90
69
}
91
70
92
- for (; bufferIndex < bufferLength ; bufferIndex ++) {
71
+ // Process the buffer
72
+ while (bufferIndex < bufferLength ) {
93
73
ChunkedBodyState nextState ;
94
74
try {
95
- nextState = state .next (buffer [bufferIndex ], chunkSize , chunkSize - chunkBytesRemaining );
75
+ nextState = state .next (buffer [bufferIndex ], chunkSize , chunkBytesRead );
96
76
} catch (ParseException e ) {
97
77
// This allows us to add the index to the exception. Useful for debugging.
98
78
e .setIndex (bufferIndex );
99
79
throw e ;
100
80
}
101
81
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 ) {
104
84
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 ;
112
87
}
113
88
114
- // Record the size hex digit
89
+ // Capture the character to calculate the next chunk size
115
90
if (nextState == ChunkedBodyState .ChunkSize ) {
116
91
headerSizeHex .appendCodePoint (buffer [bufferIndex ]);
117
92
state = nextState ;
93
+ bufferIndex ++;
118
94
continue ;
119
95
}
120
96
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.
122
98
if (state != ChunkedBodyState .Chunk && nextState == ChunkedBodyState .Chunk ) {
123
- // This means we finished reading the size and are ready to start processing
124
99
if (headerSizeHex .isEmpty ()) {
125
100
throw new ChunkException ("Chunk size is missing" );
126
101
}
127
102
128
103
// This is the start of a chunk, so set the size and counter and reset the size hex string
129
104
chunkSize = (int ) Long .parseLong (headerSizeHex , 0 , headerSizeHex .length (), 16 );
130
105
106
+ chunkBytesRead = 0 ;
131
107
chunkBytesRemaining = chunkSize ;
132
108
headerSizeHex .delete (0 , headerSizeHex .length ());
133
109
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.
135
112
if (chunkSize == 0 ) {
136
113
state = nextState ;
137
- return 0 ;
114
+ bufferIndex ++;
115
+ continue ;
138
116
}
139
117
}
140
118
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!
143
123
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.
146
132
state = nextState ;
133
+ bufferIndex ++;
147
134
continue ;
148
135
}
149
136
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 );
158
139
bufferIndex += lengthToCopy ;
140
+ chunkBytesRead += lengthToCopy ;
159
141
chunkBytesRemaining -= lengthToCopy ;
142
+ dIndex += lengthToCopy ;
160
143
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
+ }
162
150
}
163
151
}
164
152
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
+ }
166
175
}
167
176
177
+
168
178
public enum ChunkedBodyState {
169
179
ChunkExtensionStart {
170
180
@ Override
@@ -266,17 +276,18 @@ public ChunkedBodyState next(byte ch, long length, long bytesRead) {
266
276
return Chunk ;
267
277
}
268
278
},
269
-
270
279
Chunk {
271
280
@ Override
272
281
public ChunkedBodyState next (byte ch , long length , long bytesRead ) {
273
- if (bytesRead < length ) {
274
- return Chunk ;
282
+ if (length == 0 ) {
283
+ return Complete ;
275
284
} else if (bytesRead == length && ch == '\r' ) {
276
285
return ChunkCR ;
286
+ } else if (bytesRead < length ) {
287
+ return Chunk ;
288
+ } else {
289
+ throw makeParseException (ch , this );
277
290
}
278
-
279
- throw makeParseException (ch , this );
280
291
}
281
292
},
282
293
@@ -311,6 +322,6 @@ public ChunkedBodyState next(byte ch, long length, long bytesRead) {
311
322
}
312
323
};
313
324
314
- public abstract ChunkedBodyState next (byte ch , long length , long bytesRead );
325
+ public abstract ChunkedInputStream . ChunkedBodyState next (byte ch , long length , long bytesRead );
315
326
}
316
327
}
0 commit comments