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