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.hc.core5.http.impl.nio;
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.Charset;
36  import java.nio.charset.CharsetDecoder;
37  import java.nio.charset.CoderResult;
38  
39  import org.apache.hc.core5.http.Chars;
40  import org.apache.hc.core5.http.MessageConstraintException;
41  import org.apache.hc.core5.http.nio.SessionInputBuffer;
42  import org.apache.hc.core5.util.Args;
43  import org.apache.hc.core5.util.CharArrayBuffer;
44  
45  /**
46   * Default implementation of {@link SessionInputBuffer} based on
47   * the {@link ExpandableBuffer} class.
48   *
49   * @since 4.0
50   */
51  public class SessionInputBufferImpl extends ExpandableBuffer implements SessionInputBuffer {
52  
53      private final CharsetDecoder charDecoder;
54      private final int lineBuffersize;
55      private final int maxLineLen;
56  
57      private CharBuffer charbuffer;
58  
59      /**
60       *  Creates SessionInputBufferImpl instance.
61       *
62       * @param bufferSize input buffer size
63       * @param lineBuffersize buffer size for line operations. Has effect only if
64       *   {@code charDecoder} is not {@code null}.
65       * @param charDecoder charDecoder to be used for decoding HTTP protocol elements.
66       *   If {@code null} simple type cast will be used for byte to char conversion.
67       * @param maxLineLen maximum line length.
68       *
69       * @since 4.4
70       */
71      public SessionInputBufferImpl(
72              final int bufferSize,
73              final int lineBuffersize,
74              final int maxLineLen,
75              final CharsetDecoder charDecoder) {
76          super(bufferSize);
77          this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
78          this.maxLineLen = maxLineLen > 0 ? maxLineLen : 0;
79          this.charDecoder = charDecoder;
80      }
81  
82      /**
83       * @since 4.3
84       */
85      public SessionInputBufferImpl(
86              final int bufferSize,
87              final int lineBuffersize,
88              final int maxLineLen,
89              final Charset charset) {
90          this(bufferSize, lineBuffersize, maxLineLen, charset != null ? charset.newDecoder() : null);
91      }
92  
93      /**
94       * @since 4.3
95       */
96      public SessionInputBufferImpl(
97              final int bufferSize,
98              final int lineBuffersize,
99              final int maxLineLen) {
100         this(bufferSize, lineBuffersize, maxLineLen, (CharsetDecoder) null);
101     }
102 
103     /**
104      * @since 4.3
105      */
106     public SessionInputBufferImpl(
107             final int bufferSize,
108             final int lineBuffersize) {
109         this(bufferSize, lineBuffersize, 0, (CharsetDecoder) null);
110     }
111 
112     /**
113      * @since 4.3
114      */
115     public SessionInputBufferImpl(final int bufferSize) {
116         this(bufferSize, 256);
117     }
118 
119     public void put(final ByteBuffer src) {
120         if (src != null && src.hasRemaining()) {
121             setInputMode();
122             ensureCapacity(src.remaining());
123             buffer().put(src);
124         }
125     }
126 
127     @Override
128     public int fill(final ReadableByteChannel channel) throws IOException {
129         Args.notNull(channel, "Channel");
130         setInputMode();
131         if (!buffer().hasRemaining()) {
132             expand();
133         }
134         return channel.read(buffer());
135     }
136 
137     @Override
138     public int read() {
139         setOutputMode();
140         return buffer().get() & 0xff;
141     }
142 
143     @Override
144     public int read(final ByteBuffer dst, final int maxLen) {
145         if (dst == null) {
146             return 0;
147         }
148         setOutputMode();
149         final int len = Math.min(dst.remaining(), maxLen);
150         final int chunk = Math.min(buffer().remaining(), len);
151         if (buffer().remaining() > chunk) {
152             final int oldLimit = buffer().limit();
153             final int newLimit = buffer().position() + chunk;
154             buffer().limit(newLimit);
155             dst.put(buffer());
156             buffer().limit(oldLimit);
157             return len;
158         }
159         dst.put(buffer());
160         return chunk;
161     }
162 
163     @Override
164     public int read(final ByteBuffer dst) {
165         if (dst == null) {
166             return 0;
167         }
168         return read(dst, dst.remaining());
169     }
170 
171     @Override
172     public int read(final WritableByteChannel dst, final int maxLen) throws IOException {
173         if (dst == null) {
174             return 0;
175         }
176         setOutputMode();
177         final int bytesRead;
178         if (buffer().remaining() > maxLen) {
179             final int oldLimit = buffer().limit();
180             final int newLimit = oldLimit - (buffer().remaining() - maxLen);
181             buffer().limit(newLimit);
182             bytesRead = dst.write(buffer());
183             buffer().limit(oldLimit);
184         } else {
185             bytesRead = dst.write(buffer());
186         }
187         return bytesRead;
188     }
189 
190     @Override
191     public int read(final WritableByteChannel dst) throws IOException {
192         if (dst == null) {
193             return 0;
194         }
195         setOutputMode();
196         return dst.write(buffer());
197     }
198 
199     @Override
200     public boolean readLine(
201             final CharArrayBuffer lineBuffer,
202             final boolean endOfStream) throws IOException {
203 
204         setOutputMode();
205         // See if there is LF char present in the buffer
206         int pos = -1;
207         for (int i = buffer().position(); i < buffer().limit(); i++) {
208             final int b = buffer().get(i);
209             if (b == Chars.LF) {
210                 pos = i + 1;
211                 break;
212             }
213         }
214 
215         if (this.maxLineLen > 0) {
216             final int currentLen = (pos > 0 ? pos : buffer().limit()) - buffer().position();
217             if (currentLen >= this.maxLineLen) {
218                 throw new MessageConstraintException("Maximum line length limit exceeded");
219             }
220         }
221 
222         if (pos == -1) {
223             if (endOfStream && buffer().hasRemaining()) {
224                 // No more data. Get the rest
225                 pos = buffer().limit();
226             } else {
227                 // Either no complete line present in the buffer
228                 // or no more data is expected
229                 return false;
230             }
231         }
232         final int origLimit = buffer().limit();
233         buffer().limit(pos);
234 
235         final int requiredCapacity = buffer().limit() - buffer().position();
236         // Ensure capacity of len assuming ASCII as the most likely charset
237         lineBuffer.ensureCapacity(requiredCapacity);
238 
239         if (this.charDecoder == null) {
240             if (buffer().hasArray()) {
241                 final byte[] b = buffer().array();
242                 final int off = buffer().position();
243                 final int len = buffer().remaining();
244                 lineBuffer.append(b, off, len);
245                 buffer().position(off + len);
246             } else {
247                 while (buffer().hasRemaining()) {
248                     lineBuffer.append((char) (buffer().get() & 0xff));
249                 }
250             }
251         } else {
252             if (this.charbuffer == null) {
253                 this.charbuffer = CharBuffer.allocate(this.lineBuffersize);
254             }
255             this.charDecoder.reset();
256 
257             for (;;) {
258                 final CoderResult result = this.charDecoder.decode(
259                         buffer(),
260                         this.charbuffer,
261                         true);
262                 if (result.isError()) {
263                     result.throwException();
264                 }
265                 if (result.isOverflow()) {
266                     this.charbuffer.flip();
267                     lineBuffer.append(
268                             this.charbuffer.array(),
269                             this.charbuffer.position(),
270                             this.charbuffer.remaining());
271                     this.charbuffer.clear();
272                 }
273                 if (result.isUnderflow()) {
274                     break;
275                 }
276             }
277 
278             // flush the decoder
279             this.charDecoder.flush(this.charbuffer);
280             this.charbuffer.flip();
281             // append the decoded content to the line buffer
282             if (this.charbuffer.hasRemaining()) {
283                 lineBuffer.append(
284                         this.charbuffer.array(),
285                         this.charbuffer.position(),
286                         this.charbuffer.remaining());
287             }
288 
289         }
290         buffer().limit(origLimit);
291 
292         // discard LF if found
293         int l = lineBuffer.length();
294         if (l > 0) {
295             if (lineBuffer.charAt(l - 1) == Chars.LF) {
296                 l--;
297                 lineBuffer.setLength(l);
298             }
299             // discard CR if found
300             if (l > 0 && lineBuffer.charAt(l - 1) == Chars.CR) {
301                 l--;
302                 lineBuffer.setLength(l);
303             }
304         }
305         return true;
306     }
307 
308 }