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: "
141 + contentLength);
142 }
143
144 ctx.setContentLength(contentLength);
145 if (resetMatchCount) {
146 ctx.setMatchCount(0);
147 }
148 }
149
150 /**
151 * Dynamically sets a new delimiter. Next time
152 * {@link IoBufferDecoder#decodeOnce(IoSession, int) } will be called it will use the new
153 * delimiter. Delimiter matching is reset only if <tt>resetMatchCount</tt> is true but
154 * decoding will continue from current position.
155 *
156 * NB : Delimiter {@link LineDelimiter#AUTO} is not allowed.
157 *
158 * @param delim the new delimiter as a byte array
159 * @param resetMatchCount delimiter matching is reset if true
160 */
161 public void setDelimiter(byte[] delim, boolean resetMatchCount) {
162 if (delim == null) {
163 throw new NullPointerException("Null delimiter not allowed");
164 }
165
166 // Convert delimiter to IoBuffer.
167 IoBuffer delimiter = IoBuffer.allocate(delim.length);
168 delimiter.put(delim);
169 delimiter.flip();
170
171 ctx.setDelimiter(delimiter);
172 ctx.setContentLength(-1);
173 if (resetMatchCount) {
174 ctx.setMatchCount(0);
175 }
176 }
177
178 /**
179 * Will return null unless it has enough data to decode. If <code>contentLength</code>
180 * is set then it tries to retrieve <code>contentLength</code> bytes from the buffer
181 * otherwise it will scan the buffer to find the data <code>delimiter</code> and return
182 * all the data and the trailing delimiter.
183 *
184 * @param in the data to decode
185 */
186 public IoBuffer decodeFully(IoBuffer in) {
187 int contentLength = ctx.getContentLength();
188 IoBuffer decodedBuffer = ctx.getDecodedBuffer();
189
190 int oldLimit = in.limit();
191
192 // Retrieve fixed length content
193 if (contentLength > -1) {
194 if (decodedBuffer == null) {
195 decodedBuffer = IoBuffer.allocate(contentLength).setAutoExpand(
196 true);
197 }
198
199 // If not enough data to complete the decoding
200 if (in.remaining() < contentLength) {
201 int readBytes = in.remaining();
202 decodedBuffer.put(in);
203 ctx.setDecodedBuffer(decodedBuffer);
204 ctx.setContentLength(contentLength - readBytes);
205 return null;
206
207 }
208
209 int newLimit = in.position() + contentLength;
210 in.limit(newLimit);
211 decodedBuffer.put(in);
212 decodedBuffer.flip();
213 in.limit(oldLimit);
214 ctx.reset();
215
216 return decodedBuffer;
217 }
218
219 // Not a fixed length matching so try to find a delimiter match
220 int oldPos = in.position();
221 int matchCount = ctx.getMatchCount();
222 IoBuffer delimiter = ctx.getDelimiter();
223
224 while (in.hasRemaining()) {
225 byte b = in.get();
226 if (delimiter.get(matchCount) == b) {
227 matchCount++;
228 if (matchCount == delimiter.limit()) {
229 // Found a match.
230 int pos = in.position();
231 in.position(oldPos);
232
233 in.limit(pos);
234
235 if (decodedBuffer == null) {
236 decodedBuffer = IoBuffer.allocate(in.remaining())
237 .setAutoExpand(true);
238 }
239
240 decodedBuffer.put(in);
241 decodedBuffer.flip();
242
243 in.limit(oldLimit);
244 ctx.reset();
245
246 return decodedBuffer;
247 }
248 } else {
249 in.position(Math.max(0, in.position() - matchCount));
250 matchCount = 0;
251 }
252 }
253
254 // Copy remainder from buf.
255 if (in.remaining() > 0) {
256 in.position(oldPos);
257 decodedBuffer.put(in);
258 in.position(in.limit());
259 }
260
261 // Save decoding state
262 ctx.setMatchCount(matchCount);
263 ctx.setDecodedBuffer(decodedBuffer);
264
265 return decodedBuffer;
266 }
267 }