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 module hunt.net.codec.textline.TextLineDecoder; 21 22 import hunt.net.codec.textline.LineDelimiter; 23 import hunt.net.codec.Decoder; 24 import hunt.net.Connection; 25 import hunt.net.Exceptions; 26 27 import hunt.io.ByteBuffer; 28 import hunt.io.BufferUtils; 29 import hunt.Exceptions; 30 import hunt.logging.ConsoleLogger; 31 import hunt.String; 32 33 import std.algorithm; 34 import std.conv; 35 36 37 /** 38 * A {@link ProtocolDecoder} which decodes a text line into a string. 39 * 40 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 41 */ 42 class TextLineDecoder : DecoderChain { 43 private enum string CONTEXT = "decoder"; 44 45 // private final Charset charset; 46 47 /** The delimiter used to determinate when a line has been fully decoded */ 48 private LineDelimiter delimiter; 49 50 /** An ByteBuffer containing the delimiter */ 51 private ByteBuffer delimBuf; 52 53 /** The default maximum Line length. Default to 1024. */ 54 private int maxLineLength = 1024; 55 56 /** The default maximum buffer length. Default to 128 chars. */ 57 private int bufferLength = 128; 58 59 this() { 60 this(cast(DecoderChain)null); 61 } 62 63 /** 64 * Creates a new instance with the current default {@link Charset} 65 * and {@link LineDelimiter#AUTO} delimiter. 66 */ 67 this(DecoderChain nextDecoder) { 68 this(LineDelimiter.AUTO, nextDecoder); 69 } 70 71 /** 72 * Creates a new instance with the current default {@link Charset} 73 * and the specified <tt>delimiter</tt>. 74 * 75 * @param delimiter The line delimiter to use 76 */ 77 this(string delimiter, DecoderChain nextDecoder = null) { 78 this(LineDelimiter(delimiter), nextDecoder); 79 } 80 81 /** 82 * Creates a new instance with the current default {@link Charset} 83 * and the specified <tt>delimiter</tt>. 84 * 85 * @param delimiter The line delimiter to use 86 */ 87 this(LineDelimiter delimiter, DecoderChain nextDecoder = null) { 88 // this(Charset.defaultCharset(), delimiter); 89 super(nextDecoder); 90 this.delimiter = delimiter; 91 92 // Convert delimiter to ByteBuffer if not done yet. 93 if (delimBuf is null) { 94 ByteBuffer tmp = BufferUtils.allocate(2);// ByteBuffer.allocate(2).setAutoExpand(true); 95 96 try { 97 tmp.put(cast(byte[])delimiter.getValue()); 98 } catch (CharacterCodingException cce) { 99 warning(cce); 100 } 101 102 tmp.flip(); 103 delimBuf = tmp; 104 } 105 } 106 107 /** 108 * @return the allowed maximum size of the line to be decoded. 109 * If the size of the line to be decoded exceeds this value, the 110 * decoder will throw a {@link BufferDataException}. The default 111 * value is <tt>1024</tt> (1KB). 112 */ 113 int getMaxLineLength() { 114 return maxLineLength; 115 } 116 117 /** 118 * Sets the allowed maximum size of the line to be decoded. 119 * If the size of the line to be decoded exceeds this value, the 120 * decoder will throw a {@link BufferDataException}. The default 121 * value is <tt>1024</tt> (1KB). 122 * 123 * @param maxLineLength The maximum line length 124 */ 125 void setMaxLineLength(int maxLineLength) { 126 if (maxLineLength <= 0) { 127 throw new IllegalArgumentException("maxLineLength (" ~ 128 maxLineLength.to!string() ~ ") should be a positive value"); 129 } 130 131 this.maxLineLength = maxLineLength; 132 } 133 134 /** 135 * Sets the default buffer size. This buffer is used in the Context 136 * to store the decoded line. 137 * 138 * @param bufferLength The default bufer size 139 */ 140 void setBufferLength(int bufferLength) { 141 if (bufferLength <= 0) { 142 throw new IllegalArgumentException("bufferLength (" ~ 143 maxLineLength.to!string() ~ ") should be a positive value"); 144 145 } 146 147 this.bufferLength = bufferLength; 148 } 149 150 /** 151 * @return the allowed buffer size used to store the decoded line 152 * in the Context instance. 153 */ 154 int getBufferLength() { 155 return bufferLength; 156 } 157 158 /** 159 * {@inheritDoc} 160 */ 161 override 162 void decode(ByteBuffer buf, Connection connection) { 163 Context ctx = getContext(connection); 164 165 if (LineDelimiter.AUTO == delimiter) { 166 decodeAuto(ctx, connection, buf); 167 } else { 168 decodeNormal(ctx, connection, buf); 169 } 170 } 171 172 /** 173 * @return the context for this connection 174 * 175 * @param connection The connection for which we want the context 176 */ 177 private Context getContext(Connection connection) { 178 Context ctx; 179 ctx = cast(Context) connection.getAttribute(CONTEXT); 180 181 if (ctx is null) { 182 ctx = new Context(bufferLength); 183 connection.setAttribute(CONTEXT, ctx); 184 } 185 186 return ctx; 187 } 188 189 190 /** 191 * {@inheritDoc} 192 */ 193 void dispose(Connection connection) { 194 Context ctx = cast(Context) connection.getAttribute(CONTEXT); 195 196 if (ctx !is null) { 197 connection.removeAttribute(CONTEXT); 198 } 199 } 200 201 /** 202 * Decode a line using the default delimiter on the current system 203 */ 204 private void decodeAuto(Context ctx, Connection connection, ByteBuffer inBuffer) { 205 int matchCount = ctx.getMatchCount(); 206 207 // Try to find a match 208 int oldPos = inBuffer.position(); 209 int oldLimit = inBuffer.limit(); 210 211 while (inBuffer.hasRemaining()) { 212 byte b = inBuffer.get(); 213 bool matched = false; 214 215 switch (b) { 216 case '\r': 217 // Might be Mac, but we don't auto-detect Mac EOL 218 // to avoid confusion. 219 matchCount++; 220 break; 221 222 case '\n': 223 // UNIX 224 matchCount++; 225 matched = true; 226 break; 227 228 default: 229 matchCount = 0; 230 } 231 232 if (matched) { 233 // Found a match. 234 int pos = inBuffer.position(); 235 inBuffer.limit(pos); 236 inBuffer.position(oldPos); 237 238 ctx.append(inBuffer); 239 240 inBuffer.limit(oldLimit); 241 inBuffer.position(pos); 242 243 if (ctx.getOverflowPosition() == 0) { 244 ByteBuffer buf = ctx.getBuffer(); 245 buf.flip(); 246 buf.limit(buf.limit() - matchCount); 247 248 try { 249 byte[] data = new byte[buf.limit()]; 250 buf.get(data); 251 string str = cast(string)data; 252 253 // call connection handler 254 NetConnectionHandler handler = connection.getHandler(); 255 if(handler !is null) { 256 handler.messageReceived(connection, new String(str)); 257 } 258 } finally { 259 buf.clear(); 260 } 261 } else { 262 int overflowPosition = ctx.getOverflowPosition(); 263 ctx.reset(); 264 throw new RecoverableProtocolDecoderException("Line is too long: " ~ overflowPosition.to!string()); 265 } 266 267 oldPos = pos; 268 matchCount = 0; 269 } 270 } 271 272 // Put remainder to buf. 273 inBuffer.position(oldPos); 274 ctx.append(inBuffer); 275 276 ctx.setMatchCount(matchCount); 277 } 278 279 /** 280 * Decode a line using the delimiter defined by the caller 281 */ 282 private void decodeNormal(Context ctx, Connection connection, ByteBuffer inBuffer) { 283 int matchCount = ctx.getMatchCount(); 284 285 // Try to find a match 286 int oldPos = inBuffer.position(); 287 int oldLimit = inBuffer.limit(); 288 289 while (inBuffer.hasRemaining()) { 290 byte b = inBuffer.get(); 291 292 if (delimBuf.get(matchCount) == b) { 293 matchCount++; 294 295 if (matchCount == delimBuf.limit()) { 296 // Found a match. 297 int pos = inBuffer.position(); 298 inBuffer.limit(pos); 299 inBuffer.position(oldPos); 300 301 ctx.append(inBuffer); 302 303 inBuffer.limit(oldLimit); 304 inBuffer.position(pos); 305 306 if (ctx.getOverflowPosition() == 0) { 307 ByteBuffer buf = ctx.getBuffer(); 308 buf.flip(); 309 buf.limit(buf.limit() - matchCount); 310 311 try { 312 // writeText(connection, buf.getString(ctx.getDecoder()), out); 313 314 byte[] data = new byte[buf.limit()]; 315 buf.get(data); 316 string str = cast(string)data; 317 318 // call connection handler 319 NetConnectionHandler handler = connection.getHandler(); 320 if(handler !is null) { 321 handler.messageReceived(connection, new String(str)); 322 } 323 } finally { 324 buf.clear(); 325 } 326 } else { 327 int overflowPosition = ctx.getOverflowPosition(); 328 ctx.reset(); 329 throw new RecoverableProtocolDecoderException("Line is too long: " ~ overflowPosition.to!string()); 330 } 331 332 oldPos = pos; 333 matchCount = 0; 334 } 335 } else { 336 // fix for DIRMINA-506 & DIRMINA-536 337 inBuffer.position(max(0, inBuffer.position() - matchCount)); 338 matchCount = 0; 339 } 340 } 341 342 // Put remainder to buf. 343 inBuffer.position(oldPos); 344 ctx.append(inBuffer); 345 346 ctx.setMatchCount(matchCount); 347 } 348 349 350 /** 351 * A Context used during the decoding of a lin. It stores the decoder, 352 * the temporary buffer containing the decoded line, and other status flags. 353 * 354 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 355 * @version $Rev$, $Date$ 356 */ 357 private class Context { 358 /** The decoder */ 359 // private final CharsetDecoder decoder; 360 361 /** The temporary buffer containing the decoded line */ 362 private ByteBuffer buf; 363 364 /** The number of lines found so far */ 365 private int matchCount = 0; 366 367 /** A counter to signal that the line is too long */ 368 private int overflowPosition = 0; 369 370 /** Create a new Context object with a default buffer */ 371 private this(int bufferLength) { 372 // decoder = charset.newDecoder(); 373 // buf = ByteBuffer.allocate(bufferLength).setAutoExpand(true); 374 buf = BufferUtils.allocate(bufferLength); 375 } 376 377 // CharsetDecoder getDecoder() { 378 // return decoder; 379 // } 380 381 ByteBuffer getBuffer() { 382 return buf; 383 } 384 385 int getOverflowPosition() { 386 return overflowPosition; 387 } 388 389 int getMatchCount() { 390 return matchCount; 391 } 392 393 void setMatchCount(int matchCount) { 394 this.matchCount = matchCount; 395 } 396 397 void reset() { 398 overflowPosition = 0; 399 matchCount = 0; 400 // decoder.reset(); 401 } 402 403 void append(ByteBuffer buffer) { 404 if (overflowPosition != 0) { 405 discard(buffer); 406 } else if (buf.position() > maxLineLength - buffer.remaining()) { 407 overflowPosition = buf.position(); 408 buf.clear(); 409 discard(buffer); 410 } else { 411 getBuffer().put(buffer); 412 } 413 } 414 415 private void discard(ByteBuffer buffer) { 416 if (int.max - buffer.remaining() < overflowPosition) { 417 overflowPosition = int.max; 418 } else { 419 overflowPosition += buffer.remaining(); 420 } 421 422 buffer.position(buffer.limit()); 423 } 424 } 425 }