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