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