View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.nio.reactor;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.CharBuffer;
33  import java.nio.channels.ReadableByteChannel;
34  import java.nio.channels.WritableByteChannel;
35  import java.nio.charset.CharacterCodingException;
36  import java.nio.charset.Charset;
37  import java.nio.charset.CharsetDecoder;
38  import java.nio.charset.CoderResult;
39  import java.nio.charset.CodingErrorAction;
40  
41  import org.apache.http.MessageConstraintException;
42  import org.apache.http.config.MessageConstraints;
43  import org.apache.http.nio.reactor.SessionInputBuffer;
44  import org.apache.http.nio.util.ByteBufferAllocator;
45  import org.apache.http.nio.util.ExpandableBuffer;
46  import org.apache.http.nio.util.HeapByteBufferAllocator;
47  import org.apache.http.params.CoreProtocolPNames;
48  import org.apache.http.params.HttpParams;
49  import org.apache.http.protocol.HTTP;
50  import org.apache.http.util.Args;
51  import org.apache.http.util.CharArrayBuffer;
52  import org.apache.http.util.CharsetUtils;
53  
54  /**
55   * Default implementation of {@link SessionInputBuffer} based on
56   * the {@link ExpandableBuffer} class.
57   *
58   * @since 4.0
59   */
60  @SuppressWarnings("deprecation")
61  public class SessionInputBufferImpl extends ExpandableBuffer implements SessionInputBuffer {
62  
63      private final CharsetDecoder charDecoder;
64      private final MessageConstraints constraints;
65      private final int lineBufferSize;
66  
67      private CharBuffer charBuffer;
68  
69      /**
70       *  Creates SessionInputBufferImpl instance.
71       *
72       * @param bufferSize input buffer size.
73       * @param lineBufferSize buffer size for line operations. Has effect only if
74       *   {@code charDecoder} is not {@code null}.
75       * @param charDecoder CharDecoder to be used for decoding HTTP protocol elements.
76       *   If {@code null} simple type cast will be used for byte to char conversion.
77       * @param constraints Message constraints. If {@code null}
78       *   {@link MessageConstraints#DEFAULT} will be used.
79       * @param allocator memory allocator.
80       *   If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used.
81       *
82       * @since 4.4
83       */
84      public SessionInputBufferImpl(
85              final int bufferSize,
86              final int lineBufferSize,
87              final MessageConstraints constraints,
88              final CharsetDecoder charDecoder,
89              final ByteBufferAllocator allocator) {
90          super(bufferSize, allocator != null ? allocator : HeapByteBufferAllocator.INSTANCE);
91          this.lineBufferSize = Args.positive(lineBufferSize, "Line buffer size");
92          this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
93          this.charDecoder = charDecoder;
94      }
95  
96      /**
97       *  Creates SessionInputBufferImpl instance.
98       *
99       * @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      *  Creates SessionInputBufferImpl instance.
160      *
161      * @param bufferSize input buffer size.
162      * @param lineBufferSize buffer size for line operations. Has effect only if
163      *   {@code charset} is not {@code null}.
164      * @param charset Charset to be used for decoding HTTP protocol elements.
165      *   If {@code null} simple type cast will be used for byte to char conversion.
166      *
167      * @since 4.3
168      */
169     public SessionInputBufferImpl(
170             final int bufferSize,
171             final int lineBufferSize,
172             final Charset charset) {
173         this(bufferSize, lineBufferSize, null,
174                 charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE);
175     }
176 
177     /**
178      *  Creates SessionInputBufferImpl instance.
179      *
180      * @param bufferSize input buffer size.
181      * @param lineBufferSize buffer size for line operations. Has effect only if
182      *   {@code charset} is not {@code null}.
183      * @param charset Charset to be used for decoding HTTP protocol elements.
184      *   If {@code null} simple type cast will be used for byte to char conversion.
185      * @param constraints Message constraints. If {@code null}
186      *   {@link MessageConstraints#DEFAULT} will be used.
187      *
188      * @since 4.3
189      */
190     public SessionInputBufferImpl(
191             final int bufferSize,
192             final int lineBufferSize,
193             final MessageConstraints constraints,
194             final Charset charset) {
195         this(bufferSize, lineBufferSize, constraints,
196                 charset != null ? charset.newDecoder() : null, HeapByteBufferAllocator.INSTANCE);
197     }
198 
199     /**
200      *  Creates SessionInputBufferImpl instance.
201      *
202      * @param bufferSize input buffer size.
203      * @param lineBufferSize buffer size for line operations.
204      *
205      * @since 4.3
206      */
207     public SessionInputBufferImpl(
208             final int bufferSize,
209             final int lineBufferSize) {
210         this(bufferSize, lineBufferSize, null, null, HeapByteBufferAllocator.INSTANCE);
211     }
212 
213     /**
214      *  Creates SessionInputBufferImpl instance.
215      *
216      * @param bufferSize input buffer size.
217      *
218      * @since 4.3
219      */
220     public SessionInputBufferImpl(final int bufferSize) {
221         this(bufferSize, 256, null, null, HeapByteBufferAllocator.INSTANCE);
222     }
223 
224     @Override
225     public int fill(final ReadableByteChannel channel) throws IOException {
226         Args.notNull(channel, "Channel");
227         setInputMode();
228         if (!this.buffer.hasRemaining()) {
229             expand();
230         }
231         return channel.read(this.buffer);
232     }
233 
234     @Override
235     public int read() {
236         setOutputMode();
237         return this.buffer.get() & 0xff;
238     }
239 
240     @Override
241     public int read(final ByteBuffer dst, final int maxLen) {
242         if (dst == null) {
243             return 0;
244         }
245         setOutputMode();
246         final int len = Math.min(dst.remaining(), maxLen);
247         final int chunk = Math.min(this.buffer.remaining(), len);
248         if (this.buffer.remaining() > chunk) {
249             final int oldLimit = this.buffer.limit();
250             final int newLimit = this.buffer.position() + chunk;
251             this.buffer.limit(newLimit);
252             dst.put(this.buffer);
253             this.buffer.limit(oldLimit);
254             return len;
255         }
256         dst.put(this.buffer);
257         return chunk;
258     }
259 
260     @Override
261     public int read(final ByteBuffer dst) {
262         if (dst == null) {
263             return 0;
264         }
265         return read(dst, dst.remaining());
266     }
267 
268     @Override
269     public int read(final WritableByteChannel dst, final int maxLen) throws IOException {
270         if (dst == null) {
271             return 0;
272         }
273         setOutputMode();
274         final int bytesRead;
275         if (this.buffer.remaining() > maxLen) {
276             final int oldLimit = this.buffer.limit();
277             final int newLimit = oldLimit - (this.buffer.remaining() - maxLen);
278             this.buffer.limit(newLimit);
279             bytesRead = dst.write(this.buffer);
280             this.buffer.limit(oldLimit);
281         } else {
282             bytesRead = dst.write(this.buffer);
283         }
284         return bytesRead;
285     }
286 
287     @Override
288     public int read(final WritableByteChannel dst) throws IOException {
289         if (dst == null) {
290             return 0;
291         }
292         setOutputMode();
293         return dst.write(this.buffer);
294     }
295 
296     @Override
297     public boolean readLine(
298             final CharArrayBuffer lineBuffer,
299             final boolean endOfStream) throws CharacterCodingException {
300 
301         setOutputMode();
302         // See if there is LF char present in the buffer
303         int pos = -1;
304         for (int i = this.buffer.position(); i < this.buffer.limit(); i++) {
305             final int b = this.buffer.get(i);
306             if (b == HTTP.LF) {
307                 pos = i + 1;
308                 break;
309             }
310         }
311 
312         final int maxLineLen = this.constraints.getMaxLineLength();
313         if (maxLineLen > 0) {
314             final int currentLen = (pos > 0 ? pos : this.buffer.limit()) - this.buffer.position();
315             if (currentLen >= maxLineLen) {
316                 throw new MessageConstraintException("Maximum line length limit exceeded");
317             }
318         }
319 
320         if (pos == -1) {
321             if (endOfStream && this.buffer.hasRemaining()) {
322                 // No more data. Get the rest
323                 pos = this.buffer.limit();
324             } else {
325                 // Either no complete line present in the buffer
326                 // or no more data is expected
327                 return false;
328             }
329         }
330         final int origLimit = this.buffer.limit();
331         this.buffer.limit(pos);
332 
333         final int requiredCapacity = this.buffer.limit() - this.buffer.position();
334         // Ensure capacity of len assuming ASCII as the most likely charset
335         lineBuffer.ensureCapacity(requiredCapacity);
336 
337         if (this.charDecoder == null) {
338             if (this.buffer.hasArray()) {
339                 final byte[] b = this.buffer.array();
340                 final int off = this.buffer.position();
341                 final int len = this.buffer.remaining();
342                 lineBuffer.append(b, off, len);
343                 this.buffer.position(off + len);
344             } else {
345                 while (this.buffer.hasRemaining()) {
346                     lineBuffer.append((char) (this.buffer.get() & 0xff));
347                 }
348             }
349         } else {
350             if (this.charBuffer == null) {
351                 this.charBuffer = CharBuffer.allocate(this.lineBufferSize);
352             }
353             this.charDecoder.reset();
354 
355             for (;;) {
356                 final CoderResult result = this.charDecoder.decode(
357                         this.buffer,
358                         this.charBuffer,
359                         true);
360                 if (result.isError()) {
361                     result.throwException();
362                 }
363                 if (result.isOverflow()) {
364                     this.charBuffer.flip();
365                     lineBuffer.append(
366                             this.charBuffer.array(),
367                             this.charBuffer.position(),
368                             this.charBuffer.remaining());
369                     this.charBuffer.clear();
370                 }
371                 if (result.isUnderflow()) {
372                     break;
373                 }
374             }
375 
376             // flush the decoder
377             this.charDecoder.flush(this.charBuffer);
378             this.charBuffer.flip();
379             // append the decoded content to the line buffer
380             if (this.charBuffer.hasRemaining()) {
381                 lineBuffer.append(
382                         this.charBuffer.array(),
383                         this.charBuffer.position(),
384                         this.charBuffer.remaining());
385             }
386 
387         }
388         this.buffer.limit(origLimit);
389 
390         // discard LF if found
391         int len = lineBuffer.length();
392         if (len > 0) {
393             if (lineBuffer.charAt(len - 1) == HTTP.LF) {
394                 len--;
395                 lineBuffer.setLength(len);
396             }
397             // discard CR if found
398             if (len > 0) {
399                 if (lineBuffer.charAt(len - 1) == HTTP.CR) {
400                     len--;
401                     lineBuffer.setLength(len);
402                 }
403             }
404         }
405         return true;
406     }
407 
408     @Override
409     public String readLine(final boolean endOfStream) throws CharacterCodingException {
410         final CharArrayBuffer tmpBuffer = new CharArrayBuffer(64);
411         final boolean found = readLine(tmpBuffer, endOfStream);
412         return found ? tmpBuffer.toString() : null;
413     }
414 
415     @Override
416     public void clear() {
417         super.clear();
418     }
419 
420 }