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