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 java.io.IOException;
    19  import java.io.UnsupportedEncodingException;
    20  import java.net.URLDecoder;
    21  import java.nio.charset.Charset;
    22  import java.util.ArrayList;
    23  import java.util.List;
    24  import java.util.Map;
    25  import java.util.TreeMap;
    26
    27  import org.jboss.netty.buffer.ChannelBuffer;
    28  import org.jboss.netty.buffer.ChannelBuffers;
    29  import org.jboss.netty.handler.codec.http.HttpChunk;
    30  import org.jboss.netty.handler.codec.http.HttpConstants;
    31  import org.jboss.netty.handler.codec.http.HttpHeaders;
    32  import org.jboss.netty.handler.codec.http.HttpMethod;
    33  import org.jboss.netty.handler.codec.http.HttpRequest;
    34  import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadNoBackArrayException;
    35  import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.SeekAheadOptimize;
    36  import org.jboss.netty.handler.codec.http.multipart.HttpPostBodyUtil.TransferEncodingMechanism;
    37  import org.jboss.netty.util.internal.CaseIgnoringComparator;
    38
    39  /**
    40   * This decoder will decode Body and can handle POST BODY.
    41   */
    42  public class HttpPostRequestDecoder {
    43      /**
    44       * Factory used to create InterfaceHttpData
    45       */
    46      private final HttpDataFactory factory;
    47
    48      /**
    49       * Request to decode
    50       */
    51      private final HttpRequest request;
    52
    53      /**
    54       * Default charset to use
    55       */
    56      private final Charset charset;
    57
    58      /**
    59       * Does request have a body to decode
    60       */
    61      private boolean bodyToDecode;
    62
    63      /**
    64       * Does the last chunk already received
    65       */
    66      private boolean isLastChunk;
    67
    68      /**
    69       * HttpDatas from Body
    70       */
    71      private final List<InterfaceHttpData> bodyListHttpData = new ArrayList<InterfaceHttpData>();
    72
    73      /**
    74       * HttpDatas as Map from Body
    75       */
    76      private final Map<String, List<InterfaceHttpData>> bodyMapHttpData = new TreeMap<String, List<InterfaceHttpData>>(
    77              CaseIgnoringComparator.INSTANCE);
    78
    79      /**
    80       * The current channelBuffer
    81       */
    82      private ChannelBuffer undecodedChunk;
    83
    84      /**
    85       * Does this request is a Multipart request
    86       */
    87      private boolean isMultipart;
    88
    89      /**
    90       * Body HttpDatas current position
    91       */
    92      private int bodyListHttpDataRank;
    93
    94      /**
    95       * If multipart, this is the boundary for the flobal multipart
    96       */
    97      private String multipartDataBoundary;
    98
    99      /**
    100      * If multipart, there could be internal multiparts (mixed) to the global multipart.
    101      * Only one level is allowed.
    102      */
    103     private String multipartMixedBoundary;
    104
    105     /**
    106      * Current status
    107      */
    108     private MultiPartStatus currentStatus = MultiPartStatus.NOTSTARTED;
    109
    110     /**
    111      * Used in Multipart
    112      */
    113     private Map<String, Attribute> currentFieldAttributes;
    114
    115     /**
    116      * The current FileUpload that is currently in decode process
    117      */
    118     private FileUpload currentFileUpload;
    119
    120     /**
    121      * The current Attribute that is currently in decode process
    122      */
    123     private Attribute currentAttribute;
    124
    125     /**
    126     *
    127     * @param request the request to decode
    128     * @throws NullPointerException for request
    129     * @throws IncompatibleDataDecoderException if the request has no body to decode
    130     * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
    131     */
    132     public HttpPostRequestDecoder(HttpRequest request)
    133             throws ErrorDataDecoderException, IncompatibleDataDecoderException {
    134         this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
    135                 request, HttpConstants.DEFAULT_CHARSET);
    136     }
    137
    138     /**
    139      *
    140      * @param factory the factory used to create InterfaceHttpData
    141      * @param request the request to decode
    142      * @throws NullPointerException for request or factory
    143      * @throws IncompatibleDataDecoderException if the request has no body to decode
    144      * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
    145      */
    146     public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request)
    147             throws ErrorDataDecoderException, IncompatibleDataDecoderException {
    148         this(factory, request, HttpConstants.DEFAULT_CHARSET);
    149     }
    150
    151     /**
    152      *
    153      * @param factory the factory used to create InterfaceHttpData
    154      * @param request the request to decode
    155      * @param charset the charset to use as default
    156      * @throws NullPointerException for request or charset or factory
    157      * @throws IncompatibleDataDecoderException if the request has no body to decode
    158      * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
    159      */
    160     public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request,
    161             Charset charset) throws ErrorDataDecoderException,
    162             IncompatibleDataDecoderException {
    163         if (factory == null) {
    164             throw new NullPointerException("factory");
    165         }
    166         if (request == null) {
    167             throw new NullPointerException("request");
    168         }
    169         if (charset == null) {
    170             throw new NullPointerException("charset");
    171         }
    172         this.request = request;
    173         HttpMethod method = request.getMethod();
    174         if (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PATCH)) {
    175             bodyToDecode = true;
    176         }
    177         this.charset = charset;
    178         this.factory = factory;
    179         // Fill default values
    180         if (this.request.containsHeader(HttpHeaders.Names.CONTENT_TYPE)) {
    181             checkMultipart(this.request.getHeader(HttpHeaders.Names.CONTENT_TYPE));
    182         } else {
    183             isMultipart = false;
    184         }
    185         if (!bodyToDecode) {
    186             throw new IncompatibleDataDecoderException("No Body to decode");
    187         }
    188         if (!this.request.isChunked()) {
    189             undecodedChunk = this.request.getContent();
    190             isLastChunk = true;
    191             parseBody();
    192         }
    193     }
    194
    195     /**
    196      * states follow
    197      * NOTSTARTED PREAMBLE (
    198      *  (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))*
    199      *  (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE
    200      *     (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+
    201      *   MIXEDCLOSEDELIMITER)*
    202      * CLOSEDELIMITER)+ EPILOGUE
    203      *
    204      *  First status is: NOSTARTED
    205
    206         Content-type: multipart/form-data, boundary=AaB03x     => PREAMBLE in Header
    207
    208         --AaB03x                                               => HEADERDELIMITER
    209         content-disposition: form-data; name="field1"          => DISPOSITION
    210
    211         Joe Blow                                               => FIELD
    212         --AaB03x                                               => HEADERDELIMITER
    213         content-disposition: form-data; name="pics"            => DISPOSITION
    214         Content-type: multipart/mixed, boundary=BbC04y
    215
    216         --BbC04y                                               => MIXEDDELIMITER
    217         Content-disposition: attachment; filename="file1.txt"  => MIXEDDISPOSITION
    218         Content-Type: text/plain
    219
    220         ... contents of file1.txt ...                          => MIXEDFILEUPLOAD
    221         --BbC04y                                               => MIXEDDELIMITER
    222         Content-disposition: file; filename="file2.gif"  => MIXEDDISPOSITION
    223         Content-type: image/gif
    224         Content-Transfer-Encoding: binary
    225
    226           ...contents of file2.gif...                          => MIXEDFILEUPLOAD
    227         --BbC04y--                                             => MIXEDCLOSEDELIMITER
    228         --AaB03x--                                             => CLOSEDELIMITER
    229
    230        Once CLOSEDELIMITER is found, last status is EPILOGUE
    231      */
    232     private enum MultiPartStatus {
    233         NOTSTARTED,
    234         PREAMBLE,
    235         HEADERDELIMITER,
    236         DISPOSITION,
    237         FIELD,
    238         FILEUPLOAD,
    239         MIXEDPREAMBLE,
    240         MIXEDDELIMITER,
    241         MIXEDDISPOSITION,
    242         MIXEDFILEUPLOAD,
    243         MIXEDCLOSEDELIMITER,
    244         CLOSEDELIMITER,
    245         PREEPILOGUE,
    246         EPILOGUE
    247     }
    248
    249     /**
    250      * Check from the request ContentType if this request is a Multipart request.
    251      * @param contentType
    252      * @throws ErrorDataDecoderException
    253      */
    254     private void checkMultipart(String contentType)
    255             throws ErrorDataDecoderException {
    256         // Check if Post using "multipart/form-data; boundary=--89421926422648"
    257         String[] headerContentType = splitHeaderContentType(contentType);
    258         if (headerContentType[0].toLowerCase().startsWith(
    259                 HttpHeaders.Values.MULTIPART_FORM_DATA) &&
    260                 headerContentType[1].toLowerCase().startsWith(
    261                         HttpHeaders.Values.BOUNDARY)) {
    262             String[] boundary = headerContentType[1].split("=");
    263             if (boundary.length != 2) {
    264                 throw new ErrorDataDecoderException("Needs a boundary value");
    265             }
    266             multipartDataBoundary = "--" + boundary[1];
    267             isMultipart = true;
    268             currentStatus = MultiPartStatus.HEADERDELIMITER;
    269         } else {
    270             isMultipart = false;
    271         }
    272     }
    273
    274     /**
    275      * True if this request is a Multipart request
    276      * @return True if this request is a Multipart request
    277      */
    278     public boolean isMultipart() {
    279         return isMultipart;
    280     }
    281
    282     /**
    283      * This method returns a List of all HttpDatas from body.<br>
    284      *
    285      * If chunked, all chunks must have been offered using offer() method.
    286      * If not, NotEnoughDataDecoderException will be raised.
    287      *
    288      * @return the list of HttpDatas from Body part for POST method
    289      * @throws NotEnoughDataDecoderException Need more chunks
    290      */
    291     public List<InterfaceHttpData> getBodyHttpDatas()
    292             throws NotEnoughDataDecoderException {
    293         if (!isLastChunk) {
    294             throw new NotEnoughDataDecoderException();
    295         }
    296         return bodyListHttpData;
    297     }
    298
    299     /**
    300      * This method returns a List of all HttpDatas with the given name from body.<br>
    301      *
    302      * If chunked, all chunks must have been offered using offer() method.
    303      * If not, NotEnoughDataDecoderException will be raised.
    304
    305      * @param name
    306      * @return All Body HttpDatas with the given name (ignore case)
    307      * @throws NotEnoughDataDecoderException need more chunks
    308      */
    309     public List<InterfaceHttpData> getBodyHttpDatas(String name)
    310             throws NotEnoughDataDecoderException {
    311         if (!isLastChunk) {
    312             throw new NotEnoughDataDecoderException();
    313         }
    314         return bodyMapHttpData.get(name);
    315     }
    316
    317     /**
    318      * This method returns the first InterfaceHttpData with the given name from body.<br>
    319      *
    320      * If chunked, all chunks must have been offered using offer() method.
    321      * If not, NotEnoughDataDecoderException will be raised.
    322     *
    323     * @param name
    324     * @return The first Body InterfaceHttpData with the given name (ignore case)
    325     * @throws NotEnoughDataDecoderException need more chunks
    326     */
    327     public InterfaceHttpData getBodyHttpData(String name)
    328             throws NotEnoughDataDecoderException {
    329         if (!isLastChunk) {
    330             throw new NotEnoughDataDecoderException();
    331         }
    332         List<InterfaceHttpData> list = bodyMapHttpData.get(name);
    333         if (list != null) {
    334             return list.get(0);
    335         }
    336         return null;
    337     }
    338
    339     /**
    340      * Initialized the internals from a new chunk
    341      * @param chunk the new received chunk
    342      * @throws ErrorDataDecoderException if there is a problem with the charset decoding or
    343      *          other errors
    344      */
    345     public void offer(HttpChunk chunk) throws ErrorDataDecoderException {
    346         ChannelBuffer chunked = chunk.getContent();
    347         if (undecodedChunk == null) {
    348             undecodedChunk = chunked;
    349         } else {
    350             //undecodedChunk = ChannelBuffers.wrappedBuffer(undecodedChunk, chunk.getContent());
    351             // less memory usage
    352             undecodedChunk = ChannelBuffers.wrappedBuffer(
    353                     undecodedChunk, chunked);
    354         }
    355         if (chunk.isLast()) {
    356             isLastChunk = true;
    357         }
    358         parseBody();
    359     }
    360
    361     /**
    362      * True if at current status, there is an available decoded InterfaceHttpData from the Body.
    363      *
    364      * This method works for chunked and not chunked request.
    365      *
    366      * @return True if at current status, there is a decoded InterfaceHttpData
    367      * @throws EndOfDataDecoderException No more data will be available
    368      */
    369     public boolean hasNext() throws EndOfDataDecoderException {
    370         if (currentStatus == MultiPartStatus.EPILOGUE) {
    371             // OK except if end of list
    372             if (bodyListHttpDataRank >= bodyListHttpData.size()) {
    373                 throw new EndOfDataDecoderException();
    374             }
    375         }
    376         return bodyListHttpData.size() > 0 &&
    377                 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      */
    853     void skipControlCharacters() {
    854         SeekAheadOptimize sao = null;
    855         try {
    856             sao = new SeekAheadOptimize(undecodedChunk);
    857         } catch (SeekAheadNoBackArrayException e) {
    858             skipControlCharactersStandard(undecodedChunk);
    859             return;
    860         }
    861
    862         while (sao.pos < sao.limit) {
    863             char c = (char) (sao.bytes[sao.pos ++] & 0xFF);
    864             if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
    865                 sao.setReadPosition(1);
    866                 return;
    867             }
    868         }
    869         sao.setReadPosition(0);
    870     }
    871     static void skipControlCharactersStandard(ChannelBuffer buffer) {
    872         for (;;) {
    873             char c = (char) buffer.readUnsignedByte();
    874             if (!Character.isISOControl(c) && !Character.isWhitespace(c)) {
    875                 buffer.readerIndex(buffer.readerIndex() - 1);
    876                 break;
    877             }
    878         }
    879     }
    880
    881     /**
    882      * Find the next Multipart Delimiter
    883      * @param delimiter delimiter to find
    884      * @param dispositionStatus the next status if the delimiter is a start
    885      * @param closeDelimiterStatus the next status if the delimiter is a close delimiter
    886      * @return the next InterfaceHttpData if any
    887      * @throws ErrorDataDecoderException
    888      */
    889     private InterfaceHttpData findMultipartDelimiter(String delimiter,
    890             MultiPartStatus dispositionStatus,
    891             MultiPartStatus closeDelimiterStatus)
    892             throws ErrorDataDecoderException {
    893         // --AaB03x or --AaB03x--
    894         int readerIndex = undecodedChunk.readerIndex();
    895         skipControlCharacters();
    896         skipOneLine();
    897         String newline;
    898         try {
    899             newline = readDelimiter(delimiter);
    900         } catch (NotEnoughDataDecoderException e) {
    901             undecodedChunk.readerIndex(readerIndex);
    902             return null;
    903         }
    904         if (newline.equals(delimiter)) {
    905             currentStatus = dispositionStatus;
    906             return decodeMultipart(dispositionStatus);
    907         } else if (newline.equals(delimiter + "--")) {
    908             // CLOSEDELIMITER or MIXED CLOSEDELIMITER found
    909             currentStatus = closeDelimiterStatus;
    910             if (currentStatus == MultiPartStatus.HEADERDELIMITER) {
    911                 // MIXEDCLOSEDELIMITER
    912                 // end of the Mixed part
    913                 currentFieldAttributes = null;
    914                 return decodeMultipart(MultiPartStatus.HEADERDELIMITER);
    915             }
    916             return null;
    917         }
    918         undecodedChunk.readerIndex(readerIndex);
    919         throw new ErrorDataDecoderException("No Multipart delimiter found");
    920     }
    921
    922     /**
    923      * Find the next Disposition
    924      * @return the next InterfaceHttpData if any
    925      * @throws ErrorDataDecoderException
    926      */
    927     private InterfaceHttpData findMultipartDisposition()
    928             throws ErrorDataDecoderException {
    929         int readerIndex = undecodedChunk.readerIndex();
    930         if (currentStatus == MultiPartStatus.DISPOSITION) {
    931             currentFieldAttributes = new TreeMap<String, Attribute>(
    932                     CaseIgnoringComparator.INSTANCE);
    933         }
    934         // read many lines until empty line with newline found! Store all data
    935         while (!skipOneLine()) {
    936             skipControlCharacters();
    937             String newline;
    938             try {
    939                 newline = readLine();
    940             } catch (NotEnoughDataDecoderException e) {
    941                 undecodedChunk.readerIndex(readerIndex);
    942                 return null;
    943             }
    944             String[] contents = splitMultipartHeader(newline);
    945             if (contents[0].equalsIgnoreCase(HttpPostBodyUtil.CONTENT_DISPOSITION)) {
    946                 boolean checkSecondArg = false;
    947                 if (currentStatus == MultiPartStatus.DISPOSITION) {
    948                     checkSecondArg = contents[1]
    949                             .equalsIgnoreCase(HttpPostBodyUtil.FORM_DATA);
    950                 } else {
    951                     checkSecondArg = contents[1]
    952                             .equalsIgnoreCase(HttpPostBodyUtil.ATTACHMENT) ||
    953                             contents[1]
    954                             .equalsIgnoreCase(HttpPostBodyUtil.FILE);
    955                 }
    956                 if (checkSecondArg) {
    957                     // read next values and store them in the map as Attribute
    958                     for (int i = 2; i < contents.length; i ++) {
    959                         String[] values = contents[i].split("=");
    960                         Attribute attribute;
    961                         try {
    962                             attribute = factory.createAttribute(request, values[0].trim(),
    963                                     decodeAttribute(cleanString(values[1]), charset));
    964                         } catch (NullPointerException e) {
    965                             throw new ErrorDataDecoderException(e);
    966                         } catch (IllegalArgumentException e) {
    967                             throw new ErrorDataDecoderException(e);
    968                         }
    969                         currentFieldAttributes.put(attribute.getName(),
    970                                 attribute);
    971                     }
    972                 }
    973             } else if (contents[0]
    974                     .equalsIgnoreCase(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) {
    975                 Attribute attribute;
    976                 try {
    977                     attribute = factory.createAttribute(request,
    978                             HttpHeaders.Names.CONTENT_TRANSFER_ENCODING,
    979                             cleanString(contents[1]));
    980                 } catch (NullPointerException e) {
    981                     throw new ErrorDataDecoderException(e);
    982                 } catch (IllegalArgumentException e) {
    983                     throw new ErrorDataDecoderException(e);
    984                 }
    985                 currentFieldAttributes.put(
    986                         HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, attribute);
    987             } else if (contents[0]
    988                     .equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) {
    989                 Attribute attribute;
    990                 try {
    991                     attribute = factory.createAttribute(request,
    992                             HttpHeaders.Names.CONTENT_LENGTH,
    993                             cleanString(contents[1]));
    994                 } catch (NullPointerException e) {
    995                     throw new ErrorDataDecoderException(e);
    996                 } catch (IllegalArgumentException e) {
    997                     throw new ErrorDataDecoderException(e);
    998                 }
    999                 currentFieldAttributes.put(HttpHeaders.Names.CONTENT_LENGTH,
    1000                         attribute);
    1001             } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) {
    1002                 // Take care of possible "multipart/mixed"
    1003                 if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) {
    1004                     if (currentStatus == MultiPartStatus.DISPOSITION) {
    1005                         String[] values = contents[2].split("=");
    1006                         multipartMixedBoundary = "--" + values[1];
    1007                         currentStatus = MultiPartStatus.MIXEDDELIMITER;
    1008                         return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
    1009                     } else {
    1010                         throw new ErrorDataDecoderException(
    1011                                 "Mixed Multipart found in a previous Mixed Multipart");
    1012                     }
    1013                 } else {
    1014                     for (int i = 1; i < contents.length; i ++) {
    1015                         if (contents[i].toLowerCase().startsWith(
    1016                                 HttpHeaders.Values.CHARSET)) {
    1017                             String[] values = contents[i].split("=");
    1018                             Attribute attribute;
    1019                             try {
    1020                                 attribute = factory.createAttribute(request,
    1021                                         HttpHeaders.Values.CHARSET,
    1022                                         cleanString(values[1]));
    1023                             } catch (NullPointerException e) {
    1024                                 throw new ErrorDataDecoderException(e);
    1025                             } catch (IllegalArgumentException e) {
    1026                                 throw new ErrorDataDecoderException(e);
    1027                             }
    1028                             currentFieldAttributes.put(HttpHeaders.Values.CHARSET,
    1029                                     attribute);
    1030                         } else {
    1031                             Attribute attribute;
    1032                             try {
    1033                                 attribute = factory.createAttribute(request,
    1034                                         contents[0].trim(),
    1035                                         decodeAttribute(cleanString(contents[i]), charset));
    1036                             } catch (NullPointerException e) {
    1037                                 throw new ErrorDataDecoderException(e);
    1038                             } catch (IllegalArgumentException e) {
    1039                                 throw new ErrorDataDecoderException(e);
    1040                             }
    1041                             currentFieldAttributes.put(attribute.getName(),
    1042                                     attribute);
    1043                         }
    1044                     }
    1045                 }
    1046             } else {
    1047                 throw new ErrorDataDecoderException("Unknown Params: " +
    1048                         newline);
    1049             }
    1050         }
    1051         // Is it a FileUpload
    1052         Attribute filenameAttribute = currentFieldAttributes
    1053                 .get(HttpPostBodyUtil.FILENAME);
    1054         if (currentStatus == MultiPartStatus.DISPOSITION) {
    1055             if (filenameAttribute != null) {
    1056                 // FileUpload
    1057                 currentStatus = MultiPartStatus.FILEUPLOAD;
    1058                 // do not change the buffer position
    1059                 return decodeMultipart(MultiPartStatus.FILEUPLOAD);
    1060             } else {
    1061                 // Field
    1062                 currentStatus = MultiPartStatus.FIELD;
    1063                 // do not change the buffer position
    1064                 return decodeMultipart(MultiPartStatus.FIELD);
    1065             }
    1066         } else {
    1067             if (filenameAttribute != null) {
    1068                 // FileUpload
    1069                 currentStatus = MultiPartStatus.MIXEDFILEUPLOAD;
    1070                 // do not change the buffer position
    1071                 return decodeMultipart(MultiPartStatus.MIXEDFILEUPLOAD);
    1072             } else {
    1073                 // Field is not supported in MIXED mode
    1074                 throw new ErrorDataDecoderException("Filename not found");
    1075             }
    1076         }
    1077     }
    1078
    1079     /**
    1080      * Get the FileUpload (new one or current one)
    1081      * @param delimiter the delimiter to use
    1082      * @return the InterfaceHttpData if any
    1083      * @throws ErrorDataDecoderException
    1084      */
    1085     private InterfaceHttpData getFileUpload(String delimiter)
    1086             throws ErrorDataDecoderException {
    1087         // eventually restart from existing FileUpload
    1088         // Now get value according to Content-Type and Charset
    1089         Attribute encoding = currentFieldAttributes
    1090                 .get(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING);
    1091         Charset localCharset = charset;
    1092         // Default
    1093         TransferEncodingMechanism mechanism = TransferEncodingMechanism.BIT7;
    1094         if (encoding != null) {
    1095             String code;
    1096             try {
    1097                 code = encoding.getValue().toLowerCase();
    1098             } catch (IOException e) {
    1099                 throw new ErrorDataDecoderException(e);
    1100             }
    1101             if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT7.value)) {
    1102                 localCharset = HttpPostBodyUtil.US_ASCII;
    1103             } else if (code.equals(HttpPostBodyUtil.TransferEncodingMechanism.BIT8.value)) {
    1104                 localCharset = HttpPostBodyUtil.ISO_8859_1;
    1105                 mechanism = TransferEncodingMechanism.BIT8;
    1106             } else if (code
    1107                     .equals(HttpPostBodyUtil.TransferEncodingMechanism.BINARY.value)) {
    1108                 // no real charset, so let the default
    1109                 mechanism = TransferEncodingMechanism.BINARY;
    1110             } else {
    1111                 throw new ErrorDataDecoderException(
    1112                         "TransferEncoding Unknown: " + code);
    1113             }
    1114         }
    1115         Attribute charsetAttribute = currentFieldAttributes
    1116                 .get(HttpHeaders.Values.CHARSET);
    1117         if (charsetAttribute != null) {
    1118             try {
    1119                 localCharset = Charset.forName(charsetAttribute.getValue());
    1120             } catch (IOException e) {
    1121                 throw new ErrorDataDecoderException(e);
    1122             }
    1123         }
    1124         if (currentFileUpload == null) {
    1125             Attribute filenameAttribute = currentFieldAttributes
    1126                     .get(HttpPostBodyUtil.FILENAME);
    1127             Attribute nameAttribute = currentFieldAttributes
    1128                     .get(HttpPostBodyUtil.NAME);
    1129             Attribute contentTypeAttribute = currentFieldAttributes
    1130                     .get(HttpHeaders.Names.CONTENT_TYPE);
    1131             if (contentTypeAttribute == null) {
    1132                 throw new ErrorDataDecoderException(
    1133                         "Content-Type is absent but required");
    1134             }
    1135             Attribute lengthAttribute = currentFieldAttributes
    1136                     .get(HttpHeaders.Names.CONTENT_LENGTH);
    1137             long size;
    1138             try {
    1139                 size = lengthAttribute != null? Long.parseLong(lengthAttribute
    1140                         .getValue()) : 0L;
    1141             } catch (IOException e) {
    1142                 throw new ErrorDataDecoderException(e);
    1143             } catch (NumberFormatException e) {
    1144                 size = 0;
    1145             }
    1146             try {
    1147                 currentFileUpload = factory.createFileUpload(
    1148                         request,
    1149                         nameAttribute.getValue(), filenameAttribute.getValue(),
    1150                         contentTypeAttribute.getValue(), mechanism.value,
    1151                         localCharset, size);
    1152             } catch (NullPointerException e) {
    1153                 throw new ErrorDataDecoderException(e);
    1154             } catch (IllegalArgumentException e) {
    1155                 throw new ErrorDataDecoderException(e);
    1156             } catch (IOException e) {
    1157                 throw new ErrorDataDecoderException(e);
    1158             }
    1159         }
    1160         // load data as much as possible
    1161         try {
    1162             readFileUploadByteMultipart(delimiter);
    1163         } catch (NotEnoughDataDecoderException e) {
    1164             // do not change the buffer position
    1165             // since some can be already saved into FileUpload
    1166             // So do not change the currentStatus
    1167             return null;
    1168         }
    1169         if (currentFileUpload.isCompleted()) {
    1170             // ready to load the next one
    1171             if (currentStatus == MultiPartStatus.FILEUPLOAD) {
    1172                 currentStatus = MultiPartStatus.HEADERDELIMITER;
    1173                 currentFieldAttributes = null;
    1174             } else {
    1175                 currentStatus = MultiPartStatus.MIXEDDELIMITER;
    1176                 cleanMixedAttributes();
    1177             }
    1178             FileUpload fileUpload = currentFileUpload;
    1179             currentFileUpload = null;
    1180             return fileUpload;
    1181         }
    1182         // do not change the buffer position
    1183         // since some can be already saved into FileUpload
    1184         // So do not change the currentStatus
    1185         return null;
    1186     }
    1187
    1188     /**
    1189      * Clean all HttpDatas (on Disk) for the current request.
    1190  */
    1191     public void cleanFiles() {
    1192         factory.cleanRequestHttpDatas(request);
    1193     }
    1194
    1195     /**
    1196      * Remove the given FileUpload from the list of FileUploads to clean
    1197      */
    1198     public void removeHttpDataFromClean(InterfaceHttpData data) {
    1199         factory.removeHttpDataFromClean(request, data);
    1200     }
    1201
    1202     /**
    1203      * Remove all Attributes that should be cleaned between two FileUpload in Mixed mode
    1204      */
    1205     private void cleanMixedAttributes() {
    1206         currentFieldAttributes.remove(HttpHeaders.Values.CHARSET);
    1207         currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_LENGTH);
    1208         currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING);
    1209         currentFieldAttributes.remove(HttpHeaders.Names.CONTENT_TYPE);
    1210         currentFieldAttributes.remove(HttpPostBodyUtil.FILENAME);
    1211     }
    1212
    1213     /**
    1214      * Read one line up to the CRLF or LF
    1215      * @return the String from one line
    1216      * @throws NotEnoughDataDecoderException Need more chunks and
    1217      *   reset the readerInder to the previous value
    1218      */
    1219     private String readLineStandard() throws NotEnoughDataDecoderException {
    1220         int readerIndex = undecodedChunk.readerIndex();
    1221         try {
    1222             StringBuilder sb = new StringBuilder(64);
    1223             while (undecodedChunk.readable()) {
    1224                 byte nextByte = undecodedChunk.readByte();
    1225                 if (nextByte == HttpConstants.CR) {
    1226                     nextByte = undecodedChunk.readByte();
    1227                     if (nextByte == HttpConstants.LF) {
    1228                         return sb.toString();
    1229                     }
    1230                 } else if (nextByte == HttpConstants.LF) {
    1231                     return sb.toString();
    1232                 } else {
    1233                     sb.append((char) nextByte);
    1234                 }
    1235             }
    1236         } catch (IndexOutOfBoundsException e) {
    1237             undecodedChunk.readerIndex(readerIndex);
    1238             throw new NotEnoughDataDecoderException(e);
    1239         }
    1240         undecodedChunk.readerIndex(readerIndex);
    1241         throw new NotEnoughDataDecoderException();
    1242     }
    1243     /**
    1244      * Read one line up to the CRLF or LF
    1245      * @return the String from one line
    1246      * @throws NotEnoughDataDecoderException Need more chunks and
    1247      * reset the readerInder to the previous value
    1248      */
    1249     private String readLine() throws NotEnoughDataDecoderException {
    1250         SeekAheadOptimize sao = null;
    1251         try {
    1252             sao = new SeekAheadOptimize(undecodedChunk);
    1253         } catch (SeekAheadNoBackArrayException e1) {
    1254             return readLineStandard();
    1255         }
    1256         int readerIndex = undecodedChunk.readerIndex();
    1257         try {
    1258             StringBuilder sb = new StringBuilder(64);
    1259             while (sao.pos < sao.limit) {
    1260                 byte nextByte = sao.bytes[sao.pos ++];
    1261                 if (nextByte == HttpConstants.CR) {
    1262                     if (sao.pos < sao.limit) {
    1263                         nextByte = sao.bytes[sao.pos ++];
    1264                         if (nextByte == HttpConstants.LF) {
    1265                             sao.setReadPosition(0);
    1266                             return sb.toString();
    1267                         }
    1268                     } else {
    1269                         sb.append((char) nextByte);
    1270                     }
    1271                 } else if (nextByte == HttpConstants.LF) {
    1272                     sao.setReadPosition(0);
    1273                     return sb.toString();
    1274                 } else {
    1275                     sb.append((char) nextByte);
    1276                 }
    1277             }
    1278         } catch (IndexOutOfBoundsException e) {
    1279             undecodedChunk.readerIndex(readerIndex);
    1280             throw new NotEnoughDataDecoderException(e);
    1281         }
    1282         undecodedChunk.readerIndex(readerIndex);
    1283         throw new NotEnoughDataDecoderException();
    1284     }
    1285
    1286     /**
    1287      * Read one line up to --delimiter or --delimiter-- and if existing the CRLF or LF
    1288      * Read one line up to --delimiter or --delimiter-- and if existing the CRLF or LF.
    1289      * Note that CRLF or LF are mandatory for opening delimiter (--delimiter) but not for
    1290      * closing delimiter (--delimiter--) since some clients does not include CRLF in this case.
    1291      *
    1292      * @param delimiter of the form --string, such that '--' is already included
    1293      * @return the String from one line as the delimiter searched (opening or closing)
    1294      * @throws NotEnoughDataDecoderException Need more chunks and
    1295      *   reset the readerInder to the previous value
    1296      */
    1297     private String readDelimiterStandard(String delimiter) throws NotEnoughDataDecoderException {
    1298         int readerIndex = undecodedChunk.readerIndex();
    1299         try {
    1300             StringBuilder sb = new StringBuilder(64);
    1301             int delimiterPos = 0;
    1302             int len = delimiter.length();
    1303             while (undecodedChunk.readable() && delimiterPos < len) {
    1304                 byte nextByte = undecodedChunk.readByte();
    1305                 if (nextByte == delimiter.charAt(delimiterPos)) {
    1306                     delimiterPos++;
    1307                     sb.append((char) nextByte);
    1308                 } else {
    1309                     // delimiter not found so break here !
    1310                     undecodedChunk.readerIndex(readerIndex);
    1311                     throw new NotEnoughDataDecoderException();
    1312                 }
    1313             }
    1314             // Now check if either opening delimiter or closing delimiter
    1315             if (undecodedChunk.readable()) {
    1316                 byte nextByte = undecodedChunk.readByte();
    1317                 // first check for opening delimiter
    1318                 if (nextByte == HttpConstants.CR) {
    1319                     nextByte = undecodedChunk.readByte();
    1320                     if (nextByte == HttpConstants.LF) {
    1321                         return sb.toString();
    1322                     } else {
    1323                         // error since CR must be followed by LF
    1324                         // delimiter not found so break here !
    1325                         undecodedChunk.readerIndex(readerIndex);
    1326                         throw new NotEnoughDataDecoderException();
    1327                     }
    1328                 } else if (nextByte == HttpConstants.LF) {
    1329                     return sb.toString();
    1330                 } else if (nextByte == '-') {
    1331                     sb.append((char) nextByte);
    1332                     // second check for closing delimiter
    1333                     nextByte = undecodedChunk.readByte();
    1334                     if (nextByte == '-') {
    1335                         sb.append((char) nextByte);
    1336                         // now try to find if CRLF or LF there
    1337                         if (undecodedChunk.readable()) {
    1338                             nextByte = undecodedChunk.readByte();
    1339                             if (nextByte == HttpConstants.CR) {
    1340                                 nextByte = undecodedChunk.readByte();
    1341                                 if (nextByte == HttpConstants.LF) {
    1342                                     return sb.toString();
    1343                                 } else {
    1344                                     // error CR without LF
    1345                                     // delimiter not found so break here !
    1346                                    undecodedChunk.readerIndex(readerIndex);
    1347                                     throw new NotEnoughDataDecoderException();
    1348                                 }
    1349                             } else if (nextByte == HttpConstants.LF) {
    1350                                 return sb.toString();
    1351                             } else {
    1352                                 // No CRLF but ok however (Adobe Flash uploader)
    1353                                 // minus 1 since we read one char ahead but should not
    1354                                 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
    1355                                 return sb.toString();
    1356                             }
    1357                         }
    1358                         // FIXME what do we do here?
    1359                         // either considering it is fine, either waiting for more data to come?
    1360                         // lets try considering it is fine...
    1361                         return sb.toString();
    1362                     }
    1363                     // only one '-' => not enough
    1364                     // whatever now => error since incomplete
    1365                 }
    1366             }
    1367         } catch (IndexOutOfBoundsException e) {
    1368             undecodedChunk.readerIndex(readerIndex);
    1369             throw new NotEnoughDataDecoderException(e);
    1370         }
    1371         undecodedChunk.readerIndex(readerIndex);
    1372         throw new NotEnoughDataDecoderException();
    1373     }
    1374
    1375     /**
    1376      * Read one line up to --delimiter or --delimiter-- and if existing the CRLF or LF.
    1377      * Note that CRLF or LF are mandatory for opening delimiter (--delimiter) but not for
    1378      * closing delimiter (--delimiter--) since some clients does not include CRLF in this case.
    1379      *
    1380      * @param delimiter of the form --string, such that '--' is already included
    1381      * @return the String from one line as the delimiter searched (opening or closing)
    1382      * @throws NotEnoughDataDecoderException Need more chunks and
    1383      * reset the readerInder to the previous value
    1384      */
    1385     private String readDelimiter(String delimiter) throws NotEnoughDataDecoderException {
    1386         SeekAheadOptimize sao = null;
    1387         try {
    1388             sao = new SeekAheadOptimize(undecodedChunk);
    1389         } catch (SeekAheadNoBackArrayException e1) {
    1390             return readDelimiterStandard(delimiter);
    1391         }
    1392         int readerIndex = undecodedChunk.readerIndex();
    1393         int delimiterPos = 0;
    1394         int len = delimiter.length();
    1395         try {
    1396             StringBuilder sb = new StringBuilder(64);
    1397             // check conformity with delimiter
    1398             while (sao.pos < sao.limit && delimiterPos < len) {
    1399                 byte nextByte = sao.bytes[sao.pos ++];
    1400                 if (nextByte == delimiter.charAt(delimiterPos)) {
    1401                     delimiterPos++;
    1402                     sb.append((char) nextByte);
    1403                 } else {
    1404                     // delimiter not found so break here !
    1405                     undecodedChunk.readerIndex(readerIndex);
    1406                     throw new NotEnoughDataDecoderException();
    1407                 }
    1408             }
    1409             // Now check if either opening delimiter or closing delimiter
    1410             if (sao.pos < sao.limit) {
    1411                 byte nextByte = sao.bytes[sao.pos ++];
    1412                 if (nextByte == HttpConstants.CR) {
    1413                     // first check for opening delimiter
    1414                     if (sao.pos < sao.limit) {
    1415                         nextByte = sao.bytes[sao.pos ++];
    1416                         if (nextByte == HttpConstants.LF) {
    1417                             sao.setReadPosition(0);
    1418                             return sb.toString();
    1419                         }
    1420                     } else {
    1421                         // error since CR must be followed by LF
    1422                         // delimiter not found so break here !
    1423                         undecodedChunk.readerIndex(readerIndex);
    1424                         throw new NotEnoughDataDecoderException();
    1425                     }
    1426                 } else if (nextByte == HttpConstants.LF) {
    1427                     // same first check for opening delimiter where LF used with no CR
    1428                     sao.setReadPosition(0);
    1429                     return sb.toString();
    1430                 } else if (nextByte == '-') {
    1431                     sb.append((char) nextByte);
    1432                     // second check for closing delimiter
    1433                     if (sao.pos < sao.limit) {
    1434                         nextByte = sao.bytes[sao.pos ++];
    1435                         if (nextByte == '-') {
    1436                             sb.append((char) nextByte);
    1437                             // now try to find if CRLF or LF there
    1438                             if (sao.pos < sao.limit) {
    1439                                 nextByte = sao.bytes[sao.pos++];
    1440                                 if (nextByte == HttpConstants.CR) {
    1441                                     if (sao.pos < sao.limit) {
    1442                                         nextByte = sao.bytes[sao.pos++];
    1443                                         if (nextByte == HttpConstants.LF) {
    1444                                             sao.setReadPosition(0);
    1445                                             return sb.toString();
    1446                                         }
    1447                                     } else {
    1448                                         // error CR without LF
    1449                                         // delimiter not found so break here !
    1450                                         undecodedChunk.readerIndex(readerIndex);
    1451                                         throw new NotEnoughDataDecoderException();
    1452                                     }
    1453                                 } else if (nextByte == HttpConstants.LF) {
    1454                                     sao.setReadPosition(0);
    1455                                     return sb.toString();
    1456                                 } else {
    1457                                     // No CRLF but ok however (Adobe Flash uploader)
    1458                                     // minus 1 since we read one char ahead but should not
    1459                                     sao.setReadPosition(1);
    1460                                     return sb.toString();
    1461                                 }
    1462                             }
    1463                             // FIXME what do we do here?
    1464                             // either considering it is fine, either waiting for more data to come?
    1465                             // lets try considering it is fine...
    1466                             sao.setReadPosition(0);
    1467                             return sb.toString();
    1468                         }
    1469                         // whatever now => error since incomplete
    1470                         // only one '-' => not enough or whatever not enough element
    1471                     }
    1472                 }
    1473             }
    1474         } catch (IndexOutOfBoundsException e) {
    1475             undecodedChunk.readerIndex(readerIndex);
    1476             throw new NotEnoughDataDecoderException(e);
    1477         }
    1478         undecodedChunk.readerIndex(readerIndex);
    1479         throw new NotEnoughDataDecoderException();
    1480     }
    1481
    1482     /**
    1483      * Read a FileUpload data as Byte (Binary) and add the bytes directly to the
    1484      * FileUpload. If the delimiter is found, the FileUpload is completed.
    1485      * @param delimiter
    1486      * @throws NotEnoughDataDecoderException Need more chunks but
    1487      *   do not reset the readerInder since some values will be already added to the FileOutput
    1488      * @throws ErrorDataDecoderException write IO error occurs with the FileUpload
    1489      */
    1490     private void readFileUploadByteMultipartStandard(String delimiter)
    1491             throws NotEnoughDataDecoderException, ErrorDataDecoderException {
    1492         int readerIndex = undecodedChunk.readerIndex();
    1493         // found the decoder limit
    1494         boolean newLine = true;
    1495         int index = 0;
    1496         int lastPosition = undecodedChunk.readerIndex();
    1497         boolean found = false;
    1498         while (undecodedChunk.readable()) {
    1499             byte nextByte = undecodedChunk.readByte();
    1500             if (newLine) {
    1501                 // Check the delimiter
    1502                 if (nextByte == delimiter.codePointAt(index)) {
    1503                     index ++;
    1504                     if (delimiter.length() == index) {
    1505                         found = true;
    1506                         break;
    1507                     }
    1508                     continue;
    1509                 } else {
    1510                     newLine = false;
    1511                     index = 0;
    1512                     // continue until end of line
    1513                     if (nextByte == HttpConstants.CR) {
    1514                         if (undecodedChunk.readable()) {
    1515                             nextByte = undecodedChunk.readByte();
    1516                             if (nextByte == HttpConstants.LF) {
    1517                                 newLine = true;
    1518                                 index = 0;
    1519                                 lastPosition = undecodedChunk.readerIndex() - 2;
    1520                             }
    1521                         }
    1522                     } else if (nextByte == HttpConstants.LF) {
    1523                         newLine = true;
    1524                         index = 0;
    1525                         lastPosition = undecodedChunk.readerIndex() - 1;
    1526                     } else {
    1527                         // save last valid position
    1528                         lastPosition = undecodedChunk.readerIndex();
    1529                     }
    1530                 }
    1531             } else {
    1532                 // continue until end of line
    1533                 if (nextByte == HttpConstants.CR) {
    1534                     if (undecodedChunk.readable()) {
    1535                         nextByte = undecodedChunk.readByte();
    1536                         if (nextByte == HttpConstants.LF) {
    1537                             newLine = true;
    1538                             index = 0;
    1539                             lastPosition = undecodedChunk.readerIndex() - 2;
    1540                         }
    1541                     }
    1542                 } else if (nextByte == HttpConstants.LF) {
    1543                     newLine = true;
    1544                     index = 0;
    1545                     lastPosition = undecodedChunk.readerIndex() - 1;
    1546                 } else {
    1547                     // save last valid position
    1548                     lastPosition = undecodedChunk.readerIndex();
    1549                 }
    1550             }
    1551         }
    1552         ChannelBuffer buffer = undecodedChunk.slice(readerIndex, lastPosition -
    1553                 readerIndex);
    1554         if (found) {
    1555             // found so lastPosition is correct and final
    1556             try {
    1557                 currentFileUpload.addContent(buffer, true);
    1558                 // just before the CRLF and delimiter
    1559                 undecodedChunk.readerIndex(lastPosition);
    1560             } catch (IOException e) {
    1561                 throw new ErrorDataDecoderException(e);
    1562             }
    1563         } else {
    1564             // possibly the delimiter is partially found but still the last position is OK
    1565             try {
    1566                 currentFileUpload.addContent(buffer, false);
    1567                 // last valid char (not CR, not LF, not beginning of delimiter)
    1568                 undecodedChunk.readerIndex(lastPosition);
    1569                 throw new NotEnoughDataDecoderException();
    1570             } catch (IOException e) {
    1571                 throw new ErrorDataDecoderException(e);
    1572             }
    1573         }
    1574     }
    1575
    1576     /**
    1577      * Read a FileUpload data as Byte (Binary) and add the bytes directly to the
    1578      * FileUpload. If the delimiter is found, the FileUpload is completed.
    1579      * @param delimiter
    1580      * @throws NotEnoughDataDecoderException Need more chunks but
    1581      * do not reset the readerInder since some values will be already added to the FileOutput
    1582      * @throws ErrorDataDecoderException write IO error occurs with the FileUpload
    1583      */
    1584     private void readFileUploadByteMultipart(String delimiter)
    1585             throws NotEnoughDataDecoderException, ErrorDataDecoderException {
    1586         SeekAheadOptimize sao = null;
    1587         try {
    1588             sao = new SeekAheadOptimize(undecodedChunk);
    1589         } catch (SeekAheadNoBackArrayException e1) {
    1590             readFileUploadByteMultipartStandard(delimiter);
    1591             return;
    1592         }
    1593         int readerIndex = undecodedChunk.readerIndex();
    1594         // found the decoder limit
    1595         boolean newLine = true;
    1596         int index = 0;
    1597         int lastPosition = undecodedChunk.readerIndex();
    1598         int setReadPosition = -1;
    1599         boolean found = false;
    1600
    1601         while (sao.pos < sao.limit) {
    1602             byte nextByte = sao.bytes[sao.pos ++];
    1603             if (newLine) {
    1604                 // Check the delimiter
    1605                 if (nextByte == delimiter.codePointAt(index)) {
    1606                     index ++;
    1607                     if (delimiter.length() == index) {
    1608                         found = true;
    1609                         sao.setReadPosition(0);
    1610                         break;
    1611                     }
    1612                     continue;
    1613                 } else {
    1614                     newLine = false;
    1615                     index = 0;
    1616                     // continue until end of line
    1617                     if (nextByte == HttpConstants.CR) {
    1618                         if (sao.pos < sao.limit) {
    1619                             nextByte = sao.bytes[sao.pos ++];
    1620                             if (nextByte == HttpConstants.LF) {
    1621                                 newLine = true;
    1622                                 index = 0;
    1623                                 setReadPosition = sao.pos;
    1624                                 lastPosition = sao.pos - 2;
    1625                             }
    1626                         } else {
    1627                             // save last valid position
    1628                             setReadPosition = sao.pos;
    1629                             lastPosition = sao.pos;
    1630                         }
    1631                     } else if (nextByte == HttpConstants.LF) {
    1632                         newLine = true;
    1633                         index = 0;
    1634                         setReadPosition = sao.pos;
    1635                         lastPosition = sao.pos - 1;
    1636                     } else {
    1637                         // save last valid position
    1638                         setReadPosition = sao.pos;
    1639                         lastPosition = sao.pos;
    1640                     }
    1641                 }
    1642             } else {
    1643                 // continue until end of line
    1644                 if (nextByte == HttpConstants.CR) {
    1645                     if (sao.pos < sao.limit) {
    1646                         nextByte = sao.bytes[sao.pos ++];
    1647                         if (nextByte == HttpConstants.LF) {
    1648                             newLine = true;
    1649                             index = 0;
    1650                             setReadPosition = sao.pos;
    1651                             lastPosition = sao.pos - 2;
    1652                         }
    1653                     } else {
    1654                         // save last valid position
    1655                         setReadPosition = sao.pos;
    1656                         lastPosition = sao.pos;
    1657                     }
    1658                 } else if (nextByte == HttpConstants.LF) {
    1659                     newLine = true;
    1660                     index = 0;
    1661                     setReadPosition = sao.pos;
    1662                     lastPosition = sao.pos - 1;
    1663                 } else {
    1664                     // save last valid position
    1665                     setReadPosition = sao.pos;
    1666                     lastPosition = sao.pos;
    1667                 }
    1668             }
    1669         }
    1670         if (setReadPosition > 0) {
    1671             sao.pos = setReadPosition;
    1672             sao.setReadPosition(0);
    1673         }
    1674         ChannelBuffer buffer = undecodedChunk.slice(readerIndex, lastPosition - readerIndex);
    1675         if (found) {
    1676             // found so lastPosition is correct and final
    1677             try {
    1678                 currentFileUpload.addContent(buffer, true);
    1679                 // just before the CRLF and delimiter
    1680                 undecodedChunk.readerIndex(lastPosition);
    1681             } catch (IOException e) {
    1682                 throw new ErrorDataDecoderException(e);
    1683             }
    1684         } else {
    1685             // possibly the delimiter is partially found but still the last position is OK
    1686             try {
    1687                 currentFileUpload.addContent(buffer, false);
    1688                 // last valid char (not CR, not LF, not beginning of delimiter)
    1689                 undecodedChunk.readerIndex(lastPosition);
    1690                 throw new NotEnoughDataDecoderException();
    1691             } catch (IOException e) {
    1692                 throw new ErrorDataDecoderException(e);
    1693             }
    1694         }
    1695     }
    1696
    1697     /**
    1698      * Load the field value from a Multipart request
    1699      * @throws NotEnoughDataDecoderException Need more chunks
    1700      * @throws ErrorDataDecoderException
    1701      */
    1702     private void loadFieldMultipartStandard(String delimiter)
    1703             throws NotEnoughDataDecoderException, ErrorDataDecoderException {
    1704         int readerIndex = undecodedChunk.readerIndex();
    1705         try {
    1706             // found the decoder limit
    1707             boolean newLine = true;
    1708             int index = 0;
    1709             int lastPosition = undecodedChunk.readerIndex();
    1710             boolean found = false;
    1711             while (undecodedChunk.readable()) {
    1712                 byte nextByte = undecodedChunk.readByte();
    1713                 if (newLine) {
    1714                     // Check the delimiter
    1715                     if (nextByte == delimiter.codePointAt(index)) {
    1716                         index ++;
    1717                         if (delimiter.length() == index) {
    1718                             found = true;
    1719                             break;
    1720                         }
    1721                         continue;
    1722                     } else {
    1723                         newLine = false;
    1724                         index = 0;
    1725                         // continue until end of line
    1726                         if (nextByte == HttpConstants.CR) {
    1727                             if (undecodedChunk.readable()) {
    1728                                 nextByte = undecodedChunk.readByte();
    1729                                 if (nextByte == HttpConstants.LF) {
    1730                                     newLine = true;
    1731                                     index = 0;
    1732                                     lastPosition = undecodedChunk.readerIndex() - 2;
    1733                                 }
    1734                             }
    1735                         } else if (nextByte == HttpConstants.LF) {
    1736                             newLine = true;
    1737                             index = 0;
    1738                             lastPosition = undecodedChunk.readerIndex() - 1;
    1739                         } else {
    1740                             lastPosition = undecodedChunk.readerIndex();
    1741                         }
    1742                     }
    1743                 } else {
    1744                     // continue until end of line
    1745                     if (nextByte == HttpConstants.CR) {
    1746                         if (undecodedChunk.readable()) {
    1747                             nextByte = undecodedChunk.readByte();
    1748                             if (nextByte == HttpConstants.LF) {
    1749                                 newLine = true;
    1750                                 index = 0;
    1751                                 lastPosition = undecodedChunk.readerIndex() - 2;
    1752                             }
    1753                         }
    1754                     } else if (nextByte == HttpConstants.LF) {
    1755                         newLine = true;
    1756                         index = 0;
    1757                         lastPosition = undecodedChunk.readerIndex() - 1;
    1758                     } else {
    1759                         lastPosition = undecodedChunk.readerIndex();
    1760                     }
    1761                 }
    1762             }
    1763             if (found) {
    1764                 // found so lastPosition is correct
    1765                 // but position is just after the delimiter (either close delimiter or simple one)
    1766                 // so go back of delimiter size
    1767                 try {
    1768                     currentAttribute.addContent(
    1769                             undecodedChunk.slice(readerIndex, lastPosition - readerIndex),
    1770                             true);
    1771                 } catch (IOException e) {
    1772                     throw new ErrorDataDecoderException(e);
    1773                 }
    1774                 undecodedChunk.readerIndex(lastPosition);
    1775             } else {
    1776                 try {
    1777                     currentAttribute.addContent(
    1778                             undecodedChunk.slice(readerIndex, lastPosition - readerIndex),
    1779                             false);
    1780                 } catch (IOException e) {
    1781                     throw new ErrorDataDecoderException(e);
    1782                 }
    1783                 undecodedChunk.readerIndex(lastPosition);
    1784                 throw new NotEnoughDataDecoderException();
    1785             }
    1786         } catch (IndexOutOfBoundsException e) {
    1787             undecodedChunk.readerIndex(readerIndex);
    1788             throw new NotEnoughDataDecoderException(e);
    1789         }
    1790     }
    1791
    1792     /**
    1793      * Load the field value from a Multipart request
    1794      * @throws NotEnoughDataDecoderException Need more chunks
    1795      * @throws ErrorDataDecoderException
    1796      */
    1797     private void loadFieldMultipart(String delimiter)
    1798             throws NotEnoughDataDecoderException, ErrorDataDecoderException {
    1799         SeekAheadOptimize sao = null;
    1800         try {
    1801             sao = new SeekAheadOptimize(undecodedChunk);
    1802         } catch (SeekAheadNoBackArrayException e1) {
    1803             loadFieldMultipartStandard(delimiter);
    1804             return;
    1805         }
    1806         int readerIndex = undecodedChunk.readerIndex();
    1807         try {
    1808             // found the decoder limit
    1809             boolean newLine = true;
    1810             int index = 0;
    1811             int lastPosition = undecodedChunk.readerIndex();
    1812             int setReadPosition = -1;
    1813             boolean found = false;
    1814
    1815             while (sao.pos < sao.limit) {
    1816                 byte nextByte = sao.bytes[sao.pos ++];
    1817                 if (newLine) {
    1818                     // Check the delimiter
    1819                     if (nextByte == delimiter.codePointAt(index)) {
    1820                         index ++;
    1821                         if (delimiter.length() == index) {
    1822                             found = true;
    1823                             sao.setReadPosition(0);
    1824                             break;
    1825                         }
    1826                         continue;
    1827                     } else {
    1828                         newLine = false;
    1829                         index = 0;
    1830                         // continue until end of line
    1831                         if (nextByte == HttpConstants.CR) {
    1832                             if (sao.pos < sao.limit) {
    1833                                 nextByte = sao.bytes[sao.pos ++];
    1834                                 if (nextByte == HttpConstants.LF) {
    1835                                     newLine = true;
    1836                                     index = 0;
    1837                                     lastPosition = sao.pos - 2;
    1838                                     setReadPosition = sao.pos;
    1839                                 }
    1840                             } else {
    1841                                 lastPosition = sao.pos;
    1842                                 setReadPosition = sao.pos;
    1843                             }
    1844                         } else if (nextByte == HttpConstants.LF) {
    1845                             newLine = true;
    1846                             index = 0;
    1847                             lastPosition = sao.pos - 1;
    1848                             setReadPosition = sao.pos;
    1849                         } else {
    1850                             lastPosition = sao.pos;
    1851                             setReadPosition = sao.pos;
    1852                         }
    1853                     }
    1854                 } else {
    1855                     // continue until end of line
    1856                     if (nextByte == HttpConstants.CR) {
    1857                         if (sao.pos < sao.limit) {
    1858                             nextByte = sao.bytes[sao.pos ++];
    1859                             if (nextByte == HttpConstants.LF) {
    1860                                 newLine = true;
    1861                                 index = 0;
    1862                                 lastPosition = sao.pos - 2;
    1863                                 setReadPosition = sao.pos;
    1864                             }
    1865                         } else {
    1866                             lastPosition = sao.pos;
    1867                             setReadPosition = sao.pos;
    1868                         }
    1869                     } else if (nextByte == HttpConstants.LF) {
    1870                         newLine = true;
    1871                         index = 0;
    1872                         lastPosition = sao.pos - 1;
    1873                         setReadPosition = sao.pos;
    1874                     } else {
    1875                         lastPosition = sao.pos;
    1876                         setReadPosition = sao.pos;
    1877                     }
    1878                 }
    1879             }
    1880             if (setReadPosition > 0) {
    1881                 sao.pos = setReadPosition;
    1882                 sao.setReadPosition(0);
    1883             }
    1884             if (found) {
    1885                 // found so lastPosition is correct
    1886                 // but position is just after the delimiter (either close delimiter or simple one)
    1887                 // so go back of delimiter size
    1888                 try {
    1889                     currentAttribute.addContent(
    1890                             undecodedChunk.slice(readerIndex, lastPosition - readerIndex), true);
    1891                 } catch (IOException e) {
    1892                     throw new ErrorDataDecoderException(e);
    1893                 }
    1894                 undecodedChunk.readerIndex(lastPosition);
    1895             } else {
    1896                 try {
    1897                     currentAttribute.addContent(
    1898                             undecodedChunk.slice(readerIndex, lastPosition - readerIndex), false);
    1899                 } catch (IOException e) {
    1900                     throw new ErrorDataDecoderException(e);
    1901                 }
    1902                 undecodedChunk.readerIndex(lastPosition);
    1903                 throw new NotEnoughDataDecoderException();
    1904             }
    1905         } catch (IndexOutOfBoundsException e) {
    1906             undecodedChunk.readerIndex(readerIndex);
    1907             throw new NotEnoughDataDecoderException(e);
    1908         }
    1909     }
    1910
    1911     /**
    1912      * Clean the String from any unallowed character
    1913      * @return the cleaned String
    1914      */
    1915     private static String cleanString(String field) {
    1916         StringBuilder sb = new StringBuilder(field.length());
    1917         int i = 0;
    1918         for (i = 0; i < field.length(); i ++) {
    1919             char nextChar = field.charAt(i);
    1920             if (nextChar == HttpConstants.COLON) {
    1921                 sb.append(HttpConstants.SP);
    1922             } else if (nextChar == HttpConstants.COMMA) {
    1923                 sb.append(HttpConstants.SP);
    1924             } else if (nextChar == HttpConstants.EQUALS) {
    1925                 sb.append(HttpConstants.SP);
    1926             } else if (nextChar == HttpConstants.SEMICOLON) {
    1927                 sb.append(HttpConstants.SP);
    1928             } else if (nextChar == HttpConstants.HT) {
    1929                 sb.append(HttpConstants.SP);
    1930             } else if (nextChar == HttpConstants.DOUBLE_QUOTE) {
    1931                 // nothing added, just removes it
    1932             } else {
    1933                 sb.append(nextChar);
    1934             }
    1935         }
    1936         return sb.toString().trim();
    1937     }
    1938
    1939     /**
    1940      * Skip one empty line
    1941      * @return True if one empty line was skipped
    1942      */
    1943     private boolean skipOneLine() {
    1944         if (!undecodedChunk.readable()) {
    1945             return false;
    1946         }
    1947         byte nextByte = undecodedChunk.readByte();
    1948         if (nextByte == HttpConstants.CR) {
    1949             if (!undecodedChunk.readable()) {
    1950                 undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
    1951                 return false;
    1952             }
    1953             nextByte = undecodedChunk.readByte();
    1954             if (nextByte == HttpConstants.LF) {
    1955                 return true;
    1956             }
    1957             undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 2);
    1958             return false;
    1959         } else if (nextByte == HttpConstants.LF) {
    1960             return true;
    1961         }
    1962         undecodedChunk.readerIndex(undecodedChunk.readerIndex() - 1);
    1963         return false;
    1964     }
    1965
    1966     /**
    1967      * Split the very first line (Content-Type value) in 2 Strings
    1968      * @param sb
    1969      * @return the array of 2 Strings
    1970      */
    1971     private static String[] splitHeaderContentType(String sb) {
    1972         int size = sb.length();
    1973         int aStart;
    1974         int aEnd;
    1975         int bStart;
    1976         int bEnd;
    1977         aStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
    1978         aEnd = HttpPostBodyUtil.findWhitespace(sb, aStart);
    1979         if (aEnd >= size) {
    1980             return new String[] { sb, "" };
    1981         }
    1982         if (sb.charAt(aEnd) == ';') {
    1983             aEnd --;
    1984         }
    1985         bStart = HttpPostBodyUtil.findNonWhitespace(sb, aEnd);
    1986         bEnd = HttpPostBodyUtil.findEndOfString(sb);
    1987         return new String[] { sb.substring(aStart, aEnd),
    1988                 sb.substring(bStart, bEnd) };
    1989     }
    1990
    1991     /**
    1992      * Split one header in Multipart
    1993      * @param sb
    1994      * @return an array of String where rank 0 is the name of the header, follows by several
    1995      *  values that were separated by ';' or ','
    1996      */
    1997     private static String[] splitMultipartHeader(String sb) {
    1998         ArrayList<String> headers = new ArrayList<String>(1);
    1999         int nameStart;
    2000         int nameEnd;
    2001         int colonEnd;
    2002         int valueStart;
    2003         int valueEnd;
    2004         nameStart = HttpPostBodyUtil.findNonWhitespace(sb, 0);
    2005         for (nameEnd = nameStart; nameEnd < sb.length(); nameEnd ++) {
    2006             char ch = sb.charAt(nameEnd);
    2007             if (ch == ':' || Character.isWhitespace(ch)) {
    2008                 break;
    2009             }
    2010         }
    2011         for (colonEnd = nameEnd; colonEnd < sb.length(); colonEnd ++) {
    2012             if (sb.charAt(colonEnd) == ':') {
    2013                 colonEnd ++;
    2014                 break;
    2015             }
    2016         }
    2017         valueStart = HttpPostBodyUtil.findNonWhitespace(sb, colonEnd);
    2018         valueEnd = HttpPostBodyUtil.findEndOfString(sb);
    2019         headers.add(sb.substring(nameStart, nameEnd));
    2020         String svalue = sb.substring(valueStart, valueEnd);
    2021         String[] values = null;
    2022         if (svalue.indexOf(";") >= 0) {
    2023             values = svalue.split(";");
    2024         } else {
    2025             values = svalue.split(",");
    2026         }
    2027         for (String value: values) {
    2028             headers.add(value.trim());
    2029         }
    2030         String[] array = new String[headers.size()];
    2031         for (int i = 0; i < headers.size(); i ++) {
    2032             array[i] = headers.get(i);
    2033         }
    2034         return array;
    2035     }
    2036
    2037     /**
    2038      * Exception when try reading data from request in chunked format, and not enough
    2039      * data are available (need more chunks)
    2040      */
    2041     public static class NotEnoughDataDecoderException extends Exception {
    2042         /**
    2043  */
    2044         private static final long serialVersionUID = -7846841864603865638L;
    2045
    2046         /**
    2047  */
    2048         public NotEnoughDataDecoderException() {
    2049         }
    2050
    2051         /**
    2052          * @param arg0
    2053          */
    2054         public NotEnoughDataDecoderException(String arg0) {
    2055             super(arg0);
    2056         }
    2057
    2058         /**
    2059          * @param arg0
    2060          */
    2061         public NotEnoughDataDecoderException(Throwable arg0) {
    2062             super(arg0);
    2063         }
    2064
    2065         /**
    2066          * @param arg0
    2067          * @param arg1
    2068          */
    2069         public NotEnoughDataDecoderException(String arg0, Throwable arg1) {
    2070             super(arg0, arg1);
    2071         }
    2072     }
    2073
    2074     /**
    2075      * Exception when the body is fully decoded, even if there is still data
    2076      */
    2077     public static class EndOfDataDecoderException extends Exception {
    2078         /**
    2079  */
    2080         private static final long serialVersionUID = 1336267941020800769L;
    2081
    2082     }
    2083
    2084     /**
    2085      * Exception when an error occurs while decoding
    2086      */
    2087     public static class ErrorDataDecoderException extends Exception {
    2088         /**
    2089  */
    2090         private static final long serialVersionUID = 5020247425493164465L;
    2091
    2092         /**
    2093  */
    2094         public ErrorDataDecoderException() {
    2095         }
    2096
    2097         /**
    2098          * @param arg0
    2099          */
    2100         public ErrorDataDecoderException(String arg0) {
    2101             super(arg0);
    2102         }
    2103
    2104         /**
    2105          * @param arg0
    2106          */
    2107         public ErrorDataDecoderException(Throwable arg0) {
    2108             super(arg0);
    2109         }
    2110
    2111         /**
    2112          * @param arg0
    2113          * @param arg1
    2114          */
    2115         public ErrorDataDecoderException(String arg0, Throwable arg1) {
    2116             super(arg0, arg1);
    2117         }
    2118     }
    2119
    2120     /**
    2121      * Exception when an unappropriated method was called on a request
    2122      */
    2123     public static class IncompatibleDataDecoderException extends Exception {
    2124         /**
    2125  */
    2126         private static final long serialVersionUID = -953268047926250267L;
    2127
    2128         /**
    2129  */
    2130         public IncompatibleDataDecoderException() {
    2131         }
    2132
    2133         /**
    2134          * @param arg0
    2135          */
    2136         public IncompatibleDataDecoderException(String arg0) {
    2137             super(arg0);
    2138         }
    2139
    2140         /**
    2141          * @param arg0
    2142          */
    2143         public IncompatibleDataDecoderException(Throwable arg0) {
    2144             super(arg0);
    2145         }
    2146
    2147         /**
    2148          * @param arg0
    2149          * @param arg1
    2150          */
    2151         public IncompatibleDataDecoderException(String arg0, Throwable arg1) {
    2152             super(arg0, arg1);
    2153         }
    2154     }
    2155 }