1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.catalina.connector;
19
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.security.AccessController;
23 import java.security.PrivilegedActionException;
24 import java.security.PrivilegedExceptionAction;
25 import java.util.HashMap;
26
27 import org.apache.catalina.security.SecurityUtil;
28 import org.apache.coyote.ActionCode;
29 import org.apache.coyote.Request;
30 import org.apache.tomcat.util.buf.B2CConverter;
31 import org.apache.tomcat.util.buf.ByteChunk;
32 import org.apache.tomcat.util.buf.CharChunk;
33
34
35 /**
36 * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
37 * OutputBuffer, adapted to handle input instead of output. This allows
38 * complete recycling of the facade objects (the ServletInputStream and the
39 * BufferedReader).
40 *
41 * @author Remy Maucherat
42 */
43 public class InputBuffer extends Reader
44 implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel,
45 CharChunk.CharOutputChannel {
46
47
48 // -------------------------------------------------------------- Constants
49
50
51 public static final String DEFAULT_ENCODING =
52 org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
53 public static final int DEFAULT_BUFFER_SIZE = 8*1024;
54
55 // The buffer can be used for byte[] and char[] reading
56 // ( this is needed to support ServletInputStream and BufferedReader )
57 public final int INITIAL_STATE = 0;
58 public final int CHAR_STATE = 1;
59 public final int BYTE_STATE = 2;
60
61
62 // ----------------------------------------------------- Instance Variables
63
64
65 /**
66 * The byte buffer.
67 */
68 private ByteChunk bb;
69
70
71 /**
72 * The chunk buffer.
73 */
74 private CharChunk cb;
75
76
77 /**
78 * State of the output buffer.
79 */
80 private int state = 0;
81
82
83 /**
84 * Number of bytes read.
85 */
86 private int bytesRead = 0;
87
88
89 /**
90 * Number of chars read.
91 */
92 private int charsRead = 0;
93
94
95 /**
96 * Flag which indicates if the input buffer is closed.
97 */
98 private boolean closed = false;
99
100
101 /**
102 * Byte chunk used to input bytes.
103 */
104 private ByteChunk inputChunk = new ByteChunk();
105
106
107 /**
108 * Encoding to use.
109 */
110 private String enc;
111
112
113 /**
114 * Encoder is set.
115 */
116 private boolean gotEnc = false;
117
118
119 /**
120 * List of encoders.
121 */
122 protected HashMap encoders = new HashMap();
123
124
125 /**
126 * Current byte to char converter.
127 */
128 protected B2CConverter conv;
129
130
131 /**
132 * Associated Coyote request.
133 */
134 private Request coyoteRequest;
135
136
137 /**
138 * Buffer position.
139 */
140 private int markPos = -1;
141
142
143 /**
144 * Buffer size.
145 */
146 private int size = -1;
147
148
149 // ----------------------------------------------------------- Constructors
150
151
152 /**
153 * Default constructor. Allocate the buffer with the default buffer size.
154 */
155 public InputBuffer() {
156
157 this(DEFAULT_BUFFER_SIZE);
158
159 }
160
161
162 /**
163 * Alternate constructor which allows specifying the initial buffer size.
164 *
165 * @param size Buffer size to use
166 */
167 public InputBuffer(int size) {
168
169 this.size = size;
170 bb = new ByteChunk(size);
171 bb.setLimit(size);
172 bb.setByteInputChannel(this);
173 cb = new CharChunk(size);
174 cb.setLimit(size);
175 cb.setOptimizedWrite(false);
176 cb.setCharInputChannel(this);
177 cb.setCharOutputChannel(this);
178
179 }
180
181
182 // ------------------------------------------------------------- Properties
183
184
185 /**
186 * Associated Coyote request.
187 *
188 * @param coyoteRequest Associated Coyote request
189 */
190 public void setRequest(Request coyoteRequest) {
191 this.coyoteRequest = coyoteRequest;
192 }
193
194
195 /**
196 * Get associated Coyote request.
197 *
198 * @return the associated Coyote request
199 */
200 public Request getRequest() {
201 return this.coyoteRequest;
202 }
203
204
205 // --------------------------------------------------------- Public Methods
206
207
208 /**
209 * Recycle the output buffer.
210 */
211 public void recycle() {
212
213 state = INITIAL_STATE;
214 bytesRead = 0;
215 charsRead = 0;
216
217 // If usage of mark made the buffer too big, reallocate it
218 if (cb.getChars().length > size) {
219 cb = new CharChunk(size);
220 cb.setLimit(size);
221 cb.setOptimizedWrite(false);
222 cb.setCharInputChannel(this);
223 cb.setCharOutputChannel(this);
224 } else {
225 cb.recycle();
226 }
227 markPos = -1;
228 bb.recycle();
229 closed = false;
230
231 if (conv != null) {
232 conv.recycle();
233 }
234
235 gotEnc = false;
236 enc = null;
237
238 }
239
240
241 /**
242 * Clear cached encoders (to save memory for Comet requests).
243 */
244 public void clearEncoders() {
245 encoders.clear();
246 }
247
248
249 /**
250 * Close the input buffer.
251 *
252 * @throws IOException An underlying IOException occurred
253 */
254 public void close()
255 throws IOException {
256 closed = true;
257 }
258
259
260 public int available() {
261 int available = 0;
262 if (state == BYTE_STATE) {
263 available = bb.getLength();
264 } else if (state == CHAR_STATE) {
265 available = cb.getLength();
266 }
267 if (available == 0) {
268 coyoteRequest.action(ActionCode.ACTION_AVAILABLE, null);
269 available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
270 }
271 return available;
272 }
273
274
275 // ------------------------------------------------- Bytes Handling Methods
276
277
278 /**
279 * Reads new bytes in the byte chunk.
280 *
281 * @param cbuf Byte buffer to be written to the response
282 * @param off Offset
283 * @param len Length
284 *
285 * @throws IOException An underlying IOException occurred
286 */
287 public int realReadBytes(byte cbuf[], int off, int len)
288 throws IOException {
289
290 if (closed)
291 return -1;
292 if (coyoteRequest == null)
293 return -1;
294
295 if(state == INITIAL_STATE)
296 state = BYTE_STATE;
297
298 int result = coyoteRequest.doRead(bb);
299
300 return result;
301
302 }
303
304
305 public int readByte()
306 throws IOException {
307 return bb.substract();
308 }
309
310
311 public int read(byte[] b, int off, int len)
312 throws IOException {
313 return bb.substract(b, off, len);
314 }
315
316
317 // ------------------------------------------------- Chars Handling Methods
318
319
320 /**
321 * Since the converter will use append, it is possible to get chars to
322 * be removed from the buffer for "writing". Since the chars have already
323 * been read before, they are ignored. If a mark was set, then the
324 * mark is lost.
325 */
326 public void realWriteChars(char c[], int off, int len)
327 throws IOException {
328 markPos = -1;
329 cb.setOffset(0);
330 cb.setEnd(0);
331 }
332
333
334 public void setEncoding(String s) {
335 enc = s;
336 }
337
338
339 public int realReadChars(char cbuf[], int off, int len)
340 throws IOException {
341
342 if (!gotEnc)
343 setConverter();
344
345 if (bb.getLength() <= 0) {
346 int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
347 if (nRead < 0) {
348 return -1;
349 }
350 }
351
352 if (markPos == -1) {
353 cb.setOffset(0);
354 cb.setEnd(0);
355 }
356
357 state = CHAR_STATE;
358 conv.convert(bb, cb, len);
359 bb.setOffset(bb.getEnd());
360
361 return cb.getLength();
362
363 }
364
365
366 public int read()
367 throws IOException {
368 return cb.substract();
369 }
370
371
372 public int read(char[] cbuf)
373 throws IOException {
374 return read(cbuf, 0, cbuf.length);
375 }
376
377
378 public int read(char[] cbuf, int off, int len)
379 throws IOException {
380 return cb.substract(cbuf, off, len);
381 }
382
383
384 public long skip(long n)
385 throws IOException {
386
387 if (n < 0) {
388 throw new IllegalArgumentException();
389 }
390
391 long nRead = 0;
392 while (nRead < n) {
393 if (cb.getLength() >= n) {
394 cb.setOffset(cb.getStart() + (int) n);
395 nRead = n;
396 } else {
397 nRead += cb.getLength();
398 cb.setOffset(cb.getEnd());
399 int toRead = 0;
400 if (cb.getChars().length < (n - nRead)) {
401 toRead = cb.getChars().length;
402 } else {
403 toRead = (int) (n - nRead);
404 }
405 int nb = realReadChars(cb.getChars(), 0, toRead);
406 if (nb < 0)
407 break;
408 }
409 }
410
411 return nRead;
412
413 }
414
415
416 public boolean ready()
417 throws IOException {
418 return (available() > 0);
419 }
420
421
422 public boolean markSupported() {
423 return true;
424 }
425
426
427 public void mark(int readAheadLimit)
428 throws IOException {
429 if (cb.getLength() <= 0) {
430 cb.setOffset(0);
431 cb.setEnd(0);
432 } else {
433 if ((cb.getBuffer().length > (2 * size))
434 && (cb.getLength()) < (cb.getStart())) {
435 System.arraycopy(cb.getBuffer(), cb.getStart(),
436 cb.getBuffer(), 0, cb.getLength());
437 cb.setEnd(cb.getLength());
438 cb.setOffset(0);
439 }
440 }
441 int offset = readAheadLimit;
442 if (offset < size) {
443 offset = size;
444 }
445 cb.setLimit(cb.getStart() + offset);
446 markPos = cb.getStart();
447 }
448
449
450 public void reset()
451 throws IOException {
452 if (state == CHAR_STATE) {
453 if (markPos < 0) {
454 cb.recycle();
455 markPos = -1;
456 throw new IOException();
457 } else {
458 cb.setOffset(markPos);
459 }
460 } else {
461 bb.recycle();
462 }
463 }
464
465
466 public void checkConverter()
467 throws IOException {
468
469 if (!gotEnc)
470 setConverter();
471
472 }
473
474
475 protected void setConverter()
476 throws IOException {
477
478 if (coyoteRequest != null)
479 enc = coyoteRequest.getCharacterEncoding();
480
481 gotEnc = true;
482 if (enc == null)
483 enc = DEFAULT_ENCODING;
484 conv = (B2CConverter) encoders.get(enc);
485 if (conv == null) {
486 if (SecurityUtil.isPackageProtectionEnabled()){
487 try{
488 conv = (B2CConverter)AccessController.doPrivileged(
489 new PrivilegedExceptionAction(){
490
491 public Object run() throws IOException{
492 return new B2CConverter(enc);
493 }
494
495 }
496 );
497 }catch(PrivilegedActionException ex){
498 Exception e = ex.getException();
499 if (e instanceof IOException)
500 throw (IOException)e;
501 }
502 } else {
503 conv = new B2CConverter(enc);
504 }
505 encoders.put(enc, conv);
506 }
507
508 }
509
510 }