001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.nio.reactor; 029 030import java.io.IOException; 031import java.nio.ByteBuffer; 032import java.nio.CharBuffer; 033import java.nio.channels.ReadableByteChannel; 034import java.nio.channels.WritableByteChannel; 035import java.nio.charset.CharacterCodingException; 036import java.nio.charset.Charset; 037import java.nio.charset.CharsetDecoder; 038import java.nio.charset.CoderResult; 039import java.nio.charset.CodingErrorAction; 040 041import org.apache.http.MessageConstraintException; 042import org.apache.http.config.MessageConstraints; 043import org.apache.http.nio.reactor.SessionInputBuffer; 044import org.apache.http.nio.util.ByteBufferAllocator; 045import org.apache.http.nio.util.ExpandableBuffer; 046import org.apache.http.nio.util.HeapByteBufferAllocator; 047import org.apache.http.params.CoreProtocolPNames; 048import org.apache.http.params.HttpParams; 049import org.apache.http.protocol.HTTP; 050import org.apache.http.util.Args; 051import org.apache.http.util.CharArrayBuffer; 052import org.apache.http.util.CharsetUtils; 053 054/** 055 * Default implementation of {@link SessionInputBuffer} based on 056 * the {@link ExpandableBuffer} class. 057 * 058 * @since 4.0 059 */ 060@SuppressWarnings("deprecation") 061public class SessionInputBufferImpl extends ExpandableBuffer implements SessionInputBuffer { 062 063 private final CharsetDecoder chardecoder; 064 private final MessageConstraints constraints; 065 private final int lineBuffersize; 066 067 private CharBuffer charbuffer; 068 069 /** 070 * Creates SessionInputBufferImpl instance. 071 * 072 * @param buffersize input buffer size 073 * @param lineBuffersize buffer size for line operations. Has effect only if 074 * {@code chardecoder} is not {@code null}. 075 * @param chardecoder chardecoder to be used for decoding HTTP protocol elements. 076 * If {@code null} simple type cast will be used for byte to char conversion. 077 * @param constraints Message constraints. If {@code null} 078 * {@link MessageConstraints#DEFAULT} will be used. 079 * @param allocator memory allocator. 080 * If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used. 081 * 082 * @since 4.4 083 */ 084 public SessionInputBufferImpl( 085 final int buffersize, 086 final int lineBuffersize, 087 final MessageConstraints constraints, 088 final CharsetDecoder chardecoder, 089 final ByteBufferAllocator allocator) { 090 super(buffersize, allocator != null ? allocator : HeapByteBufferAllocator.INSTANCE); 091 this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size"); 092 this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT; 093 this.chardecoder = chardecoder; 094 } 095 096 /** 097 * Creates SessionInputBufferImpl instance. 098 * 099 * @param buffersize input buffer size 100 * @param lineBuffersize buffer size for line operations. Has effect only if 101 * {@code chardecoder} is not {@code null}. 102 * @param chardecoder chardecoder to be used for decoding HTTP protocol elements. 103 * If {@code null} simple type cast will be used for byte to char conversion. 104 * @param allocator memory allocator. 105 * If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used. 106 * 107 * @since 4.3 108 */ 109 public SessionInputBufferImpl( 110 final int buffersize, 111 final int lineBuffersize, 112 final CharsetDecoder chardecoder, 113 final ByteBufferAllocator allocator) { 114 this(buffersize, lineBuffersize, null, chardecoder, allocator); 115 } 116 117 /** 118 * @deprecated (4.3) use 119 * {@link SessionInputBufferImpl#SessionInputBufferImpl(int, int, CharsetDecoder, 120 * ByteBufferAllocator)} 121 */ 122 @Deprecated 123 public SessionInputBufferImpl( 124 final int buffersize, 125 final int lineBuffersize, 126 final ByteBufferAllocator allocator, 127 final HttpParams params) { 128 super(buffersize, allocator); 129 this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size"); 130 final String charsetName = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET); 131 final Charset charset = CharsetUtils.lookup(charsetName); 132 if (charset != null) { 133 this.chardecoder = charset.newDecoder(); 134 final CodingErrorAction a1 = (CodingErrorAction) params.getParameter( 135 CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION); 136 this.chardecoder.onMalformedInput(a1 != null ? a1 : CodingErrorAction.REPORT); 137 final CodingErrorAction a2 = (CodingErrorAction) params.getParameter( 138 CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION); 139 this.chardecoder.onUnmappableCharacter(a2 != null? a2 : CodingErrorAction.REPORT); 140 } else { 141 this.chardecoder = null; 142 } 143 this.constraints = MessageConstraints.DEFAULT; 144 } 145 146 /** 147 * @deprecated (4.3) use 148 * {@link SessionInputBufferImpl#SessionInputBufferImpl(int, int, Charset)} 149 */ 150 @Deprecated 151 public SessionInputBufferImpl( 152 final int buffersize, 153 final int linebuffersize, 154 final HttpParams params) { 155 this(buffersize, linebuffersize, HeapByteBufferAllocator.INSTANCE, params); 156 } 157 158 /** 159 * @since 4.3 160 */ 161 public SessionInputBufferImpl( 162 final int buffersize, 163 final int lineBuffersize, 164 final Charset charset) { 165 this(buffersize, lineBuffersize, null, 166 charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE); 167 } 168 169 /** 170 * @since 4.3 171 */ 172 public SessionInputBufferImpl( 173 final int buffersize, 174 final int lineBuffersize, 175 final MessageConstraints constraints, 176 final Charset charset) { 177 this(buffersize, lineBuffersize, constraints, 178 charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE); 179 } 180 181 /** 182 * @since 4.3 183 */ 184 public SessionInputBufferImpl( 185 final int buffersize, 186 final int lineBuffersize) { 187 this(buffersize, lineBuffersize, null, null, HeapByteBufferAllocator.INSTANCE); 188 } 189 190 /** 191 * @since 4.3 192 */ 193 public SessionInputBufferImpl(final int buffersize) { 194 this(buffersize, 256, null, null, HeapByteBufferAllocator.INSTANCE); 195 } 196 197 @Override 198 public int fill(final ReadableByteChannel channel) throws IOException { 199 Args.notNull(channel, "Channel"); 200 setInputMode(); 201 if (!this.buffer.hasRemaining()) { 202 expand(); 203 } 204 return channel.read(this.buffer); 205 } 206 207 @Override 208 public int read() { 209 setOutputMode(); 210 return this.buffer.get() & 0xff; 211 } 212 213 @Override 214 public int read(final ByteBuffer dst, final int maxLen) { 215 if (dst == null) { 216 return 0; 217 } 218 setOutputMode(); 219 final int len = Math.min(dst.remaining(), maxLen); 220 final int chunk = Math.min(this.buffer.remaining(), len); 221 if (this.buffer.remaining() > chunk) { 222 final int oldLimit = this.buffer.limit(); 223 final int newLimit = this.buffer.position() + chunk; 224 this.buffer.limit(newLimit); 225 dst.put(this.buffer); 226 this.buffer.limit(oldLimit); 227 return len; 228 } else { 229 dst.put(this.buffer); 230 } 231 return chunk; 232 } 233 234 @Override 235 public int read(final ByteBuffer dst) { 236 if (dst == null) { 237 return 0; 238 } 239 return read(dst, dst.remaining()); 240 } 241 242 @Override 243 public int read(final WritableByteChannel dst, final int maxLen) throws IOException { 244 if (dst == null) { 245 return 0; 246 } 247 setOutputMode(); 248 final int bytesRead; 249 if (this.buffer.remaining() > maxLen) { 250 final int oldLimit = this.buffer.limit(); 251 final int newLimit = oldLimit - (this.buffer.remaining() - maxLen); 252 this.buffer.limit(newLimit); 253 bytesRead = dst.write(this.buffer); 254 this.buffer.limit(oldLimit); 255 } else { 256 bytesRead = dst.write(this.buffer); 257 } 258 return bytesRead; 259 } 260 261 @Override 262 public int read(final WritableByteChannel dst) throws IOException { 263 if (dst == null) { 264 return 0; 265 } 266 setOutputMode(); 267 return dst.write(this.buffer); 268 } 269 270 @Override 271 public boolean readLine( 272 final CharArrayBuffer linebuffer, 273 final boolean endOfStream) throws CharacterCodingException { 274 275 setOutputMode(); 276 // See if there is LF char present in the buffer 277 int pos = -1; 278 for (int i = this.buffer.position(); i < this.buffer.limit(); i++) { 279 final int b = this.buffer.get(i); 280 if (b == HTTP.LF) { 281 pos = i + 1; 282 break; 283 } 284 } 285 286 final int maxLineLen = this.constraints.getMaxLineLength(); 287 if (maxLineLen > 0) { 288 final int currentLen = (pos > 0 ? pos : this.buffer.limit()) - this.buffer.position(); 289 if (currentLen >= maxLineLen) { 290 throw new MessageConstraintException("Maximum line length limit exceeded"); 291 } 292 } 293 294 if (pos == -1) { 295 if (endOfStream && this.buffer.hasRemaining()) { 296 // No more data. Get the rest 297 pos = this.buffer.limit(); 298 } else { 299 // Either no complete line present in the buffer 300 // or no more data is expected 301 return false; 302 } 303 } 304 final int origLimit = this.buffer.limit(); 305 this.buffer.limit(pos); 306 307 final int requiredCapacity = this.buffer.limit() - this.buffer.position(); 308 // Ensure capacity of len assuming ASCII as the most likely charset 309 linebuffer.ensureCapacity(requiredCapacity); 310 311 if (this.chardecoder == null) { 312 if (this.buffer.hasArray()) { 313 final byte[] b = this.buffer.array(); 314 final int off = this.buffer.position(); 315 final int len = this.buffer.remaining(); 316 linebuffer.append(b, off, len); 317 this.buffer.position(off + len); 318 } else { 319 while (this.buffer.hasRemaining()) { 320 linebuffer.append((char) (this.buffer.get() & 0xff)); 321 } 322 } 323 } else { 324 if (this.charbuffer == null) { 325 this.charbuffer = CharBuffer.allocate(this.lineBuffersize); 326 } 327 this.chardecoder.reset(); 328 329 for (;;) { 330 final CoderResult result = this.chardecoder.decode( 331 this.buffer, 332 this.charbuffer, 333 true); 334 if (result.isError()) { 335 result.throwException(); 336 } 337 if (result.isOverflow()) { 338 this.charbuffer.flip(); 339 linebuffer.append( 340 this.charbuffer.array(), 341 this.charbuffer.position(), 342 this.charbuffer.remaining()); 343 this.charbuffer.clear(); 344 } 345 if (result.isUnderflow()) { 346 break; 347 } 348 } 349 350 // flush the decoder 351 this.chardecoder.flush(this.charbuffer); 352 this.charbuffer.flip(); 353 // append the decoded content to the line buffer 354 if (this.charbuffer.hasRemaining()) { 355 linebuffer.append( 356 this.charbuffer.array(), 357 this.charbuffer.position(), 358 this.charbuffer.remaining()); 359 } 360 361 } 362 this.buffer.limit(origLimit); 363 364 // discard LF if found 365 int l = linebuffer.length(); 366 if (l > 0) { 367 if (linebuffer.charAt(l - 1) == HTTP.LF) { 368 l--; 369 linebuffer.setLength(l); 370 } 371 // discard CR if found 372 if (l > 0) { 373 if (linebuffer.charAt(l - 1) == HTTP.CR) { 374 l--; 375 linebuffer.setLength(l); 376 } 377 } 378 } 379 return true; 380 } 381 382 @Override 383 public String readLine(final boolean endOfStream) throws CharacterCodingException { 384 final CharArrayBuffer buffer = new CharArrayBuffer(64); 385 final boolean found = readLine(buffer, endOfStream); 386 if (found) { 387 return buffer.toString(); 388 } else { 389 return null; 390 } 391 } 392 393}