001// Copyright (C) 1999-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
002// All rights reserved.  Use of this class is limited.
003// Please see the LICENSE for more information.
004 
005package com.oreilly.servlet.multipart;
006
007import java.io.IOException;
008
009import javax.servlet.ServletInputStream;
010
011/**
012 * A <code>BufferedServletInputStream</code> wraps a 
013 * <code>ServletInputStream</code> in order to provide input buffering and to 
014 * avoid calling the the <code>readLine</code> method of the wrapped 
015 * <code>ServletInputStream</code>.
016 * <p>
017 * This is necessary because some servlet containers rely on the default 
018 * implementation of the <code>readLine</code> method provided by the Servlet 
019 * API classes, which is very slow. Tomcat 3.2, Tomcat 3.1, the JSWDK 1.0 web 
020 * server and the JSDK2.1 web server are all known to need this class for 
021 * performance reasons. 
022 * <p>
023 * Also, it may be used to work around a bug in the Servlet API 2.0 
024 * implementation of <code>readLine</code> which contains a bug that causes
025 * <code>ArrayIndexOutOfBoundsExceptions</code> under certain conditions.
026 * Apache JServ is known to suffer from this bug.
027 * 
028 * @author Geoff Soutter
029 * @version 1.1, 2001/05/21, removed block of commented out code
030 * @version 1.0, 2000/10/27, initial revision
031 */
032public class BufferedServletInputStream extends ServletInputStream {
033  
034  /** input stream we are filtering */
035  private ServletInputStream in;
036  
037  /** our buffer */
038  private byte[] buf = new byte[64*1024];  // 64k
039  
040  /** number of bytes we've read into the buffer */
041  private int count; 
042  
043  /** current position in the buffer */
044  private int pos;
045  
046  /**
047   * Creates a <code>BufferedServletInputStream</code> that wraps the provided 
048   * <code>ServletInputStream</code>.
049   * 
050   * @param in  a servlet input stream.
051   */
052  public BufferedServletInputStream(ServletInputStream in) {
053    this.in = in;
054  }
055
056  /**
057   * Fill up our buffer from the underlying input stream. Users of this
058   * method must ensure that they use all characters in the buffer before
059   * calling this method.
060   * 
061   * @exception  IOException  if an I/O error occurs.
062   */
063  private void fill() throws IOException {
064    int i = in.read(buf, 0, buf.length);
065    if (i > 0) {
066      pos = 0;
067      count = i;
068    }
069  }
070    
071  /**
072   * Implement buffering on top of the <code>readLine</code> method of 
073   * the wrapped <code>ServletInputStream</code>.
074   *
075   * @param b    an array of bytes into which data is read.
076   * @param off  an integer specifying the character at which
077   *        this method begins reading.
078   * @param len  an integer specifying the maximum number of 
079   *        bytes to read.
080   * @return     an integer specifying the actual number of bytes 
081   *        read, or -1 if the end of the stream is reached.
082   * @exception  IOException  if an I/O error occurs.
083   */
084  public int readLine(byte b[], int off, int len) throws IOException {
085    int total = 0;
086    if (len == 0) {
087      return 0;
088    }
089    
090    int avail = count - pos;
091    if (avail <= 0) {
092      fill();
093      avail = count - pos;
094      if (avail <= 0) {
095        return -1;
096      }
097    }
098    int copy = Math.min(len, avail);
099    int eol = findeol(buf, pos, copy);
100    if (eol != -1) {
101      copy = eol;
102    }
103    System.arraycopy(buf, pos, b, off, copy);
104    pos += copy;
105    total += copy;
106    
107    while (total < len && eol == -1) {
108      fill();
109      avail = count - pos;
110      if(avail <= 0) {
111        return total;
112      }
113      copy = Math.min(len - total, avail);
114      eol = findeol(buf, pos, copy);
115      if (eol != -1) {
116        copy = eol;
117      }
118      System.arraycopy(buf, pos, b, off + total, copy);
119      pos += copy;
120      total += copy;
121    }
122    return total;
123  }
124
125  /**
126   * Attempt to find the '\n' end of line marker as defined in the comment of
127   * the <code>readLine</code> method of <code>ServletInputStream</code>.
128   * 
129   * @param b byte array to search.
130   * @param pos position in byte array to search from.
131   * @param len maximum number of bytes to search.
132   * 
133   * @return the number of bytes including the \n, or -1 if none found.
134   */
135  private static int findeol(byte b[], int pos, int len) {
136    int end = pos + len;
137    int i = pos;
138    while (i < end) {
139      if (b[i++] == '\n') {
140        return i - pos;
141      }
142    }
143    return -1;
144  }
145  
146  /**
147   * Implement buffering on top of the <code>read</code> method of 
148   * the wrapped <code>ServletInputStream</code>.
149   *
150   * @return     the next byte of data, or <code>-1</code> if the end of the
151   *             stream is reached.
152   * @exception  IOException  if an I/O error occurs.
153   */
154  public int read() throws IOException {
155    if (count <= pos) {
156      fill();
157      if (count <= pos) {
158        return -1;
159      }
160    }
161    return buf[pos++] & 0xff;
162  }
163
164  /**
165   * Implement buffering on top of the <code>read</code> method of 
166   * the wrapped <code>ServletInputStream</code>.
167   *
168   * @param      b     the buffer into which the data is read.
169   * @param      off   the start offset of the data.
170   * @param      len   the maximum number of bytes read.
171   * @return     the total number of bytes read into the buffer, or
172   *             <code>-1</code> if there is no more data because the end
173   *             of the stream has been reached.
174   * @exception  IOException  if an I/O error occurs.
175   */
176  public int read(byte b[], int off, int len) throws IOException
177  {
178    int total = 0;
179    while (total < len) {
180      int avail = count - pos;
181      if (avail <= 0) {
182        fill();
183        avail = count - pos;
184        if(avail <= 0) {
185          if (total > 0)
186            return total;
187          else
188            return -1;
189        }
190      }
191      int copy = Math.min(len - total, avail);
192      System.arraycopy(buf, pos, b, off + total, copy);
193      pos += copy;
194      total += copy;
195    }
196    return total;
197  }
198}