001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.proxy.utils; 021 022import org.apache.mina.core.buffer.IoBuffer; 023import org.apache.mina.filter.codec.textline.LineDelimiter; 024 025/** 026 * IoBufferDecoder.java - Handles an {@link IoBuffer} decoder which supports 027 * two methods : 028 * - dynamic delimiter decoding 029 * - fixed length content reading 030 * 031 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 032 * @since MINA 2.0.0-M3 033 */ 034public class IoBufferDecoder { 035 036 /** 037 * The class holding the decoding context. 038 */ 039 public class DecodingContext { 040 041 /** 042 * The buffered data. 043 */ 044 private IoBuffer decodedBuffer; 045 046 /** 047 * The delimiter in use. Set if delimiter mode is in use. 048 */ 049 private IoBuffer delimiter; 050 051 /** 052 * The currently matched bytes of the delimiter. 053 */ 054 private int matchCount = 0; 055 056 /** 057 * Holds the current content length of decoded data if in 058 * content-length mode. 059 */ 060 private int contentLength = -1; 061 062 /** 063 * Resets the decoding state. 064 */ 065 public void reset() { 066 contentLength = -1; 067 matchCount = 0; 068 decodedBuffer = null; 069 } 070 071 public int getContentLength() { 072 return contentLength; 073 } 074 075 public void setContentLength(int contentLength) { 076 this.contentLength = contentLength; 077 } 078 079 public int getMatchCount() { 080 return matchCount; 081 } 082 083 public void setMatchCount(int matchCount) { 084 this.matchCount = matchCount; 085 } 086 087 public IoBuffer getDecodedBuffer() { 088 return decodedBuffer; 089 } 090 091 public void setDecodedBuffer(IoBuffer decodedBuffer) { 092 this.decodedBuffer = decodedBuffer; 093 } 094 095 public IoBuffer getDelimiter() { 096 return delimiter; 097 } 098 099 public void setDelimiter(IoBuffer delimiter) { 100 this.delimiter = delimiter; 101 } 102 } 103 104 /** 105 * The decoding context. 106 */ 107 private DecodingContext ctx = new DecodingContext(); 108 109 /** 110 * Creates a new instance that uses specified <tt>delimiter</tt> byte array as a 111 * message delimiter. 112 * 113 * @param delimiter an array of characters which delimits messages 114 */ 115 public IoBufferDecoder(byte[] delimiter) { 116 setDelimiter(delimiter, true); 117 } 118 119 /** 120 * Creates a new instance that will read messages of <tt>contentLength</tt> bytes. 121 * 122 * @param contentLength the exact length to read 123 */ 124 public IoBufferDecoder(int contentLength) { 125 setContentLength(contentLength, false); 126 } 127 128 /** 129 * Sets the the length of the content line to be decoded. 130 * When set, it overrides the dynamic delimiter setting and content length 131 * method will be used for decoding on the next decodeOnce call. 132 * The default value is <tt>-1</tt>. 133 * 134 * @param contentLength the content length to match 135 * @param resetMatchCount delimiter matching is reset if true 136 */ 137 public void setContentLength(int contentLength, boolean resetMatchCount) { 138 if (contentLength <= 0) { 139 throw new IllegalArgumentException("contentLength: " + contentLength); 140 } 141 142 ctx.setContentLength(contentLength); 143 if (resetMatchCount) { 144 ctx.setMatchCount(0); 145 } 146 } 147 148 /** 149 * Dynamically sets a new delimiter. Next time 150 * {@link #decodeFully(IoBuffer)} will be called it will use the new 151 * delimiter. Delimiter matching is reset only if <tt>resetMatchCount</tt> is true but 152 * decoding will continue from current position. 153 * 154 * NB : Delimiter {@link LineDelimiter#AUTO} is not allowed. 155 * 156 * @param delim the new delimiter as a byte array 157 * @param resetMatchCount delimiter matching is reset if true 158 */ 159 public void setDelimiter(byte[] delim, boolean resetMatchCount) { 160 if (delim == null) { 161 throw new IllegalArgumentException("Null delimiter not allowed"); 162 } 163 164 // Convert delimiter to IoBuffer. 165 IoBuffer delimiter = IoBuffer.allocate(delim.length); 166 delimiter.put(delim); 167 delimiter.flip(); 168 169 ctx.setDelimiter(delimiter); 170 ctx.setContentLength(-1); 171 if (resetMatchCount) { 172 ctx.setMatchCount(0); 173 } 174 } 175 176 /** 177 * Will return null unless it has enough data to decode. If <code>contentLength</code> 178 * is set then it tries to retrieve <code>contentLength</code> bytes from the buffer 179 * otherwise it will scan the buffer to find the data <code>delimiter</code> and return 180 * all the data and the trailing delimiter. 181 * 182 * @param in the data to decode 183 * @return The decoded buffer 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}