1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http.impl.io;
29
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.nio.ByteBuffer;
33 import java.nio.CharBuffer;
34 import java.nio.charset.CharsetDecoder;
35 import java.nio.charset.CoderResult;
36
37 import org.apache.hc.core5.http.Chars;
38 import org.apache.hc.core5.http.MessageConstraintException;
39 import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
40 import org.apache.hc.core5.http.io.HttpTransportMetrics;
41 import org.apache.hc.core5.http.io.SessionInputBuffer;
42 import org.apache.hc.core5.util.Args;
43 import org.apache.hc.core5.util.ByteArrayBuffer;
44 import org.apache.hc.core5.util.CharArrayBuffer;
45
46
47
48
49
50
51
52
53
54
55
56
57 public class SessionInputBufferImpl implements SessionInputBuffer {
58
59 private final BasicHttpTransportMetrics metrics;
60 private final byte[] buffer;
61 private final ByteArrayBuffer lineBuffer;
62 private final int minChunkLimit;
63 private final int maxLineLen;
64 private final CharsetDecoder decoder;
65
66 private int bufferPos;
67 private int bufferLen;
68 private CharBuffer cbuf;
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public SessionInputBufferImpl(
85 final BasicHttpTransportMetrics metrics,
86 final int bufferSize,
87 final int minChunkLimit,
88 final int maxLineLen,
89 final CharsetDecoder charDecoder) {
90 Args.notNull(metrics, "HTTP transport metrics");
91 Args.positive(bufferSize, "Buffer size");
92 this.metrics = metrics;
93 this.buffer = new byte[bufferSize];
94 this.bufferPos = 0;
95 this.bufferLen = 0;
96 this.minChunkLimit = minChunkLimit >= 0 ? minChunkLimit : 512;
97 this.maxLineLen = maxLineLen > 0 ? maxLineLen : 0;
98 this.lineBuffer = new ByteArrayBuffer(bufferSize);
99 this.decoder = charDecoder;
100 }
101
102 public SessionInputBufferImpl(
103 final BasicHttpTransportMetrics metrics,
104 final int bufferSize) {
105 this(metrics, bufferSize, bufferSize, 0, null);
106 }
107
108 public SessionInputBufferImpl(final int bufferSize, final int maxLineLen) {
109 this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, maxLineLen, null);
110 }
111
112 public SessionInputBufferImpl(final int bufferSize, final CharsetDecoder decoder) {
113 this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, 0, decoder);
114 }
115
116 public SessionInputBufferImpl(final int bufferSize) {
117 this(new BasicHttpTransportMetrics(), bufferSize, bufferSize, 0, null);
118 }
119
120 @Override
121 public int capacity() {
122 return this.buffer.length;
123 }
124
125 @Override
126 public int length() {
127 return this.bufferLen - this.bufferPos;
128 }
129
130 @Override
131 public int available() {
132 return capacity() - length();
133 }
134
135 public int fillBuffer(final InputStream inputStream) throws IOException {
136 Args.notNull(inputStream, "Input stream");
137
138 if (this.bufferPos > 0) {
139 final int len = this.bufferLen - this.bufferPos;
140 if (len > 0) {
141 System.arraycopy(this.buffer, this.bufferPos, this.buffer, 0, len);
142 }
143 this.bufferPos = 0;
144 this.bufferLen = len;
145 }
146 final int readLen;
147 final int off = this.bufferLen;
148 final int len = this.buffer.length - off;
149 readLen = inputStream.read(this.buffer, off, len);
150 if (readLen == -1) {
151 return -1;
152 }
153 this.bufferLen = off + readLen;
154 this.metrics.incrementBytesTransferred(readLen);
155 return readLen;
156 }
157
158 public boolean hasBufferedData() {
159 return this.bufferPos < this.bufferLen;
160 }
161
162 public void clear() {
163 this.bufferPos = 0;
164 this.bufferLen = 0;
165 }
166
167 @Override
168 public int read(final InputStream inputStream) throws IOException {
169 Args.notNull(inputStream, "Input stream");
170 int readLen;
171 while (!hasBufferedData()) {
172 readLen = fillBuffer(inputStream);
173 if (readLen == -1) {
174 return -1;
175 }
176 }
177 return this.buffer[this.bufferPos++] & 0xff;
178 }
179
180 @Override
181 public int read(final byte[] b, final int off, final int len, final InputStream inputStream) throws IOException {
182 Args.notNull(inputStream, "Input stream");
183 if (b == null) {
184 return 0;
185 }
186 if (hasBufferedData()) {
187 final int chunk = Math.min(len, this.bufferLen - this.bufferPos);
188 System.arraycopy(this.buffer, this.bufferPos, b, off, chunk);
189 this.bufferPos += chunk;
190 return chunk;
191 }
192
193
194 if (len > this.minChunkLimit) {
195 final int read = inputStream.read(b, off, len);
196 if (read > 0) {
197 this.metrics.incrementBytesTransferred(read);
198 }
199 return read;
200 }
201
202 while (!hasBufferedData()) {
203 final int readLen = fillBuffer(inputStream);
204 if (readLen == -1) {
205 return -1;
206 }
207 }
208 final int chunk = Math.min(len, this.bufferLen - this.bufferPos);
209 System.arraycopy(this.buffer, this.bufferPos, b, off, chunk);
210 this.bufferPos += chunk;
211 return chunk;
212 }
213
214 @Override
215 public int read(final byte[] b, final InputStream inputStream) throws IOException {
216 if (b == null) {
217 return 0;
218 }
219 return read(b, 0, b.length, inputStream);
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 @Override
240 public int readLine(final CharArrayBuffer charBuffer, final InputStream inputStream) throws IOException {
241 Args.notNull(charBuffer, "Char array buffer");
242 Args.notNull(inputStream, "Input stream");
243 int readLen = 0;
244 boolean retry = true;
245 while (retry) {
246
247 int pos = -1;
248 for (int i = this.bufferPos; i < this.bufferLen; i++) {
249 if (this.buffer[i] == Chars.LF) {
250 pos = i;
251 break;
252 }
253 }
254
255 if (this.maxLineLen > 0) {
256 final int currentLen = this.lineBuffer.length()
257 + (pos >= 0 ? pos : this.bufferLen) - this.bufferPos;
258 if (currentLen >= this.maxLineLen) {
259 throw new MessageConstraintException("Maximum line length limit exceeded");
260 }
261 }
262
263 if (pos != -1) {
264
265 if (this.lineBuffer.isEmpty()) {
266
267 return lineFromReadBuffer(charBuffer, pos);
268 }
269 retry = false;
270 final int len = pos + 1 - this.bufferPos;
271 this.lineBuffer.append(this.buffer, this.bufferPos, len);
272 this.bufferPos = pos + 1;
273 } else {
274
275 if (hasBufferedData()) {
276 final int len = this.bufferLen - this.bufferPos;
277 this.lineBuffer.append(this.buffer, this.bufferPos, len);
278 this.bufferPos = this.bufferLen;
279 }
280 readLen = fillBuffer(inputStream);
281 if (readLen == -1) {
282 retry = false;
283 }
284 }
285 }
286 if (readLen == -1 && this.lineBuffer.isEmpty()) {
287
288 return -1;
289 }
290 return lineFromLineBuffer(charBuffer);
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306 private int lineFromLineBuffer(final CharArrayBuffer charBuffer)
307 throws IOException {
308
309 int len = this.lineBuffer.length();
310 if (len > 0) {
311 if (this.lineBuffer.byteAt(len - 1) == Chars.LF) {
312 len--;
313 }
314
315 if (len > 0 && this.lineBuffer.byteAt(len - 1) == Chars.CR) {
316 len--;
317 }
318 }
319 if (this.decoder == null) {
320 charBuffer.append(this.lineBuffer, 0, len);
321 } else {
322 final ByteBuffer bbuf = ByteBuffer.wrap(this.lineBuffer.array(), 0, len);
323 len = appendDecoded(charBuffer, bbuf);
324 }
325 this.lineBuffer.clear();
326 return len;
327 }
328
329 private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position)
330 throws IOException {
331 int pos = position;
332 final int off = this.bufferPos;
333 int len;
334 this.bufferPos = pos + 1;
335 if (pos > off && this.buffer[pos - 1] == Chars.CR) {
336
337 pos--;
338 }
339 len = pos - off;
340 if (this.decoder == null) {
341 charbuffer.append(this.buffer, off, len);
342 } else {
343 final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len);
344 len = appendDecoded(charbuffer, bbuf);
345 }
346 return len;
347 }
348
349 private int appendDecoded(
350 final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException {
351 if (!bbuf.hasRemaining()) {
352 return 0;
353 }
354 if (this.cbuf == null) {
355 this.cbuf = CharBuffer.allocate(1024);
356 }
357 this.decoder.reset();
358 int len = 0;
359 while (bbuf.hasRemaining()) {
360 final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true);
361 len += handleDecodingResult(result, charbuffer);
362 }
363 final CoderResult result = this.decoder.flush(this.cbuf);
364 len += handleDecodingResult(result, charbuffer);
365 this.cbuf.clear();
366 return len;
367 }
368
369 private int handleDecodingResult(
370 final CoderResult result,
371 final CharArrayBuffer charBuffer) throws IOException {
372 if (result.isError()) {
373 result.throwException();
374 }
375 this.cbuf.flip();
376 final int len = this.cbuf.remaining();
377 while (this.cbuf.hasRemaining()) {
378 charBuffer.append(this.cbuf.get());
379 }
380 this.cbuf.compact();
381 return len;
382 }
383
384 @Override
385 public HttpTransportMetrics getMetrics() {
386 return this.metrics;
387 }
388
389 }