4646import java .io .InputStream ;
4747import java .lang .annotation .Annotation ;
4848import java .lang .reflect .Type ;
49+ import java .util .ArrayList ;
4950import java .util .Arrays ;
5051import java .util .Collections ;
52+ import java .util .Comparator ;
53+ import java .util .List ;
5154import java .util .concurrent .atomic .AtomicBoolean ;
5255import java .util .logging .Level ;
5356import java .util .logging .Logger ;
@@ -107,34 +110,41 @@ public static ChunkParser createParser(final byte[] boundary) {
107110 return new FixedBoundaryParser (boundary );
108111 }
109112
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+ }
113123
114- public FixedBoundaryParser (final byte [] boundary ) {
115- delimiter = Arrays .copyOf (boundary , boundary .length );
116- }
124+ private abstract static class AbstractBoundaryParser implements ChunkParser {
117125
118126 @ Override
119127 public byte [] readChunk (final InputStream in ) throws IOException {
120128 final ByteArrayOutputStream buffer = new ByteArrayOutputStream ();
121- byte [] delimiterBuffer = new byte [delimiter . length ];
129+ byte [] delimiterBuffer = new byte [getDelimiterBufferSize () ];
122130
123131 int data ;
124132 int dPos ;
125133 do {
126134 dPos = 0 ;
127135 while ((data = in .read ()) != -1 ) {
128136 final byte b = (byte ) data ;
137+ byte [] delimiter = getDelimiter (b , dPos , delimiterBuffer );
129138
130139 // last read byte is part of the chunk delimiter
131- if (b == delimiter [dPos ]) {
140+ if (delimiter != null && b == delimiter [dPos ]) {
132141 delimiterBuffer [dPos ++] = b ;
133142 if (dPos == delimiter .length ) {
134143 // found chunk delimiter
135144 break ;
136145 }
137146 } else if (dPos > 0 ) {
147+ delimiter = getDelimiter (dPos - 1 , delimiterBuffer );
138148 delimiterBuffer [dPos ] = b ;
139149
140150 int matched = matchTail (delimiterBuffer , 1 , dPos , delimiter );
@@ -159,14 +169,44 @@ public byte[] readChunk(final InputStream in) throws IOException {
159169
160170 } while (data != -1 && buffer .size () == 0 ); // skip an empty chunk
161171
162- if (dPos > 0 && dPos != delimiter .length ) {
172+ if (dPos > 0 && dPos != getDelimiter ( dPos - 1 , delimiterBuffer ) .length ) {
163173 // flush the delimiter buffer, if not empty - parsing finished in the middle of a potential delimiter sequence
164174 buffer .write (delimiterBuffer , 0 , dPos );
165175 }
166176
167177 return (buffer .size () > 0 ) ? buffer .toByteArray () : null ;
168178 }
169179
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+
170210 /**
171211 * Tries to find an element intersection between two arrays in a way that intersecting elements must be
172212 * 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 {
192232 * any part of the head of the pattern, otherwise returns number of overlapping elements.
193233 */
194234 private static int matchTail (byte [] buffer , int offset , int length , byte [] pattern ) {
235+ if (pattern == null ) {
236+ return 0 ;
237+ }
238+
195239 outer :
196240 for (int i = 0 ; i < length ; i ++) {
197241 final int tailLength = length - i ;
@@ -209,6 +253,87 @@ private static int matchTail(byte[] buffer, int offset, int length, byte[] patte
209253 }
210254 }
211255
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+
212337 /**
213338 * Package-private constructor used by the {@link ChunkedInputReader}.
214339 *
0 commit comments