View Javadoc
1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http.multipart;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.handler.codec.http.HttpConstants;
20  import io.netty.handler.codec.http.HttpContent;
21  import io.netty.handler.codec.http.HttpHeaders;
22  import io.netty.handler.codec.http.HttpRequest;
23  import io.netty.handler.codec.http.LastHttpContent;
24  import io.netty.handler.codec.http.QueryStringDecoder;
25  import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
26  import io.netty.handler.codec.http.multipart.HttpPostBodyUtil.TransferEncodingMechanism;
27  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
28  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
29  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.MultiPartStatus;
30  import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException;
31  import io.netty.util.internal.InternalThreadLocalMap;
32  import io.netty.util.internal.StringUtil;
33  
34  import java.io.IOException;
35  import java.nio.charset.Charset;
36  import java.nio.charset.UnsupportedCharsetException;
37  import java.util.ArrayList;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.TreeMap;
41  
42  import static io.netty.buffer.Unpooled.*;
43  import static io.netty.util.internal.ObjectUtil.*;
44  
45  /**
46   * This decoder will decode Body and can handle POST BODY.
47   *
48   * You <strong>MUST</strong> call {@link #destroy()} after completion to release all resources.
49   *
50   */
51  public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequestDecoder {
52  
53      /**
54       * Factory used to create InterfaceHttpData
55       */
56      private final HttpDataFactory factory;
57  
58      /**
59       * Request to decode
60       */
61      private final HttpRequest request;
62  
63      /**
64       * Default charset to use
65       */
66      private Charset charset;
67  
68      /**
69       * Does the last chunk already received
70       */
71      private boolean isLastChunk;
72  
73      /**
74       * HttpDatas from Body
75       */
76      private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
77  
78      /**
79       * HttpDatas as Map from Body
80       */
81      private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
82              CaseIgnoringComparator.INSTANCE);
83  
84      /**
85       * The current channelBuffer
86       */
87      private ByteBuf undecodedChunk;
88  
89      /**
90       * Body HttpDatas current position
91       */
92      private int bodyListHttpDataRank;
93  
94      /**
95       * If multipart, this is the boundary for the global multipart
96       */
97      private String multipartDataBoundary;
98  
99      /**
100      * If multipart, there could be internal multiparts (mixed) to the global
101      * multipart. Only one level is allowed.
102      */
103     private String multipartMixedBoundary;
104 
105     /**
106      * Current getStatus
107      */
108     private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
109 
110     /**
111      * Used in Multipart
112      */
113     private Map<CharSequence, Attribute> currentFieldAttributes;
114 
115     /**
116      * The current FileUpload that is currently in decode process
117      */
118     private FileUpload currentFileUpload;
119 
120     /**
121      * The current Attribute that is currently in decode process
122      */
123     private Attribute currentAttribute;
124 
125     private boolean destroyed;
126 
127     private int discardThreshold = HttpPostRequestDecoder.DEFAULT_DISCARD_THRESHOLD;
128 
129     /**
130      *
131      * @param request
132      *            the request to decode
133      * @throws NullPointerException
134      *             for request
135      * @throws ErrorDataDecoderException
136      *             if the default charset was wrong when decoding or other
137      *             errors
138      */
139     public HttpPostMultipartRequestDecoder(HttpRequest request) {
140         this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE), request, HttpConstants.DEFAULT_CHARSET);
141     }
142 
143     /**
144      *
145      * @param factory
146      *            the factory used to create InterfaceHttpData
147      * @param request
148      *            the request to decode
149      * @throws NullPointerException
150      *             for request or factory
151      * @throws ErrorDataDecoderException
152      *             if the default charset was wrong when decoding or other
153      *             errors
154      */
155     public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request) {
156         this(factory, request, HttpConstants.DEFAULT_CHARSET);
157     }
158 
159     /**
160      *
161      * @param factory
162      *            the factory used to create InterfaceHttpData
163      * @param request
164      *            the request to decode
165      * @param charset
166      *            the charset to use as default
167      * @throws NullPointerException
168      *             for request or charset or factory
169      * @throws ErrorDataDecoderException
170      *             if the default charset was wrong when decoding or other
171      *             errors
172      */
173     public HttpPostMultipartRequestDecoder(HttpDataFactory factory, HttpRequest request, Charset charset) {
174         this.request = checkNotNull(request, "request");
175         this.charset = checkNotNull(charset, "charset");
176         this.factory = checkNotNull(factory, "factory");
177         // Fill default values
178 
179         setMultipart(this.request.headers().get(HttpHeaders.Names.CONTENT_TYPE));
180         if (request instanceof HttpContent) {
181             // Offer automatically if the given request is als type of HttpContent
182             // See #1089
183             offer((HttpContent) request);
184         } else {
185             undecodedChunk = buffer();
186             parseBody();
187         }
188     }
189 
190     /**
191      * Set from the request ContentType the multipartDataBoundary and the possible charset.
192      */
193     private void setMultipart(String contentType) {
194         String[] dataBoundary = HttpPostRequestDecoder.getMultipartDataBoundary(contentType);
195         if (dataBoundary != null) {
196             multipartDataBoundary = dataBoundary[0];
197             if (dataBoundary.length > 1 && dataBoundary[1] != null) {
198                 charset = Charset.forName(dataBoundary[1]);
199             }
200         } else {
201             multipartDataBoundary = null;
202         }
203         currentStatus = MultiPartStatus.HEADERDELIMITER;
204     }
205 
206     private void checkDestroyed() {
207         if (destroyed) {
208             throw new IllegalStateException(HttpPostMultipartRequestDecoder.class.getSimpleName()
209                     + " was destroyed already");
210         }
211     }
212 
213     /**
214      * True if this request is a Multipart request
215      *
216      * @return True if this request is a Multipart request
217      */
218     @Override
219     public boolean isMultipart() {
220         checkDestroyed();
221         return true;
222     }
223 
224     /**
225      * Set the amount of bytes after which read bytes in the buffer should be discarded.
226      * Setting this lower gives lower memory usage but with the overhead of more memory copies.
227      * Use {@code 0} to disable it.
228      */
229     @Override
230     public void setDiscardThreshold(int discardThreshold) {
231         this.discardThreshold = checkPositiveOrZero(discardThreshold, "discardThreshold");
232     }
233 
234     /**
235      * Return the threshold in bytes after which read data in the buffer should be discarded.
236      */
237     @Override
238     public int getDiscardThreshold() {
239         return discardThreshold;
240     }
241 
242     /**
243      * This getMethod returns a List of all HttpDatas from body.<br>
244      *
245      * If chunked, all chunks must have been offered using offer() getMethod. If
246      * not, NotEnoughDataDecoderException will be raised.
247      *
248      * @return the list of HttpDatas from Body part for POST getMethod
249      * @throws NotEnoughDataDecoderException
250      *             Need more chunks
251      */
252     @Override
253     public List<InterfaceHttpData> getBodyHttpDatas() {
254         checkDestroyed();
255 
256         if (!isLastChunk) {
257             throw new NotEnoughDataDecoderException();
258         }
259         return bodyListHttpData;
260     }
261 
262     /**
263      * This getMethod returns a List of all HttpDatas with the given name from
264      * body.<br>
265      *
266      * If chunked, all chunks must have been offered using offer() getMethod. If
267      * not, NotEnoughDataDecoderException will be raised.
268      *
269      * @return All Body HttpDatas with the given name (ignore case)
270      * @throws NotEnoughDataDecoderException
271      *             need more chunks
272      */
273     @Override
274     public List<InterfaceHttpData> getBodyHttpDatas(String name) {
275         checkDestroyed();
276 
277         if (!isLastChunk) {
278             throw new NotEnoughDataDecoderException();
279         }
280         return bodyMapHttpData.get(name);
281     }
282 
283     /**
284      * This getMethod returns the first InterfaceHttpData with the given name from
285      * body.<br>
286      *
287      * If chunked, all chunks must have been offered using offer() getMethod. If
288      * not, NotEnoughDataDecoderException will be raised.
289      *
290      * @return The first Body InterfaceHttpData with the given name (ignore
291      *         case)
292      * @throws NotEnoughDataDecoderException
293      *             need more chunks
294      */
295     @Override
296     public InterfaceHttpData getBodyHttpData(String name) {
297         checkDestroyed();
298 
299         if (!isLastChunk) {
300             throw new NotEnoughDataDecoderException();
301         }
302         List<InterfaceHttpData> list = bodyMapHttpData.get(name);
303         if (list != null) {
304             return list.get(0);
305         }
306         return null;
307     }
308 
309     /**
310      * Initialized the internals from a new chunk
311      *
312      * @param content
313      *            the new received chunk
314      * @throws ErrorDataDecoderException
315      *             if there is a problem with the charset decoding or other
316      *             errors
317      */
318     @Override
319     public HttpPostMultipartRequestDecoder offer(HttpContent content) {
320         checkDestroyed();
321 
322         // Maybe we should better not copy here for performance reasons but this will need
323         // more care by the caller to release the content in a correct manner later
324         // So maybe something to optimize on a later stage
325         ByteBuf buf = content.content();
326         if (undecodedChunk == null) {
327             undecodedChunk = buf.copy();
328         } else {
329             undecodedChunk.writeBytes(buf);
330         }
331         if (content instanceof LastHttpContent) {
332             isLastChunk = true;
333         }
334         parseBody();
335         if (undecodedChunk != null && undecodedChunk.writerIndex() > discardThreshold) {
336             undecodedChunk.discardReadBytes();
337         }
338         return this;
339     }
340 
341     /**
342      * True if at current getStatus, there is an available decoded
343      * InterfaceHttpData from the Body.
344      *
345      * This getMethod works for chunked and not chunked request.
346      *
347      * @return True if at current getStatus, there is a decoded InterfaceHttpData
348      * @throws EndOfDataDecoderException
349      *             No more data will be available
350      */
351     @Override
352     public boolean hasNext() {
353         checkDestroyed();
354 
355         if (currentStatus == MultiPartStatus.EPILOGUE) {
356             // OK except if end of list
357             if (bodyListHttpDataRank >= bodyListHttpData.size()) {
358                 throw new EndOfDataDecoderException();
359             }
360         }
361         return !bodyListHttpData.isEmpty() && bodyListHttpDataRank < bodyListHttpData.size();
362     }
363 
364     /**
365      * Returns the next available InterfaceHttpData or null if, at the time it
366      * is called, there is no more available InterfaceHttpData. A subsequent
367      * call to offer(httpChunk) could enable more data.
368      *
369      * Be sure to call {@link InterfaceHttpData#release()} after you are done
370      * with processing to make sure to not leak any resources
371      *
372      * @return the next available InterfaceHttpData or null if none
373      * @throws EndOfDataDecoderException
374      *             No more data will be available
375      */
376     @Override
377     public InterfaceHttpData next() {
378         checkDestroyed();
379 
380         if (hasNext()) {
381             return bodyListHttpData.get(bodyListHttpDataRank++);
382         }
383         return null;
384     }
385 
386     /**
387      * This getMethod will parse as much as possible data and fill the list and map
388      *
389      * @throws ErrorDataDecoderException
390      *             if there is a problem with the charset decoding or other
391      *             errors
392      */
393     private void parseBody() {
394         if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
395             if (isLastChunk) {
396                 currentStatus = MultiPartStatus.EPILOGUE;
397             }
398             return;
399         }
400         parseBodyMultipart();
401     }
402 
403     /**
404      * Utility function to add a new decoded data
405      */
406     protected void addHttpData(InterfaceHttpData data) {
407         if (data == null) {
408             return;
409         }
410         List<InterfaceHttpData> datas = bodyMapHttpData.get(data.getName());
411         if (datas == null) {
412             datas = new ArrayList<InterfaceHttpData>(1);
413             bodyMapHttpData.put(data.getName(), datas);
414         }
415         datas.add(data);
416         bodyListHttpData.add(data);
417     }
418 
419     /**
420      * Parse the Body for multipart
421      *
422      * @throws ErrorDataDecoderException
423      *             if there is a problem with the charset decoding or other
424      *             errors
425      */
426     private void parseBodyMultipart() {
427         if (undecodedChunk == null || undecodedChunk.readableBytes() == 0) {
428             // nothing to decode
429             return;
430         }
431         InterfaceHttpData data = decodeMultipart(currentStatus);
432         while (data != null) {
433             addHttpData(data);
434             if (currentStatus == MultiPartStatus.PREEPILOGUE || currentStatus == MultiPartStatus.EPILOGUE) {
435                 break;
436             }
437             data = decodeMultipart(currentStatus);
438         }
439     }
440 
441     /**
442      * Decode a multipart request by pieces<br>
443      * <br>
444      * NOTSTARTED PREAMBLE (<br>
445      * (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))*<br>
446      * (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE<br>
447      * (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+<br>
448      * MIXEDCLOSEDELIMITER)*<br>
449      * CLOSEDELIMITER)+ EPILOGUE<br>
450      *
451      * Inspired from HttpMessageDecoder
452      *
453      * @return the next decoded InterfaceHttpData or null if none until now.
454      * @throws ErrorDataDecoderException
455      *             if an error occurs
456      */
457     private InterfaceHttpData decodeMultipart(MultiPartStatus state) {
458         switch (state) {
459         case NOTSTARTED:
460             throw new ErrorDataDecoderException("Should not be called with the current getStatus");
461         case PREAMBLE:
462             // Content-type: multipart/form-data, boundary=AaB03x
463             throw new ErrorDataDecoderException("Should not be called with the current getStatus");
464         case HEADERDELIMITER: {
465             // --AaB03x or --AaB03x--
466             return findMultipartDelimiter(multipartDataBoundary, MultiPartStatus.DISPOSITION,
467                     MultiPartStatus.PREEPILOGUE);
468         }
469         case DISPOSITION: {
470             // content-disposition: form-data; name="field1"
471             // content-disposition: form-data; name="pics"; filename="file1.txt"
472             // and other immediate values like
473             // Content-type: image/gif
474             // Content-Type: text/plain
475             // Content-Type: text/plain; charset=ISO-8859-1
476             // Content-Transfer-Encoding: binary
477             // The following line implies a change of mode (mixed mode)
478             // Content-type: multipart/mixed, boundary=BbC04y
479             return findMultipartDisposition();
480         }
481         case FIELD: {
482             // Now get value according to Content-Type and Charset
483             Charset localCharset = null;
484             Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaders.Values.CHARSET);
485             if (charsetAttribute != null) {
486                 try {
487                     localCharset = Charset.forName(charsetAttribute.getValue());
488                 } catch (IOException e) {
489                     throw new ErrorDataDecoderException(e);
490                 } catch (UnsupportedCharsetException e) {
491                     throw new ErrorDataDecoderException(e);
492                 }
493             }
494             Attribute nameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.NAME);
495             if (currentAttribute == null) {
496                 try {
497                     currentAttribute = factory.createAttribute(request,
498                             cleanString(nameAttribute.getValue()));
499                 } catch (NullPointerException e) {
500                     throw new ErrorDataDecoderException(e);
501                 } catch (IllegalArgumentException e) {
502                     throw new ErrorDataDecoderException(e);
503                 } catch (IOException e) {
504                     throw new ErrorDataDecoderException(e);
505                 }
506                 if (localCharset != null) {
507                     currentAttribute.setCharset(localCharset);
508                 }
509             }
510             // load data
511             try {
512                 loadFieldMultipart(undecodedChunk, multipartDataBoundary, currentAttribute);
513             } catch (NotEnoughDataDecoderException ignored) {
514                 return null;
515             }
516             Attribute finalAttribute = currentAttribute;
517             currentAttribute = null;
518             currentFieldAttributes = null;
519             // ready to load the next one
520             currentStatus = MultiPartStatus.HEADERDELIMITER;
521             return finalAttribute;
522         }
523         case FILEUPLOAD: {
524             // eventually restart from existing FileUpload
525             return getFileUpload(multipartDataBoundary);
526         }
527         case MIXEDDELIMITER: {
528             // --AaB03x or --AaB03x--
529             // Note that currentFieldAttributes exists
530             return findMultipartDelimiter(multipartMixedBoundary, MultiPartStatus.MIXEDDISPOSITION,
531                     MultiPartStatus.HEADERDELIMITER);
532         }
533         case MIXEDDISPOSITION: {
534             return findMultipartDisposition();
535         }
536         case MIXEDFILEUPLOAD: {
537             // eventually restart from existing FileUpload
538             return getFileUpload(multipartMixedBoundary);
539         }
540         case PREEPILOGUE:
541             return null;
542         case EPILOGUE:
543             return null;
544         default:
545             throw new ErrorDataDecoderException("Shouldn't reach here.");
546         }
547     }
548 
549     /**
550      * Skip control Characters
551      *
552      * @throws NotEnoughDataDecoderException
553      */
554     private static void skipControlCharacters(ByteBuf undecodedChunk) {
555         if (!undecodedChunk.hasArray()) {
556             try {
557                 skipControlCharactersStandard(undecodedChunk);
558             } catch (IndexOutOfBoundsException e1) {
559                 throw new NotEnoughDataDecoderException(e1);
560             }
561             return;
562         }
563         SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
564         while (sao.pos < sao.limit) {
565             char c = (char) (sao.bytes[sao.pos++] & 0xFF);
566             if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
567                 sao.setReadPosition(1);
568                 return;
569             }
570         }
571         throw new NotEnoughDataDecoderException("Access out of bounds");
572     }
573 
574     private static void skipControlCharactersStandard(ByteBuf undecodedChunk) {
575         for (;;) {
576             char c = (char) undecodedChunk.readUnsignedByte();
577             if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
578                 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
579                 break;
580             }
581         }
582     }
583 
584     /**
585      * Find the next Multipart Delimiter
586      *
587      * @param delimiter
588      *            delimiter to find
589      * @param dispositionStatus
590      *            the next getStatus if the delimiter is a start
591      * @param closeDelimiterStatus
592      *            the next getStatus if the delimiter is a close delimiter
593      * @return the next InterfaceHttpData if any
594      * @throws ErrorDataDecoderException
595      */
596     private InterfaceHttpData findMultipartDelimiter(String delimiter, MultiPartStatus dispositionStatus,
597             MultiPartStatus closeDelimiterStatus) {
598         // --AaB03x or --AaB03x--
599         int readerIndex = undecodedChunk.readerIndex();
600         try {
601             skipControlCharacters(undecodedChunk);
602         } catch (NotEnoughDataDecoderException ignored) {
603             undecodedChunk.readerIndex(readerIndex);
604             return null;
605         }
606         skipOneLine();
607         String newline;
608         try {
609             newline = readDelimiter(undecodedChunk, delimiter);
610         } catch (NotEnoughDataDecoderException ignored) {
611             undecodedChunk.readerIndex(readerIndex);
612             return null;
613         }
614         if (newline.equals(delimiter)) {
615             currentStatus = dispositionStatus;
616             return decodeMultipart(dispositionStatus);
617         }
618         if (newline.equals(delimiter + "--")) {
619             // CLOSEDELIMITER or MIXED CLOSEDELIMITER found
620             currentStatus = closeDelimiterStatus;
621             if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
622                 // MIXEDCLOSEDELIMITER
623                 // end of the Mixed part
624                 currentFieldAttributes = null;
625                 return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
626             }
627             return null;
628         }
629         undecodedChunk.readerIndex(readerIndex);
630         throw new ErrorDataDecoderException("No Multipart delimiter found");
631     }
632 
633     /**
634      * Find the next Disposition
635      *
636      * @return the next InterfaceHttpData if any
637      * @throws ErrorDataDecoderException
638      */
639     private InterfaceHttpData findMultipartDisposition() {
640         int readerIndex = undecodedChunk.readerIndex();
641         if (currentStatus == MultiPartStatus.DISPOSITION) {
642             currentFieldAttributes = new TreeMap<CharSequence, Attribute>(CaseIgnoringComparator.INSTANCE);
643         }
644         // read many lines until empty line with newline found! Store all data
645         while (!skipOneLine()) {
646             String newline;
647             try {
648                 skipControlCharacters(undecodedChunk);
649                 newline = readLine(undecodedChunk, charset);
650             } catch (NotEnoughDataDecoderException ignored) {
651                 undecodedChunk.readerIndex(readerIndex);
652                 return null;
653             }
654             String[] contents = splitMultipartHeader(newline);
655             if (contents[0].equalsIgnoreCase(HttpPostBodyUtil.CONTENT_DISPOSITION)) {
656                 boolean checkSecondArg;
657                 if (currentStatus == MultiPartStatus.DISPOSITION) {
658                     checkSecondArg = contents[1].equalsIgnoreCase(HttpPostBodyUtil.FORM_DATA);
659                 } else {
660                     checkSecondArg = contents[1].equalsIgnoreCase(HttpPostBodyUtil.ATTACHMENT)
661                             || contents[1].equalsIgnoreCase(HttpPostBodyUtil.FILE);
662                 }
663                 if (checkSecondArg) {
664                     // read next values and store them in the map as Attribute
665                     for (int i = 2; i < contents.length; i++) {
666                         String[] values = contents[i].split("=", 2);
667                         Attribute attribute;
668                         try {
669                             attribute = getContentDispositionAttribute(values);
670                         } catch (NullPointerException e) {
671                             throw new ErrorDataDecoderException(e);
672                         } catch (IllegalArgumentException e) {
673                             throw new ErrorDataDecoderException(e);
674                         }
675                         currentFieldAttributes.put(attribute.getName(), attribute);
676                     }
677                 }
678             } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) {
679                 Attribute attribute;
680                 try {
681                     attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_TRANSFER_ENCODING,
682                             cleanString(contents[1]));
683                 } catch (NullPointerException e) {
684                     throw new ErrorDataDecoderException(e);
685                 } catch (IllegalArgumentException e) {
686                     throw new ErrorDataDecoderException(e);
687                 }
688                 currentFieldAttributes.put(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, attribute);
689             } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) {
690                 Attribute attribute;
691                 try {
692                     attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_LENGTH,
693                             cleanString(contents[1]));
694                 } catch (NullPointerException e) {
695                     throw new ErrorDataDecoderException(e);
696                 } catch (IllegalArgumentException e) {
697                     throw new ErrorDataDecoderException(e);
698                 }
699                 currentFieldAttributes.put(HttpHeaders.Names.CONTENT_LENGTH, attribute);
700             } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) {
701                 // Take care of possible "multipart/mixed"
702                 if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) {
703                     if (currentStatus == MultiPartStatus.DISPOSITION) {
704                         String values = StringUtil.substringAfter(contents[2], '=');
705                         multipartMixedBoundary = "--" + values;
706                         currentStatus = MultiPartStatus.MIXEDDELIMITER;
707                         return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
708                     } else {
709                         throw new ErrorDataDecoderException("Mixed Multipart found in a previous Mixed Multipart");
710                     }
711                 } else {
712                     for (int i = 1; i < contents.length; i++) {
713                         final String charsetHeader = HttpHeaders.Values.CHARSET;
714                         if (contents[i].regionMatches(true, 0, charsetHeader, 0, charsetHeader.length())) {
715                             String values = StringUtil.substringAfter(contents[i], '=');
716                             Attribute attribute;
717                             try {
718                                 attribute = factory.createAttribute(request, charsetHeader, cleanString(values));
719                             } catch (NullPointerException e) {
720                                 throw new ErrorDataDecoderException(e);
721                             } catch (IllegalArgumentException e) {
722                                 throw new ErrorDataDecoderException(e);
723                             }
724                             currentFieldAttributes.put(HttpHeaders.Values.CHARSET, attribute);
725                         } else {
726                             Attribute attribute;
727                             try {
728                                 attribute = factory.createAttribute(request,
729                                         cleanString(contents[0]), contents[i]);
730                             } catch (NullPointerException e) {
731                                 throw new ErrorDataDecoderException(e);
732                             } catch (IllegalArgumentException e) {
733                                 throw new ErrorDataDecoderException(e);
734                             }
735                             currentFieldAttributes.put(attribute.getName(), attribute);
736                         }
737                     }
738                 }
739             } else {
740                 throw new ErrorDataDecoderException("Unknown Params: " + newline);
741             }
742         }
743         // Is it a FileUpload
744         Attribute filenameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.FILENAME);
745         if (currentStatus == MultiPartStatus.DISPOSITION) {
746             if (filenameAttribute != null) {
747                 // FileUpload
748                 currentStatus = MultiPartStatus.FILEUPLOAD;
749                 // do not change the buffer position
750                 return decodeMultipart(MultiPartStatus.FILEUPLOAD);
751             } else {
752                 // Field
753                 currentStatus = MultiPartStatus.FIELD;
754                 // do not change the buffer position
755                 return decodeMultipart(MultiPartStatus.FIELD);
756             }
757         } else {
758             if (filenameAttribute != null) {
759                 // FileUpload
760                 currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
761                 // do not change the buffer position
762                 return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
763             } else {
764                 // Field is not supported in MIXED mode
765                 throw new ErrorDataDecoderException("Filename not found");
766             }
767         }
768     }
769 
770     private static final String FILENAME_ENCODED = HttpPostBodyUtil.FILENAME + '*';
771 
772     private Attribute getContentDispositionAttribute(String... values) {
773         String name = cleanString(values[0]);
774         String value = values[1];
775 
776         // Filename can be token, quoted or encoded. See https://tools.ietf.org/html/rfc5987
777         if (HttpPostBodyUtil.FILENAME.contentEquals(name)) {
778             // Value is quoted or token. Strip if quoted:
779             int last = value.length() - 1;
780             if (last > 0 &&
781               value.charAt(0) == HttpConstants.DOUBLE_QUOTE &&
782               value.charAt(last) == HttpConstants.DOUBLE_QUOTE) {
783                 value = value.substring(1, last);
784             }
785         } else if (FILENAME_ENCODED.equals(name)) {
786             try {
787                 name = HttpPostBodyUtil.FILENAME;
788                 String[] split = value.split("'", 3);
789                 value = QueryStringDecoder.decodeComponent(split[2], Charset.forName(split[0]));
790             } catch (ArrayIndexOutOfBoundsException e) {
791                  throw new ErrorDataDecoderException(e);
792             } catch (UnsupportedCharsetException e) {
793                 throw new ErrorDataDecoderException(e);
794             }
795         } else {
796             // otherwise we need to clean the value
797             value = cleanString(value);
798         }
799         return factory.createAttribute(request, name, value);
800     }
801 
802     /**
803      * Get the FileUpload (new one or current one)
804      *
805      * @param delimiter
806      *            the delimiter to use
807      * @return the InterfaceHttpData if any
808      * @throws ErrorDataDecoderException
809      */
810     protected InterfaceHttpData getFileUpload(String delimiter) {
811         // eventually restart from existing FileUpload
812         // Now get value according to Content-Type and Charset
813         Attribute encoding = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING);
814         Charset localCharset = charset;
815         // Default
816         TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
817         if (encoding != null) {
818             String code;
819             try {
820                 code = encoding.getValue().toLowerCase();
821             } catch (IOException e) {
822                 throw new ErrorDataDecoderException(e);
823             }
824             if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value())) {
825                 localCharset = HttpPostBodyUtil.US_ASCII;
826             } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value())) {
827                 localCharset = HttpPostBodyUtil.ISO_8859_1;
828                 mechanism = TransferEncodingMechanism.BIT8;
829             } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value())) {
830                 // no real charset, so let the default
831                 mechanism = TransferEncodingMechanism.BINARY;
832             } else {
833                 throw new ErrorDataDecoderException("TransferEncoding Unknown: " + code);
834             }
835         }
836         Attribute charsetAttribute = currentFieldAttributes.get(HttpHeaders.Values.CHARSET);
837         if (charsetAttribute != null) {
838             try {
839                 localCharset = Charset.forName(charsetAttribute.getValue());
840             } catch (IOException e) {
841                 throw new ErrorDataDecoderException(e);
842             } catch (UnsupportedCharsetException e) {
843                 throw new ErrorDataDecoderException(e);
844             }
845         }
846         if (currentFileUpload == null) {
847             Attribute filenameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.FILENAME);
848             Attribute nameAttribute = currentFieldAttributes.get(HttpPostBodyUtil.NAME);
849             Attribute contentTypeAttribute = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_TYPE);
850             Attribute lengthAttribute = currentFieldAttributes.get(HttpHeaders.Names.CONTENT_LENGTH);
851             long size;
852             try {
853                 size = lengthAttribute != null ? Long.parseLong(lengthAttribute.getValue()) : 0L;
854             } catch (IOException e) {
855                 throw new ErrorDataDecoderException(e);
856             } catch (NumberFormatException ignored) {
857                 size = 0;
858             }
859             try {
860                 String contentType;
861                 if (contentTypeAttribute != null) {
862                     contentType = contentTypeAttribute.getValue();
863                 } else {
864                     contentType = HttpPostBodyUtil.DEFAULT_BINARY_CONTENT_TYPE;
865                 }
866                 currentFileUpload = factory.createFileUpload(request,
867                         cleanString(nameAttribute.getValue()), cleanString(filenameAttribute.getValue()),
868                         contentType, mechanism.value(), localCharset,
869                         size);
870             } catch (NullPointerException e) {
871                 throw new ErrorDataDecoderException(e);
872             } catch (IllegalArgumentException e) {
873                 throw new ErrorDataDecoderException(e);
874             } catch (IOException e) {
875                 throw new ErrorDataDecoderException(e);
876             }
877         }
878         // load data as much as possible
879         try {
880             readFileUploadByteMultipart(undecodedChunk, delimiter, currentFileUpload);
881         } catch (NotEnoughDataDecoderException e) {
882             // do not change the buffer position
883             // since some can be already saved into FileUpload
884             // So do not change the currentStatus
885             return null;
886         }
887         if (currentFileUpload.isCompleted()) {
888             // ready to load the next one
889             if (currentStatus == MultiPartStatus.FILEUPLOAD) {
890                 currentStatus = MultiPartStatus.HEADERDELIMITER;
891                 currentFieldAttributes = null;
892             } else {
893                 currentStatus = MultiPartStatus.MIXEDDELIMITER;
894                 cleanMixedAttributes();
895             }
896             FileUpload fileUpload = currentFileUpload;
897             currentFileUpload = null;
898             return fileUpload;
899         }
900         // do not change the buffer position
901         // since some can be already saved into FileUpload
902         // So do not change the currentStatus
903         return null;
904     }
905 
906     /**
907      * Destroy the {@link HttpPostMultipartRequestDecoder} and release all it resources. After this method
908      * was called it is not possible to operate on it anymore.
909      */
910     @Override
911     public void destroy() {
912         checkDestroyed();
913         cleanFiles();
914         destroyed = true;
915 
916         if (undecodedChunk != null && undecodedChunk.refCnt() > 0) {
917             undecodedChunk.release();
918             undecodedChunk = null;
919         }
920 
921         // release all data which was not yet pulled
922         for (int i = bodyListHttpDataRank; i < bodyListHttpData.size(); i++) {
923             bodyListHttpData.get(i).release();
924         }
925     }
926 
927     /**
928      * Clean all HttpDatas (on Disk) for the current request.
929      */
930     @Override
931     public void cleanFiles() {
932         checkDestroyed();
933 
934         factory.cleanRequestHttpDatas(request);
935     }
936 
937     /**
938      * Remove the given FileUpload from the list of FileUploads to clean
939      */
940     @Override
941     public void removeHttpDataFromClean(InterfaceHttpData data) {
942         checkDestroyed();
943 
944         factory.removeHttpDataFromClean(request, data);
945     }
946 
947     /**
948      * Remove all Attributes that should be cleaned between two FileUpload in
949      * Mixed mode
950      */
951     private void cleanMixedAttributes() {
952         currentFieldAttributes.remove(HttpHeaders.Values.CHARSET);
953         currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_LENGTH);
954         currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING);
955         currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TYPE);
956         currentFieldAttributes.remove(HttpPostBodyUtil.FILENAME);
957     }
958 
959     /**
960      * Read one line up to the CRLF or LF
961      *
962      * @return the String from one line
963      * @throws NotEnoughDataDecoderException
964      *             Need more chunks and reset the {@code readerIndex} to the previous
965      *             value
966      */
967     private static String readLineStandard(ByteBuf undecodedChunk, Charset charset) {
968         int readerIndex = undecodedChunk.readerIndex();
969         try {
970             ByteBuf line = buffer(64);
971 
972             while (undecodedChunk.isReadable()) {
973                 byte nextByte = undecodedChunk.readByte();
974                 if (nextByte == HttpConstants.CR) {
975                     // check but do not changed readerIndex
976                     nextByte = undecodedChunk.getByte(undecodedChunk.readerIndex());
977                     if (nextByte == HttpConstants.LF) {
978                         // force read
979                         undecodedChunk.readByte();
980                         return line.toString(charset);
981                     } else {
982                         // Write CR (not followed by LF)
983                         line.writeByte(HttpConstants.CR);
984                     }
985                 } else if (nextByte == HttpConstants.LF) {
986                     return line.toString(charset);
987                 } else {
988                     line.writeByte(nextByte);
989                 }
990             }
991         } catch (IndexOutOfBoundsException e) {
992             undecodedChunk.readerIndex(readerIndex);
993             throw new NotEnoughDataDecoderException(e);
994         }
995         undecodedChunk.readerIndex(readerIndex);
996         throw new NotEnoughDataDecoderException();
997     }
998 
999     /**
1000      * Read one line up to the CRLF or LF
1001      *
1002      * @return the String from one line
1003      * @throws NotEnoughDataDecoderException
1004      *             Need more chunks and reset the {@code readerIndex} to the previous
1005      *             value
1006      */
1007     private static String readLine(ByteBuf undecodedChunk, Charset charset) {
1008         if (!undecodedChunk.hasArray()) {
1009             return readLineStandard(undecodedChunk, charset);
1010         }
1011         SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
1012         int readerIndex = undecodedChunk.readerIndex();
1013         try {
1014             ByteBuf line = buffer(64);
1015 
1016             while (sao.pos < sao.limit) {
1017                 byte nextByte = sao.bytes[sao.pos++];
1018                 if (nextByte == HttpConstants.CR) {
1019                     if (sao.pos < sao.limit) {
1020                         nextByte = sao.bytes[sao.pos++];
1021                         if (nextByte == HttpConstants.LF) {
1022                             sao.setReadPosition(0);
1023                             return line.toString(charset);
1024                         } else {
1025                             // Write CR (not followed by LF)
1026                             sao.pos--;
1027                             line.writeByte(HttpConstants.CR);
1028                         }
1029                     } else {
1030                         line.writeByte(nextByte);
1031                     }
1032                 } else if (nextByte == HttpConstants.LF) {
1033                     sao.setReadPosition(0);
1034                     return line.toString(charset);
1035                 } else {
1036                     line.writeByte(nextByte);
1037                 }
1038             }
1039         } catch (IndexOutOfBoundsException e) {
1040             undecodedChunk.readerIndex(readerIndex);
1041             throw new NotEnoughDataDecoderException(e);
1042         }
1043         undecodedChunk.readerIndex(readerIndex);
1044         throw new NotEnoughDataDecoderException();
1045     }
1046 
1047     /**
1048      * Read one line up to --delimiter or --delimiter-- and if existing the CRLF
1049      * or LF Read one line up to --delimiter or --delimiter-- and if existing
1050      * the CRLF or LF. Note that CRLF or LF are mandatory for opening delimiter
1051      * (--delimiter) but not for closing delimiter (--delimiter--) since some
1052      * clients does not include CRLF in this case.
1053      *
1054      * @param delimiter
1055      *            of the form --string, such that '--' is already included
1056      * @return the String from one line as the delimiter searched (opening or
1057      *         closing)
1058      * @throws NotEnoughDataDecoderException
1059      *             Need more chunks and reset the {@code readerIndex} to the previous
1060      *             value
1061      */
1062     private static String readDelimiterStandard(ByteBuf undecodedChunk, String delimiter) {
1063         int readerIndex = undecodedChunk.readerIndex();
1064         try {
1065             StringBuilder sb = new StringBuilder(64);
1066             int delimiterPos = 0;
1067             int len = delimiter.length();
1068             while (undecodedChunk.isReadable() && delimiterPos < len) {
1069                 byte nextByte = undecodedChunk.readByte();
1070                 if (nextByte == delimiter.charAt(delimiterPos)) {
1071                     delimiterPos++;
1072                     sb.append((char) nextByte);
1073                 } else {
1074                     // delimiter not found so break here !
1075                     undecodedChunk.readerIndex(readerIndex);
1076                     throw new NotEnoughDataDecoderException();
1077                 }
1078             }
1079             // Now check if either opening delimiter or closing delimiter
1080             if (undecodedChunk.isReadable()) {
1081                 byte nextByte = undecodedChunk.readByte();
1082                 // first check for opening delimiter
1083                 if (nextByte == HttpConstants.CR) {
1084                     nextByte = undecodedChunk.readByte();
1085                     if (nextByte == HttpConstants.LF) {
1086                         return sb.toString();
1087                     } else {
1088                         // error since CR must be followed by LF
1089                         // delimiter not found so break here !
1090                         undecodedChunk.readerIndex(readerIndex);
1091                         throw new NotEnoughDataDecoderException();
1092                     }
1093                 } else if (nextByte == HttpConstants.LF) {
1094                     return sb.toString();
1095                 } else if (nextByte == '-') {
1096                     sb.append('-');
1097                     // second check for closing delimiter
1098                     nextByte = undecodedChunk.readByte();
1099                     if (nextByte == '-') {
1100                         sb.append('-');
1101                         // now try to find if CRLF or LF there
1102                         if (undecodedChunk.isReadable()) {
1103                             nextByte = undecodedChunk.readByte();
1104                             if (nextByte == HttpConstants.CR) {
1105                                 nextByte = undecodedChunk.readByte();
1106                                 if (nextByte == HttpConstants.LF) {
1107                                     return sb.toString();
1108                                 } else {
1109                                     // error CR without LF
1110                                     // delimiter not found so break here !
1111                                     undecodedChunk.readerIndex(readerIndex);
1112                                     throw new NotEnoughDataDecoderException();
1113                                 }
1114                             } else if (nextByte == HttpConstants.LF) {
1115                                 return sb.toString();
1116                             } else {
1117                                 // No CRLF but ok however (Adobe Flash uploader)
1118                                 // minus 1 since we read one char ahead but
1119                                 // should not
1120                                 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1121                                 return sb.toString();
1122                             }
1123                         }
1124                         // FIXME what do we do here?
1125                         // either considering it is fine, either waiting for
1126                         // more data to come?
1127                         // lets try considering it is fine...
1128                         return sb.toString();
1129                     }
1130                     // only one '-' => not enough
1131                     // whatever now => error since incomplete
1132                 }
1133             }
1134         } catch (IndexOutOfBoundsException e) {
1135             undecodedChunk.readerIndex(readerIndex);
1136             throw new NotEnoughDataDecoderException(e);
1137         }
1138         undecodedChunk.readerIndex(readerIndex);
1139         throw new NotEnoughDataDecoderException();
1140     }
1141 
1142     /**
1143      * Read one line up to --delimiter or --delimiter-- and if existing the CRLF
1144      * or LF. Note that CRLF or LF are mandatory for opening delimiter
1145      * (--delimiter) but not for closing delimiter (--delimiter--) since some
1146      * clients does not include CRLF in this case.
1147      *
1148      * @param delimiter
1149      *            of the form --string, such that '--' is already included
1150      * @return the String from one line as the delimiter searched (opening or
1151      *         closing)
1152      * @throws NotEnoughDataDecoderException
1153      *             Need more chunks and reset the readerInder to the previous
1154      *             value
1155      */
1156     private static String readDelimiter(ByteBuf undecodedChunk, String delimiter) {
1157         if (!undecodedChunk.hasArray()) {
1158             return readDelimiterStandard(undecodedChunk, delimiter);
1159         }
1160         SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
1161         int readerIndex = undecodedChunk.readerIndex();
1162         int delimiterPos = 0;
1163         int len = delimiter.length();
1164         try {
1165             StringBuilder sb = new StringBuilder(64);
1166             // check conformity with delimiter
1167             while (sao.pos < sao.limit && delimiterPos < len) {
1168                 byte nextByte = sao.bytes[sao.pos++];
1169                 if (nextByte == delimiter.charAt(delimiterPos)) {
1170                     delimiterPos++;
1171                     sb.append((char) nextByte);
1172                 } else {
1173                     // delimiter not found so break here !
1174                     undecodedChunk.readerIndex(readerIndex);
1175                     throw new NotEnoughDataDecoderException();
1176                 }
1177             }
1178             // Now check if either opening delimiter or closing delimiter
1179             if (sao.pos < sao.limit) {
1180                 byte nextByte = sao.bytes[sao.pos++];
1181                 if (nextByte == HttpConstants.CR) {
1182                     // first check for opening delimiter
1183                     if (sao.pos < sao.limit) {
1184                         nextByte = sao.bytes[sao.pos++];
1185                         if (nextByte == HttpConstants.LF) {
1186                             sao.setReadPosition(0);
1187                             return sb.toString();
1188                         } else {
1189                             // error CR without LF
1190                             // delimiter not found so break here !
1191                             undecodedChunk.readerIndex(readerIndex);
1192                             throw new NotEnoughDataDecoderException();
1193                         }
1194                     } else {
1195                         // error since CR must be followed by LF
1196                         // delimiter not found so break here !
1197                         undecodedChunk.readerIndex(readerIndex);
1198                         throw new NotEnoughDataDecoderException();
1199                     }
1200                 } else if (nextByte == HttpConstants.LF) {
1201                     // same first check for opening delimiter where LF used with
1202                     // no CR
1203                     sao.setReadPosition(0);
1204                     return sb.toString();
1205                 } else if (nextByte == '-') {
1206                     sb.append('-');
1207                     // second check for closing delimiter
1208                     if (sao.pos < sao.limit) {
1209                         nextByte = sao.bytes[sao.pos++];
1210                         if (nextByte == '-') {
1211                             sb.append('-');
1212                             // now try to find if CRLF or LF there
1213                             if (sao.pos < sao.limit) {
1214                                 nextByte = sao.bytes[sao.pos++];
1215                                 if (nextByte == HttpConstants.CR) {
1216                                     if (sao.pos < sao.limit) {
1217                                         nextByte = sao.bytes[sao.pos++];
1218                                         if (nextByte == HttpConstants.LF) {
1219                                             sao.setReadPosition(0);
1220                                             return sb.toString();
1221                                         } else {
1222                                             // error CR without LF
1223                                             // delimiter not found so break here !
1224                                             undecodedChunk.readerIndex(readerIndex);
1225                                             throw new NotEnoughDataDecoderException();
1226                                         }
1227                                     } else {
1228                                         // error CR without LF
1229                                         // delimiter not found so break here !
1230                                         undecodedChunk.readerIndex(readerIndex);
1231                                         throw new NotEnoughDataDecoderException();
1232                                     }
1233                                 } else if (nextByte == HttpConstants.LF) {
1234                                     sao.setReadPosition(0);
1235                                     return sb.toString();
1236                                 } else {
1237                                     // No CRLF but ok however (Adobe Flash
1238                                     // uploader)
1239                                     // minus 1 since we read one char ahead but
1240                                     // should not
1241                                     sao.setReadPosition(1);
1242                                     return sb.toString();
1243                                 }
1244                             }
1245                             // FIXME what do we do here?
1246                             // either considering it is fine, either waiting for
1247                             // more data to come?
1248                             // lets try considering it is fine...
1249                             sao.setReadPosition(0);
1250                             return sb.toString();
1251                         }
1252                         // whatever now => error since incomplete
1253                         // only one '-' => not enough or whatever not enough
1254                         // element
1255                     }
1256                 }
1257             }
1258         } catch (IndexOutOfBoundsException e) {
1259             undecodedChunk.readerIndex(readerIndex);
1260             throw new NotEnoughDataDecoderException(e);
1261         }
1262         undecodedChunk.readerIndex(readerIndex);
1263         throw new NotEnoughDataDecoderException();
1264     }
1265 
1266     /**
1267      * Read a FileUpload data as Byte (Binary) and add the bytes directly to the
1268      * FileUpload. If the delimiter is found, the FileUpload is completed.
1269      *
1270      * @throws NotEnoughDataDecoderException
1271      *             Need more chunks but do not reset the readerInder since some
1272      *             values will be already added to the FileOutput
1273      * @throws ErrorDataDecoderException
1274      *             write IO error occurs with the FileUpload
1275      */
1276     private static void readFileUploadByteMultipartStandard(ByteBuf undecodedChunk, String delimiter,
1277                                                             FileUpload currentFileUpload) {
1278         int readerIndex = undecodedChunk.readerIndex();
1279         // found the decoder limit
1280         boolean newLine = true;
1281         int index = 0;
1282         int lastPosition = undecodedChunk.readerIndex();
1283         boolean found = false;
1284         while (undecodedChunk.isReadable()) {
1285             byte nextByte = undecodedChunk.readByte();
1286             if (newLine) {
1287                 // Check the delimiter
1288                 if (nextByte == delimiter.codePointAt(index)) {
1289                     index++;
1290                     if (delimiter.length() == index) {
1291                         found = true;
1292                         break;
1293                     }
1294                 } else {
1295                     newLine = false;
1296                     index = 0;
1297                     // continue until end of line
1298                     if (nextByte == HttpConstants.CR) {
1299                         if (undecodedChunk.isReadable()) {
1300                             nextByte = undecodedChunk.readByte();
1301                             if (nextByte == HttpConstants.LF) {
1302                                 newLine = true;
1303                                 index = 0;
1304                                 lastPosition = undecodedChunk.readerIndex() - 2;
1305                             } else {
1306                                 // save last valid position
1307                                 lastPosition = undecodedChunk.readerIndex() - 1;
1308 
1309                                 // Unread next byte.
1310                                 undecodedChunk.readerIndex(lastPosition);
1311                             }
1312                         }
1313                     } else if (nextByte == HttpConstants.LF) {
1314                         newLine = true;
1315                         index = 0;
1316                         lastPosition = undecodedChunk.readerIndex() - 1;
1317                     } else {
1318                         // save last valid position
1319                         lastPosition = undecodedChunk.readerIndex();
1320                     }
1321                 }
1322             } else {
1323                 // continue until end of line
1324                 if (nextByte == HttpConstants.CR) {
1325                     if (undecodedChunk.isReadable()) {
1326                         nextByte = undecodedChunk.readByte();
1327                         if (nextByte == HttpConstants.LF) {
1328                             newLine = true;
1329                             index = 0;
1330                             lastPosition = undecodedChunk.readerIndex() - 2;
1331                         } else {
1332                             // save last valid position
1333                             lastPosition = undecodedChunk.readerIndex() - 1;
1334 
1335                             // Unread next byte.
1336                             undecodedChunk.readerIndex(lastPosition);
1337                         }
1338                     }
1339                 } else if (nextByte == HttpConstants.LF) {
1340                     newLine = true;
1341                     index = 0;
1342                     lastPosition = undecodedChunk.readerIndex() - 1;
1343                 } else {
1344                     // save last valid position
1345                     lastPosition = undecodedChunk.readerIndex();
1346                 }
1347             }
1348         }
1349         ByteBuf buffer = undecodedChunk.copy(readerIndex, lastPosition - readerIndex);
1350         if (found) {
1351             // found so lastPosition is correct and final
1352             try {
1353                 currentFileUpload.addContent(buffer, true);
1354                 // just before the CRLF and delimiter
1355                 undecodedChunk.readerIndex(lastPosition);
1356             } catch (IOException e) {
1357                 throw new ErrorDataDecoderException(e);
1358             }
1359         } else {
1360             // possibly the delimiter is partially found but still the last
1361             // position is OK
1362             try {
1363                 currentFileUpload.addContent(buffer, false);
1364                 // last valid char (not CR, not LF, not beginning of delimiter)
1365                 undecodedChunk.readerIndex(lastPosition);
1366                 throw new NotEnoughDataDecoderException();
1367             } catch (IOException e) {
1368                 throw new ErrorDataDecoderException(e);
1369             }
1370         }
1371     }
1372 
1373     /**
1374      * Read a FileUpload data as Byte (Binary) and add the bytes directly to the
1375      * FileUpload. If the delimiter is found, the FileUpload is completed.
1376      *
1377      * @throws NotEnoughDataDecoderException
1378      *             Need more chunks but do not reset the {@code readerIndex} since some
1379      *             values will be already added to the FileOutput
1380      * @throws ErrorDataDecoderException
1381      *             write IO error occurs with the FileUpload
1382      */
1383     private static void readFileUploadByteMultipart(ByteBuf undecodedChunk, String delimiter,
1384                                                     FileUpload currentFileUpload) {
1385         if (!undecodedChunk.hasArray()) {
1386             readFileUploadByteMultipartStandard(undecodedChunk, delimiter, currentFileUpload);
1387             return;
1388         }
1389         SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
1390         int readerIndex = undecodedChunk.readerIndex();
1391         // found the decoder limit
1392         boolean newLine = true;
1393         int index = 0;
1394         int lastrealpos = sao.pos;
1395         int lastPosition;
1396         boolean found = false;
1397 
1398         while (sao.pos < sao.limit) {
1399             byte nextByte = sao.bytes[sao.pos++];
1400             if (newLine) {
1401                 // Check the delimiter
1402                 if (nextByte == delimiter.codePointAt(index)) {
1403                     index++;
1404                     if (delimiter.length() == index) {
1405                         found = true;
1406                         break;
1407                     }
1408                 } else {
1409                     newLine = false;
1410                     index = 0;
1411                     // continue until end of line
1412                     if (nextByte == HttpConstants.CR) {
1413                         if (sao.pos < sao.limit) {
1414                             nextByte = sao.bytes[sao.pos++];
1415                             if (nextByte == HttpConstants.LF) {
1416                                 newLine = true;
1417                                 index = 0;
1418                                 lastrealpos = sao.pos - 2;
1419                             } else {
1420                                 // unread next byte
1421                                 sao.pos--;
1422 
1423                                 // save last valid position
1424                                 lastrealpos = sao.pos;
1425                             }
1426                         }
1427                     } else if (nextByte == HttpConstants.LF) {
1428                         newLine = true;
1429                         index = 0;
1430                         lastrealpos = sao.pos - 1;
1431                     } else {
1432                         // save last valid position
1433                         lastrealpos = sao.pos;
1434                     }
1435                 }
1436             } else {
1437                 // continue until end of line
1438                 if (nextByte == HttpConstants.CR) {
1439                     if (sao.pos < sao.limit) {
1440                         nextByte = sao.bytes[sao.pos++];
1441                         if (nextByte == HttpConstants.LF) {
1442                             newLine = true;
1443                             index = 0;
1444                             lastrealpos = sao.pos - 2;
1445                         } else {
1446                             // unread next byte
1447                             sao.pos--;
1448 
1449                             // save last valid position
1450                             lastrealpos = sao.pos;
1451                         }
1452                     }
1453                 } else if (nextByte == HttpConstants.LF) {
1454                     newLine = true;
1455                     index = 0;
1456                     lastrealpos = sao.pos - 1;
1457                 } else {
1458                     // save last valid position
1459                     lastrealpos = sao.pos;
1460                 }
1461             }
1462         }
1463         lastPosition = sao.getReadPosition(lastrealpos);
1464         ByteBuf buffer = undecodedChunk.copy(readerIndex, lastPosition - readerIndex);
1465         if (found) {
1466             // found so lastPosition is correct and final
1467             try {
1468                 currentFileUpload.addContent(buffer, true);
1469                 // just before the CRLF and delimiter
1470                 undecodedChunk.readerIndex(lastPosition);
1471             } catch (IOException e) {
1472                 throw new ErrorDataDecoderException(e);
1473             }
1474         } else {
1475             // possibly the delimiter is partially found but still the last
1476             // position is OK
1477             try {
1478                 currentFileUpload.addContent(buffer, false);
1479                 // last valid char (not CR, not LF, not beginning of delimiter)
1480                 undecodedChunk.readerIndex(lastPosition);
1481                 throw new NotEnoughDataDecoderException();
1482             } catch (IOException e) {
1483                 throw new ErrorDataDecoderException(e);
1484             }
1485         }
1486     }
1487 
1488     /**
1489      * Load the field value from a Multipart request
1490      *
1491      * @throws NotEnoughDataDecoderException
1492      *             Need more chunks
1493      * @throws ErrorDataDecoderException
1494      */
1495     private static void loadFieldMultipartStandard(ByteBuf undecodedChunk, String delimiter,
1496                                                    Attribute currentAttribute) {
1497         int readerIndex = undecodedChunk.readerIndex();
1498         try {
1499             // found the decoder limit
1500             boolean newLine = true;
1501             int index = 0;
1502             int lastPosition = undecodedChunk.readerIndex();
1503             boolean found = false;
1504             while (undecodedChunk.isReadable()) {
1505                 byte nextByte = undecodedChunk.readByte();
1506                 if (newLine) {
1507                     // Check the delimiter
1508                     if (nextByte == delimiter.codePointAt(index)) {
1509                         index++;
1510                         if (delimiter.length() == index) {
1511                             found = true;
1512                             break;
1513                         }
1514                     } else {
1515                         newLine = false;
1516                         index = 0;
1517                         // continue until end of line
1518                         if (nextByte == HttpConstants.CR) {
1519                             if (undecodedChunk.isReadable()) {
1520                                 nextByte = undecodedChunk.readByte();
1521                                 if (nextByte == HttpConstants.LF) {
1522                                     newLine = true;
1523                                     index = 0;
1524                                     lastPosition = undecodedChunk.readerIndex() - 2;
1525                                 } else {
1526                                     // Unread second nextByte
1527                                     lastPosition = undecodedChunk.readerIndex() - 1;
1528                                     undecodedChunk.readerIndex(lastPosition);
1529                                 }
1530                             } else {
1531                                 lastPosition = undecodedChunk.readerIndex() - 1;
1532                             }
1533                         } else if (nextByte == HttpConstants.LF) {
1534                             newLine = true;
1535                             index = 0;
1536                             lastPosition = undecodedChunk.readerIndex() - 1;
1537                         } else {
1538                             lastPosition = undecodedChunk.readerIndex();
1539                         }
1540                     }
1541                 } else {
1542                     // continue until end of line
1543                     if (nextByte == HttpConstants.CR) {
1544                         if (undecodedChunk.isReadable()) {
1545                             nextByte = undecodedChunk.readByte();
1546                             if (nextByte == HttpConstants.LF) {
1547                                 newLine = true;
1548                                 index = 0;
1549                                 lastPosition = undecodedChunk.readerIndex() - 2;
1550                             } else {
1551                                 // Unread second nextByte
1552                                 lastPosition = undecodedChunk.readerIndex() - 1;
1553                                 undecodedChunk.readerIndex(lastPosition);
1554                             }
1555                         } else {
1556                             lastPosition = undecodedChunk.readerIndex() - 1;
1557                         }
1558                     } else if (nextByte == HttpConstants.LF) {
1559                         newLine = true;
1560                         index = 0;
1561                         lastPosition = undecodedChunk.readerIndex() - 1;
1562                     } else {
1563                         lastPosition = undecodedChunk.readerIndex();
1564                     }
1565                 }
1566             }
1567             if (found) {
1568                 // found so lastPosition is correct
1569                 // but position is just after the delimiter (either close
1570                 // delimiter or simple one)
1571                 // so go back of delimiter size
1572                 try {
1573                     currentAttribute.addContent(
1574                             undecodedChunk.copy(readerIndex, lastPosition - readerIndex), true);
1575                 } catch (IOException e) {
1576                     throw new ErrorDataDecoderException(e);
1577                 }
1578                 undecodedChunk.readerIndex(lastPosition);
1579             } else {
1580                 try {
1581                     currentAttribute.addContent(
1582                             undecodedChunk.copy(readerIndex, lastPosition - readerIndex), false);
1583                 } catch (IOException e) {
1584                     throw new ErrorDataDecoderException(e);
1585                 }
1586                 undecodedChunk.readerIndex(lastPosition);
1587                 throw new NotEnoughDataDecoderException();
1588             }
1589         } catch (IndexOutOfBoundsException e) {
1590             undecodedChunk.readerIndex(readerIndex);
1591             throw new NotEnoughDataDecoderException(e);
1592         }
1593     }
1594 
1595     /**
1596      * Load the field value from a Multipart request
1597      *
1598      * @throws NotEnoughDataDecoderException
1599      *             Need more chunks
1600      * @throws ErrorDataDecoderException
1601      */
1602     private static void loadFieldMultipart(ByteBuf undecodedChunk, String delimiter, Attribute currentAttribute) {
1603         if (!undecodedChunk.hasArray()) {
1604             loadFieldMultipartStandard(undecodedChunk, delimiter, currentAttribute);
1605             return;
1606         }
1607         SeekAheadOptimize sao = new SeekAheadOptimize(undecodedChunk);
1608         int readerIndex = undecodedChunk.readerIndex();
1609         try {
1610             // found the decoder limit
1611             boolean newLine = true;
1612             int index = 0;
1613             int lastPosition;
1614             int lastrealpos = sao.pos;
1615             boolean found = false;
1616 
1617             while (sao.pos < sao.limit) {
1618                 byte nextByte = sao.bytes[sao.pos++];
1619                 if (newLine) {
1620                     // Check the delimiter
1621                     if (nextByte == delimiter.codePointAt(index)) {
1622                         index++;
1623                         if (delimiter.length() == index) {
1624                             found = true;
1625                             break;
1626                         }
1627                     } else {
1628                         newLine = false;
1629                         index = 0;
1630                         // continue until end of line
1631                         if (nextByte == HttpConstants.CR) {
1632                             if (sao.pos < sao.limit) {
1633                                 nextByte = sao.bytes[sao.pos++];
1634                                 if (nextByte == HttpConstants.LF) {
1635                                     newLine = true;
1636                                     index = 0;
1637                                     lastrealpos = sao.pos - 2;
1638                                 } else {
1639                                     // Unread last nextByte
1640                                     sao.pos--;
1641                                     lastrealpos = sao.pos;
1642                                 }
1643                             }
1644                         } else if (nextByte == HttpConstants.LF) {
1645                             newLine = true;
1646                             index = 0;
1647                             lastrealpos = sao.pos - 1;
1648                         } else {
1649                             lastrealpos = sao.pos;
1650                         }
1651                     }
1652                 } else {
1653                     // continue until end of line
1654                     if (nextByte == HttpConstants.CR) {
1655                         if (sao.pos < sao.limit) {
1656                             nextByte = sao.bytes[sao.pos++];
1657                             if (nextByte == HttpConstants.LF) {
1658                                 newLine = true;
1659                                 index = 0;
1660                                 lastrealpos = sao.pos - 2;
1661                             } else {
1662                                 // Unread last nextByte
1663                                 sao.pos--;
1664                                 lastrealpos = sao.pos;
1665                             }
1666                         }
1667                     } else if (nextByte == HttpConstants.LF) {
1668                         newLine = true;
1669                         index = 0;
1670                         lastrealpos = sao.pos - 1;
1671                     } else {
1672                         lastrealpos = sao.pos;
1673                     }
1674                 }
1675             }
1676             lastPosition = sao.getReadPosition(lastrealpos);
1677             if (found) {
1678                 // found so lastPosition is correct
1679                 // but position is just after the delimiter (either close
1680                 // delimiter or simple one)
1681                 // so go back of delimiter size
1682                 try {
1683                     currentAttribute.addContent(
1684                             undecodedChunk.copy(readerIndex, lastPosition - readerIndex), true);
1685                 } catch (IOException e) {
1686                     throw new ErrorDataDecoderException(e);
1687                 }
1688                 undecodedChunk.readerIndex(lastPosition);
1689             } else {
1690                 try {
1691                     currentAttribute.addContent(
1692                             undecodedChunk.copy(readerIndex, lastPosition - readerIndex), false);
1693                 } catch (IOException e) {
1694                     throw new ErrorDataDecoderException(e);
1695                 }
1696                 undecodedChunk.readerIndex(lastPosition);
1697                 throw new NotEnoughDataDecoderException();
1698             }
1699         } catch (IndexOutOfBoundsException e) {
1700             undecodedChunk.readerIndex(readerIndex);
1701             throw new NotEnoughDataDecoderException(e);
1702         }
1703     }
1704 
1705     /**
1706      * Clean the String from any unallowed character
1707      *
1708      * @return the cleaned String
1709      */
1710     @SuppressWarnings("StatementWithEmptyBody")
1711     private static String cleanString(String field) {
1712         StringBuilder sb = new StringBuilder(field.length());
1713         for (int i = 0; i < field.length(); i++) {
1714             char nextChar = field.charAt(i);
1715             if (nextChar == HttpConstants.COLON) {
1716                 sb.append(HttpConstants.SP_CHAR);
1717             } else if (nextChar == HttpConstants.COMMA) {
1718                 sb.append(HttpConstants.SP_CHAR);
1719             } else if (nextChar == HttpConstants.EQUALS) {
1720                 sb.append(HttpConstants.SP_CHAR);
1721             } else if (nextChar == HttpConstants.SEMICOLON) {
1722                 sb.append(HttpConstants.SP_CHAR);
1723             } else if (nextChar == HttpConstants.HT) {
1724                 sb.append(HttpConstants.SP_CHAR);
1725             } else if (nextChar == HttpConstants.DOUBLE_QUOTE) {
1726                 // nothing added, just removes it
1727             } else {
1728                 sb.append(nextChar);
1729             }
1730         }
1731         return sb.toString().trim();
1732     }
1733 
1734     /**
1735      * Skip one empty line
1736      *
1737      * @return True if one empty line was skipped
1738      */
1739     private boolean skipOneLine() {
1740         if (!undecodedChunk.isReadable()) {
1741             return false;
1742         }
1743         byte nextByte = undecodedChunk.readByte();
1744         if (nextByte == HttpConstants.CR) {
1745             if (!undecodedChunk.isReadable()) {
1746                 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1747                 return false;
1748             }
1749             nextByte = undecodedChunk.readByte();
1750             if (nextByte == HttpConstants.LF) {
1751                 return true;
1752             }
1753             undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
1754             return false;
1755         }
1756         if (nextByte == HttpConstants.LF) {
1757             return true;
1758         }
1759         undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
1760         return false;
1761     }
1762 
1763     /**
1764      * Split one header in Multipart
1765      *
1766      * @return an array of String where rank 0 is the name of the header,
1767      *         follows by several values that were separated by ';' or ','
1768      */
1769     private static String[] splitMultipartHeader(String sb) {
1770         ArrayList<String> headers = new ArrayList<String>(1);
1771         int nameStart;
1772         int nameEnd;
1773         int colonEnd;
1774         int valueStart;
1775         int valueEnd;
1776         nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
1777         for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd++) {
1778             char ch = sb.charAt(nameEnd);
1779             if (ch == ':' || Character.isWhitespace(ch)) {
1780                 break;
1781             }
1782         }
1783         for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd++) {
1784             if (sb.charAt(colonEnd) == ':') {
1785                 colonEnd++;
1786                 break;
1787             }
1788         }
1789         valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd);
1790         valueEnd = HttpPostBodyUtil.findEndOfString(sb);
1791         headers.add(sb.substring(nameStart, nameEnd));
1792         String svalue = sb.substring(valueStart, valueEnd);
1793         String[] values;
1794         if (svalue.indexOf(';') >= 0) {
1795             values = splitMultipartHeaderValues(svalue);
1796         } else {
1797             values = svalue.split(",");
1798         }
1799         for (String value : values) {
1800             headers.add(value.trim());
1801         }
1802         String[] array = new String[headers.size()];
1803         for (int i = 0; i < headers.size(); i++) {
1804             array[i] = headers.get(i);
1805         }
1806         return array;
1807     }
1808 
1809     /**
1810      * Split one header value in Multipart
1811      * @return an array of String where values that were separated by ';' or ','
1812      */
1813     private static String[] splitMultipartHeaderValues(String svalue) {
1814         List<String> values = InternalThreadLocalMap.get().arrayList(1);
1815         boolean inQuote = false;
1816         boolean escapeNext = false;
1817         int start = 0;
1818         for (int i = 0; i < svalue.length(); i++) {
1819             char c = svalue.charAt(i);
1820             if (inQuote) {
1821                 if (escapeNext) {
1822                     escapeNext = false;
1823                 } else {
1824                     if (c == '\\') {
1825                         escapeNext = true;
1826                     } else if (c == '"') {
1827                         inQuote = false;
1828                     }
1829                 }
1830             } else {
1831                 if (c == '"') {
1832                     inQuote = true;
1833                 } else if (c == ';') {
1834                     values.add(svalue.substring(start, i));
1835                     start = i + 1;
1836                 }
1837             }
1838         }
1839         values.add(svalue.substring(start));
1840         return values.toArray(new String[values.size()]);
1841     }
1842 }