001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.connector;
019:
020: import java.io.IOException;
021: import java.io.Reader;
022: import java.security.AccessController;
023: import java.security.PrivilegedActionException;
024: import java.security.PrivilegedExceptionAction;
025: import java.util.HashMap;
026:
027: import org.apache.catalina.security.SecurityUtil;
028: import org.apache.coyote.ActionCode;
029: import org.apache.coyote.Request;
030: import org.apache.tomcat.util.buf.B2CConverter;
031: import org.apache.tomcat.util.buf.ByteChunk;
032: import org.apache.tomcat.util.buf.CharChunk;
033:
034: /**
035: * The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
036: * OutputBuffer, adapted to handle input instead of output. This allows
037: * complete recycling of the facade objects (the ServletInputStream and the
038: * BufferedReader).
039: *
040: * @author Remy Maucherat
041: */
042: public class InputBuffer extends Reader implements
043: ByteChunk.ByteInputChannel, CharChunk.CharInputChannel,
044: CharChunk.CharOutputChannel {
045:
046: // -------------------------------------------------------------- Constants
047:
048: public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
049: public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
050:
051: // The buffer can be used for byte[] and char[] reading
052: // ( this is needed to support ServletInputStream and BufferedReader )
053: public final int INITIAL_STATE = 0;
054: public final int CHAR_STATE = 1;
055: public final int BYTE_STATE = 2;
056:
057: // ----------------------------------------------------- Instance Variables
058:
059: /**
060: * The byte buffer.
061: */
062: private ByteChunk bb;
063:
064: /**
065: * The chunk buffer.
066: */
067: private CharChunk cb;
068:
069: /**
070: * State of the output buffer.
071: */
072: private int state = 0;
073:
074: /**
075: * Number of bytes read.
076: */
077: private int bytesRead = 0;
078:
079: /**
080: * Number of chars read.
081: */
082: private int charsRead = 0;
083:
084: /**
085: * Flag which indicates if the input buffer is closed.
086: */
087: private boolean closed = false;
088:
089: /**
090: * Byte chunk used to input bytes.
091: */
092: private ByteChunk inputChunk = new ByteChunk();
093:
094: /**
095: * Encoding to use.
096: */
097: private String enc;
098:
099: /**
100: * Encoder is set.
101: */
102: private boolean gotEnc = false;
103:
104: /**
105: * List of encoders.
106: */
107: protected HashMap encoders = new HashMap();
108:
109: /**
110: * Current byte to char converter.
111: */
112: protected B2CConverter conv;
113:
114: /**
115: * Associated Coyote request.
116: */
117: private Request coyoteRequest;
118:
119: /**
120: * Buffer position.
121: */
122: private int markPos = -1;
123:
124: /**
125: * Buffer size.
126: */
127: private int size = -1;
128:
129: // ----------------------------------------------------------- Constructors
130:
131: /**
132: * Default constructor. Allocate the buffer with the default buffer size.
133: */
134: public InputBuffer() {
135:
136: this (DEFAULT_BUFFER_SIZE);
137:
138: }
139:
140: /**
141: * Alternate constructor which allows specifying the initial buffer size.
142: *
143: * @param size Buffer size to use
144: */
145: public InputBuffer(int size) {
146:
147: this .size = size;
148: bb = new ByteChunk(size);
149: bb.setLimit(size);
150: bb.setByteInputChannel(this );
151: cb = new CharChunk(size);
152: cb.setLimit(size);
153: cb.setOptimizedWrite(false);
154: cb.setCharInputChannel(this );
155: cb.setCharOutputChannel(this );
156:
157: }
158:
159: // ------------------------------------------------------------- Properties
160:
161: /**
162: * Associated Coyote request.
163: *
164: * @param coyoteRequest Associated Coyote request
165: */
166: public void setRequest(Request coyoteRequest) {
167: this .coyoteRequest = coyoteRequest;
168: }
169:
170: /**
171: * Get associated Coyote request.
172: *
173: * @return the associated Coyote request
174: */
175: public Request getRequest() {
176: return this .coyoteRequest;
177: }
178:
179: // --------------------------------------------------------- Public Methods
180:
181: /**
182: * Recycle the output buffer.
183: */
184: public void recycle() {
185:
186: state = INITIAL_STATE;
187: bytesRead = 0;
188: charsRead = 0;
189:
190: // If usage of mark made the buffer too big, reallocate it
191: if (cb.getChars().length > size) {
192: cb = new CharChunk(size);
193: cb.setLimit(size);
194: cb.setOptimizedWrite(false);
195: cb.setCharInputChannel(this );
196: cb.setCharOutputChannel(this );
197: } else {
198: cb.recycle();
199: }
200: markPos = -1;
201: bb.recycle();
202: closed = false;
203:
204: if (conv != null) {
205: conv.recycle();
206: }
207:
208: gotEnc = false;
209: enc = null;
210:
211: }
212:
213: /**
214: * Clear cached encoders (to save memory for Comet requests).
215: */
216: public void clearEncoders() {
217: encoders.clear();
218: }
219:
220: /**
221: * Close the input buffer.
222: *
223: * @throws IOException An underlying IOException occurred
224: */
225: public void close() throws IOException {
226: closed = true;
227: }
228:
229: public int available() {
230: int available = 0;
231: if (state == BYTE_STATE) {
232: available = bb.getLength();
233: } else if (state == CHAR_STATE) {
234: available = cb.getLength();
235: }
236: if (available == 0) {
237: coyoteRequest.action(ActionCode.ACTION_AVAILABLE, null);
238: available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
239: }
240: return available;
241: }
242:
243: // ------------------------------------------------- Bytes Handling Methods
244:
245: /**
246: * Reads new bytes in the byte chunk.
247: *
248: * @param cbuf Byte buffer to be written to the response
249: * @param off Offset
250: * @param len Length
251: *
252: * @throws IOException An underlying IOException occurred
253: */
254: public int realReadBytes(byte cbuf[], int off, int len)
255: throws IOException {
256:
257: if (closed)
258: return -1;
259: if (coyoteRequest == null)
260: return -1;
261:
262: state = BYTE_STATE;
263:
264: int result = coyoteRequest.doRead(bb);
265:
266: return result;
267:
268: }
269:
270: public int readByte() throws IOException {
271: return bb.substract();
272: }
273:
274: public int read(byte[] b, int off, int len) throws IOException {
275: return bb.substract(b, off, len);
276: }
277:
278: // ------------------------------------------------- Chars Handling Methods
279:
280: /**
281: * Since the converter will use append, it is possible to get chars to
282: * be removed from the buffer for "writing". Since the chars have already
283: * been read before, they are ignored. If a mark was set, then the
284: * mark is lost.
285: */
286: public void realWriteChars(char c[], int off, int len)
287: throws IOException {
288: markPos = -1;
289: }
290:
291: public void setEncoding(String s) {
292: enc = s;
293: }
294:
295: public int realReadChars(char cbuf[], int off, int len)
296: throws IOException {
297:
298: if (!gotEnc)
299: setConverter();
300:
301: if (bb.getLength() <= 0) {
302: int nRead = realReadBytes(bb.getBytes(), 0,
303: bb.getBytes().length);
304: if (nRead < 0) {
305: return -1;
306: }
307: }
308:
309: if (markPos == -1) {
310: cb.setOffset(0);
311: cb.setEnd(0);
312: }
313:
314: int limit = bb.getLength() + cb.getStart();
315: if (cb.getLimit() < limit)
316: cb.setLimit(limit);
317: conv.convert(bb, cb);
318: bb.setOffset(bb.getEnd());
319: state = CHAR_STATE;
320:
321: return cb.getLength();
322:
323: }
324:
325: public int read() throws IOException {
326: return cb.substract();
327: }
328:
329: public int read(char[] cbuf) throws IOException {
330: return read(cbuf, 0, cbuf.length);
331: }
332:
333: public int read(char[] cbuf, int off, int len) throws IOException {
334: return cb.substract(cbuf, off, len);
335: }
336:
337: public long skip(long n) throws IOException {
338:
339: if (n < 0) {
340: throw new IllegalArgumentException();
341: }
342:
343: long nRead = 0;
344: while (nRead < n) {
345: if (cb.getLength() >= n) {
346: cb.setOffset(cb.getStart() + (int) n);
347: nRead = n;
348: } else {
349: nRead += cb.getLength();
350: cb.setOffset(cb.getEnd());
351: int toRead = 0;
352: if (cb.getChars().length < (n - nRead)) {
353: toRead = cb.getChars().length;
354: } else {
355: toRead = (int) (n - nRead);
356: }
357: int nb = realReadChars(cb.getChars(), 0, toRead);
358: if (nb < 0)
359: break;
360: }
361: }
362:
363: return nRead;
364:
365: }
366:
367: public boolean ready() throws IOException {
368: return (available() > 0);
369: }
370:
371: public boolean markSupported() {
372: return true;
373: }
374:
375: public void mark(int readAheadLimit) throws IOException {
376: if (cb.getLength() <= 0) {
377: cb.setOffset(0);
378: cb.setEnd(0);
379: } else {
380: if ((cb.getBuffer().length > (2 * size))
381: && (cb.getLength()) < (cb.getStart())) {
382: System.arraycopy(cb.getBuffer(), cb.getStart(), cb
383: .getBuffer(), 0, cb.getLength());
384: cb.setEnd(cb.getLength());
385: cb.setOffset(0);
386: }
387: }
388: int offset = readAheadLimit;
389: if (offset < size) {
390: offset = size;
391: }
392: cb.setLimit(cb.getStart() + offset);
393: markPos = cb.getStart();
394: }
395:
396: public void reset() throws IOException {
397: if (state == CHAR_STATE) {
398: if (markPos < 0) {
399: cb.recycle();
400: markPos = -1;
401: throw new IOException();
402: } else {
403: cb.setOffset(markPos);
404: }
405: } else {
406: bb.recycle();
407: }
408: }
409:
410: public void checkConverter() throws IOException {
411:
412: if (!gotEnc)
413: setConverter();
414:
415: }
416:
417: protected void setConverter() throws IOException {
418:
419: if (coyoteRequest != null)
420: enc = coyoteRequest.getCharacterEncoding();
421:
422: gotEnc = true;
423: if (enc == null)
424: enc = DEFAULT_ENCODING;
425: conv = (B2CConverter) encoders.get(enc);
426: if (conv == null) {
427: if (SecurityUtil.isPackageProtectionEnabled()) {
428: try {
429: conv = (B2CConverter) AccessController
430: .doPrivileged(new PrivilegedExceptionAction() {
431:
432: public Object run() throws IOException {
433: return new B2CConverter(enc);
434: }
435:
436: });
437: } catch (PrivilegedActionException ex) {
438: Exception e = ex.getException();
439: if (e instanceof IOException)
440: throw (IOException) e;
441: }
442: } else {
443: conv = new B2CConverter(enc);
444: }
445: encoders.put(enc, conv);
446: }
447:
448: }
449:
450: }
|