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