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