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}