View Javadoc

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one
    3    *  or more contributor license agreements.  See the NOTICE file
    4    *  distributed with this work for additional information
    5    *  regarding copyright ownership.  The ASF licenses this file
    6    *  to you under the Apache License, Version 2.0 (the
    7    *  "License"); you may not use this file except in compliance
    8    *  with the License.  You may obtain a copy of the License at
    9    *
    10   *    http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   *  Unless required by applicable law or agreed to in writing,
    13   *  software distributed under the License is distributed on an
    14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   *  KIND, either express or implied.  See the License for the
    16   *  specific language governing permissions and limitations
    17   *  under the License.
    18   *
    19   */
    20  package org.apache.mina.proxy.utils;
    21
    22  import org.apache.mina.core.buffer.IoBuffer;
    23  import org.apache.mina.core.session.IoSession;
    24  import org.apache.mina.filter.codec.textline.LineDelimiter;
    25
    26  /**
    27   * IoBufferDecoder.java - Handles an {@link IoBuffer} decoder which supports
    28   * two methods :
    29   * - dynamic delimiter decoding
    30   * - fixed length content reading
    31   *
    32   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
    33   * @since MINA 2.0.0-M3
    34   */
    35  public class IoBufferDecoder {
    36
    37      /**
    38       * The class holding the decoding context.
    39       */
    40      public class DecodingContext {
    41
    42          /**
    43           * The buffered data.
    44           */
    45          private IoBuffer decodedBuffer;
    46
    47          /**
    48           * The delimiter in use. Set if delimiter mode is in use.
    49           */
    50          private IoBuffer delimiter;
    51
    52          /**
    53           * The currently matched bytes of the delimiter.
    54           */
    55          private int matchCount = 0;
    56
    57          /**
    58           * Holds the current content length of decoded data if in
    59           * content-length mode.
    60           */
    61          private int contentLength = -1;
    62
    63          /**
    64           * Resets the decoding state.
    65           */
    66          public void reset() {
    67              contentLength = -1;
    68              matchCount = 0;
    69              decodedBuffer = null;
    70          }
    71
    72          public int getContentLength() {
    73              return contentLength;
    74          }
    75
    76          public void setContentLength(int contentLength) {
    77              this.contentLength = contentLength;
    78          }
    79
    80          public int getMatchCount() {
    81              return matchCount;
    82          }
    83
    84          public void setMatchCount(int matchCount) {
    85              this.matchCount = matchCount;
    86          }
    87
    88          public IoBuffer getDecodedBuffer() {
    89              return decodedBuffer;
    90          }
    91
    92          public void setDecodedBuffer(IoBuffer decodedBuffer) {
    93              this.decodedBuffer = decodedBuffer;
    94          }
    95
    96          public IoBuffer getDelimiter() {
    97              return delimiter;
    98          }
    99
    100         public void setDelimiter(IoBuffer delimiter) {
    101             this.delimiter = delimiter;
    102         }
    103     }
    104
    105     /**
    106      * The decoding context.
    107      */
    108     private DecodingContext ctx = new DecodingContext();
    109
    110     /**
    111      * Creates a new instance that uses specified <tt>delimiter</tt> byte array as a
    112      * message delimiter.
    113      *
    114      * @param delimiter an array of characters which delimits messages
    115      */
    116     public IoBufferDecoder(byte[] delimiter) {
    117         setDelimiter(delimiter, true);
    118     }
    119
    120     /**
    121      * Creates a new instance that will read messages of <tt>contentLength</tt> bytes.
    122      *
    123      * @param contentLength the exact length to read
    124      */
    125     public IoBufferDecoder(int contentLength) {
    126         setContentLength(contentLength, false);
    127     }
    128
    129     /**
    130      * Sets the the length of the content line to be decoded.
    131      * When set, it overrides the dynamic delimiter setting and content length
    132      * method will be used for decoding on the next decodeOnce call.
    133      * The default value is <tt>-1</tt>.
    134      *
    135      * @param contentLength the content length to match
    136      * @param resetMatchCount delimiter matching is reset if true
    137      */
    138     public void setContentLength(int contentLength, boolean resetMatchCount) {
    139         if (contentLength <= 0) {
    140             throw new IllegalArgumentException("contentLength: " + contentLength);
    141         }
    142
    143         ctx.setContentLength(contentLength);
    144         if (resetMatchCount) {
    145             ctx.setMatchCount(0);
    146         }
    147     }
    148
    149     /**
    150      * Dynamically sets a new delimiter. Next time
    151      * {@link IoBufferDecoder#decodeOnce(IoSession, int) } will be called it will use the new
    152      * delimiter. Delimiter matching is reset only if <tt>resetMatchCount</tt> is true but
    153      * decoding will continue from current position.
    154      *
    155      * NB : Delimiter {@link LineDelimiter#AUTO} is not allowed.
    156      *
    157      * @param delim the new delimiter as a byte array
    158      * @param resetMatchCount delimiter matching is reset if true
    159      */
    160     public void setDelimiter(byte[] delim, boolean resetMatchCount) {
    161         if (delim == null) {
    162             throw new IllegalArgumentException("Null delimiter not allowed");
    163         }
    164
    165         // Convert delimiter to IoBuffer.
    166         IoBuffer delimiter = IoBuffer.allocate(delim.length);
    167         delimiter.put(delim);
    168         delimiter.flip();
    169
    170         ctx.setDelimiter(delimiter);
    171         ctx.setContentLength(-1);
    172         if (resetMatchCount) {
    173             ctx.setMatchCount(0);
    174         }
    175     }
    176
    177     /**
    178      * Will return null unless it has enough data to decode. If <code>contentLength</code>
    179      * is set then it tries to retrieve <code>contentLength</code> bytes from the buffer
    180      * otherwise it will scan the buffer to find the data <code>delimiter</code> and return
    181      * all the data and the trailing delimiter.
    182      *
    183      * @param in the data to decode
    184      */
    185     public IoBuffer decodeFully(IoBuffer in) {
    186         int contentLength = ctx.getContentLength();
    187         IoBuffer decodedBuffer = ctx.getDecodedBuffer();
    188
    189         int oldLimit = in.limit();
    190
    191         // Retrieve fixed length content
    192         if (contentLength > -1) {
    193             if (decodedBuffer == null) {
    194                 decodedBuffer = IoBuffer.allocate(contentLength).setAutoExpand(true);
    195             }
    196
    197             // If not enough data to complete the decoding
    198             if (in.remaining() < contentLength) {
    199                 int readBytes = in.remaining();
    200                 decodedBuffer.put(in);
    201                 ctx.setDecodedBuffer(decodedBuffer);
    202                 ctx.setContentLength(contentLength - readBytes);
    203                 return null;
    204
    205             }
    206
    207             int newLimit = in.position() + contentLength;
    208             in.limit(newLimit);
    209             decodedBuffer.put(in);
    210             decodedBuffer.flip();
    211             in.limit(oldLimit);
    212             ctx.reset();
    213
    214             return decodedBuffer;
    215         }
    216
    217         // Not a fixed length matching so try to find a delimiter match
    218         int oldPos = in.position();
    219         int matchCount = ctx.getMatchCount();
    220         IoBuffer delimiter = ctx.getDelimiter();
    221
    222         while (in.hasRemaining()) {
    223             byte b = in.get();
    224             if (delimiter.get(matchCount) == b) {
    225                 matchCount++;
    226                 if (matchCount == delimiter.limit()) {
    227                     // Found a match.
    228                     int pos = in.position();
    229                     in.position(oldPos);
    230
    231                     in.limit(pos);
    232
    233                     if (decodedBuffer == null) {
    234                         decodedBuffer = IoBuffer.allocate(in.remaining()).setAutoExpand(true);
    235                     }
    236
    237                     decodedBuffer.put(in);
    238                     decodedBuffer.flip();
    239
    240                     in.limit(oldLimit);
    241                     ctx.reset();
    242
    243                     return decodedBuffer;
    244                 }
    245             } else {
    246                 in.position(Math.max(0, in.position() - matchCount));
    247                 matchCount = 0;
    248             }
    249         }
    250
    251         // Copy remainder from buf.
    252         if (in.remaining() > 0) {
    253             in.position(oldPos);
    254             decodedBuffer.put(in);
    255             in.position(in.limit());
    256         }
    257
    258         // Save decoding state
    259         ctx.setMatchCount(matchCount);
    260         ctx.setDecodedBuffer(decodedBuffer);
    261
    262         return decodedBuffer;
    263     }
    264 }