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