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 #decodeFully(IoBuffer)} 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 }