46
46
import java .io .InputStream ;
47
47
import java .lang .annotation .Annotation ;
48
48
import java .lang .reflect .Type ;
49
+ import java .util .ArrayList ;
49
50
import java .util .Arrays ;
50
51
import java .util .Collections ;
52
+ import java .util .Comparator ;
53
+ import java .util .List ;
51
54
import java .util .concurrent .atomic .AtomicBoolean ;
52
55
import java .util .logging .Level ;
53
56
import java .util .logging .Logger ;
@@ -107,34 +110,41 @@ public static ChunkParser createParser(final byte[] boundary) {
107
110
return new FixedBoundaryParser (boundary );
108
111
}
109
112
110
- private static class FixedBoundaryParser implements ChunkParser {
111
-
112
- private final byte [] delimiter ;
113
+ /**
114
+ * Create a new chunk multi-parser that will split the response entity input stream
115
+ * based on multiple fixed boundary strings.
116
+ *
117
+ * @param boundaries chunk boundaries.
118
+ * @return new fixed boundary string-based chunk parser.
119
+ */
120
+ public static ChunkParser createMultiParser (final String ... boundaries ) {
121
+ return new FixedMultiBoundaryParser (boundaries );
122
+ }
113
123
114
- public FixedBoundaryParser (final byte [] boundary ) {
115
- delimiter = Arrays .copyOf (boundary , boundary .length );
116
- }
124
+ private abstract static class AbstractBoundaryParser implements ChunkParser {
117
125
118
126
@ Override
119
127
public byte [] readChunk (final InputStream in ) throws IOException {
120
128
final ByteArrayOutputStream buffer = new ByteArrayOutputStream ();
121
- byte [] delimiterBuffer = new byte [delimiter . length ];
129
+ byte [] delimiterBuffer = new byte [getDelimiterBufferSize () ];
122
130
123
131
int data ;
124
132
int dPos ;
125
133
do {
126
134
dPos = 0 ;
127
135
while ((data = in .read ()) != -1 ) {
128
136
final byte b = (byte ) data ;
137
+ byte [] delimiter = getDelimiter (b , dPos , delimiterBuffer );
129
138
130
139
// last read byte is part of the chunk delimiter
131
- if (b == delimiter [dPos ]) {
140
+ if (delimiter != null && b == delimiter [dPos ]) {
132
141
delimiterBuffer [dPos ++] = b ;
133
142
if (dPos == delimiter .length ) {
134
143
// found chunk delimiter
135
144
break ;
136
145
}
137
146
} else if (dPos > 0 ) {
147
+ delimiter = getDelimiter (dPos - 1 , delimiterBuffer );
138
148
delimiterBuffer [dPos ] = b ;
139
149
140
150
int matched = matchTail (delimiterBuffer , 1 , dPos , delimiter );
@@ -159,14 +169,44 @@ public byte[] readChunk(final InputStream in) throws IOException {
159
169
160
170
} while (data != -1 && buffer .size () == 0 ); // skip an empty chunk
161
171
162
- if (dPos > 0 && dPos != delimiter .length ) {
172
+ if (dPos > 0 && dPos != getDelimiter ( dPos - 1 , delimiterBuffer ) .length ) {
163
173
// flush the delimiter buffer, if not empty - parsing finished in the middle of a potential delimiter sequence
164
174
buffer .write (delimiterBuffer , 0 , dPos );
165
175
}
166
176
167
177
return (buffer .size () > 0 ) ? buffer .toByteArray () : null ;
168
178
}
169
179
180
+ /**
181
+ * Selects a delimiter which corresponds to delimiter buffer. Method automatically appends {@code b} param on the
182
+ * {@code pos} position of {@code delimiterBuffer} array and then starts the selection process with a newly created array.
183
+ *
184
+ * @param b byte which will be added on the {@code pos} position of {@code delimiterBuffer} array
185
+ * @param pos number of bytes from the delimiter buffer which will be used in processing
186
+ * @param delimiterBuffer current content of the delimiter buffer
187
+ * @return delimiter which corresponds to delimiterBuffer
188
+ */
189
+ abstract byte [] getDelimiter (byte b , int pos , byte [] delimiterBuffer );
190
+
191
+ /**
192
+ * Selects a delimiter which corresponds to delimiter buffer.
193
+ *
194
+ * @param pos position of the last read byte
195
+ * @param delimiterBuffer number of bytes from the delimiter buffer which will be used in processing
196
+ * @return delimiter which corresponds to delimiterBuffer
197
+ */
198
+ abstract byte [] getDelimiter (int pos , byte [] delimiterBuffer );
199
+
200
+ /**
201
+ * Returns a delimiter buffer size depending on the selected strategy.
202
+ * <p>
203
+ * If a strategy has multiple registered delimiters, then the delimiter buffer should be a length of the longest
204
+ * delimiter.
205
+ *
206
+ * @return length of the delimiter buffer
207
+ */
208
+ abstract int getDelimiterBufferSize ();
209
+
170
210
/**
171
211
* Tries to find an element intersection between two arrays in a way that intersecting elements must be
172
212
* at the tail of the first array and at the beginning of the second array.
@@ -192,6 +232,10 @@ public byte[] readChunk(final InputStream in) throws IOException {
192
232
* any part of the head of the pattern, otherwise returns number of overlapping elements.
193
233
*/
194
234
private static int matchTail (byte [] buffer , int offset , int length , byte [] pattern ) {
235
+ if (pattern == null ) {
236
+ return 0 ;
237
+ }
238
+
195
239
outer :
196
240
for (int i = 0 ; i < length ; i ++) {
197
241
final int tailLength = length - i ;
@@ -209,6 +253,87 @@ private static int matchTail(byte[] buffer, int offset, int length, byte[] patte
209
253
}
210
254
}
211
255
256
+ private static class FixedBoundaryParser extends AbstractBoundaryParser {
257
+
258
+ private final byte [] delimiter ;
259
+
260
+ public FixedBoundaryParser (final byte [] boundary ) {
261
+ delimiter = Arrays .copyOf (boundary , boundary .length );
262
+ }
263
+
264
+ @ Override
265
+ byte [] getDelimiter (byte b , int pos , byte [] delimiterBuffer ) {
266
+ return delimiter ;
267
+ }
268
+
269
+ @ Override
270
+ byte [] getDelimiter (int pos , byte [] delimiterBuffer ) {
271
+ return delimiter ;
272
+ }
273
+
274
+ @ Override
275
+ int getDelimiterBufferSize () {
276
+ return delimiter .length ;
277
+ }
278
+ }
279
+
280
+ private static class FixedMultiBoundaryParser extends AbstractBoundaryParser {
281
+
282
+ private final List <byte []> delimiters = new ArrayList <byte []>();
283
+
284
+ private final int longestDelimiterLength ;
285
+
286
+ public FixedMultiBoundaryParser (String ... boundaries ) {
287
+ for (String boundary : boundaries ) {
288
+ byte [] boundaryBytes = boundary .getBytes ();
289
+ delimiters .add (Arrays .copyOf (boundaryBytes , boundaryBytes .length ));
290
+ }
291
+
292
+ Collections .sort (delimiters , new Comparator <byte []>() {
293
+ @ Override
294
+ public int compare (byte [] o1 , byte [] o2 ) {
295
+ return Integer .compare (o1 .length , o2 .length );
296
+ }
297
+ });
298
+
299
+ byte [] longestDelimiter = delimiters .get (delimiters .size () - 1 );
300
+ this .longestDelimiterLength = longestDelimiter .length ;
301
+ }
302
+
303
+ @ Override
304
+ byte [] getDelimiter (byte b , int pos , byte [] delimiterBuffer ) {
305
+ byte [] buffer = Arrays .copyOf (delimiterBuffer , delimiterBuffer .length );
306
+ buffer [pos ] = b ;
307
+
308
+ return getDelimiter (pos , buffer );
309
+ }
310
+
311
+ @ Override
312
+ byte [] getDelimiter (int pos , byte [] delimiterBuffer ) {
313
+ outer :
314
+ for (byte [] delimiter : delimiters ) {
315
+ if (pos > delimiter .length ) {
316
+ continue ;
317
+ }
318
+
319
+ for (int i = 0 ; i <= pos && i < delimiter .length ; i ++) {
320
+ if (delimiter [i ] != delimiterBuffer [i ]) {
321
+ continue outer ;
322
+ } else if (pos == i ) {
323
+ return delimiter ;
324
+ }
325
+ }
326
+ }
327
+
328
+ return null ;
329
+ }
330
+
331
+ @ Override
332
+ int getDelimiterBufferSize () {
333
+ return this .longestDelimiterLength ;
334
+ }
335
+ }
336
+
212
337
/**
213
338
* Package-private constructor used by the {@link ChunkedInputReader}.
214
339
*
0 commit comments