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.FilterInputStream; 008import java.io.IOException; 009import javax.servlet.ServletInputStream; 010 011/** 012 * A <code>PartInputStream</code> filters a <code>ServletInputStream</code>, 013 * providing access to a single MIME part contained with in which ends with 014 * the boundary specified. It uses buffering to provide maximum performance. 015 * <p> 016 * Note the <code>readLine</code> method of <code>ServletInputStream</code> 017 * has the annoying habit of adding a \r\n to the end of the last line. Since 018 * we want a byte-for-byte transfer, we have to cut those chars. This means 019 * that we must always maintain at least 2 characters in our buffer to allow 020 * us to trim when necessary. 021 * 022 * @author Geoff Soutter 023 * @author Jason Hunter 024 * @version 1.4, 2002/11/01, fix for "unexpected end of part" caused by 025 * boundary newlines split across buffers 026 * @version 1.3, 2001/05/21, fix to handle boundaries crossing 64K mark 027 * @version 1.2, 2001/02/07, added read(byte[]) implementation for safety 028 * @version 1.1, 2000/11/26, fixed available() to never return negative 029 * @version 1.0, 2000/10/27, initial revision 030 */ 031public class PartInputStream extends FilterInputStream { 032 /** boundary which "ends" the stream */ 033 private String boundary; 034 035 /** our buffer */ 036 private byte [] buf = new byte[64*1024]; // 64k 037 038 /** number of bytes we've read into the buffer */ 039 private int count; 040 041 /** current position in the buffer */ 042 private int pos; 043 044 /** flag that indicates if we have encountered the boundary */ 045 private boolean eof; 046 047 /** 048 * Creates a <code>PartInputStream</code> which stops at the specified 049 * boundary from a <code>ServletInputStream<code>. 050 * 051 * @param in a servlet input stream. 052 * @param boundary the MIME boundary to stop at. 053 */ 054 PartInputStream(ServletInputStream in, 055 String boundary) throws IOException { 056 super(in); 057 this.boundary = boundary; 058 } 059 060 /** 061 * Fill up our buffer from the underlying input stream, and check for the 062 * boundary that signifies end-of-file. Users of this method must ensure 063 * that they leave exactly 2 characters in the buffer before calling this 064 * method (except the first time), so that we may only use these characters 065 * if a boundary is not found in the first line read. 066 * 067 * @exception IOException if an I/O error occurs. 068 */ 069 private void fill() throws IOException 070 { 071 if (eof) 072 return; 073 074 // as long as we are not just starting up 075 if (count > 0) 076 { 077 // if the caller left the requisite amount spare in the buffer 078 if (count - pos == 2) { 079 // copy it back to the start of the buffer 080 System.arraycopy(buf, pos, buf, 0, count - pos); 081 count -= pos; 082 pos = 0; 083 } else { 084 // should never happen, but just in case 085 throw new IllegalStateException("fill() detected illegal buffer state"); 086 } 087 } 088 089 // Try and fill the entire buffer, starting at count, line by line 090 // but never read so close to the end that we might split a boundary 091 // Thanks to Tony Chu, tony.chu@brio.com, for the -2 suggestion. 092 int read = 0; 093 int boundaryLength = boundary.length(); 094 int maxRead = buf.length - boundaryLength - 2; // -2 is for /r/n 095 while (count < maxRead) { 096 // read a line 097 read = ((ServletInputStream)in).readLine(buf, count, buf.length - count); 098 // check for eof and boundary 099 if (read == -1) { 100 throw new IOException("unexpected end of part"); 101 } else { 102 if (read >= boundaryLength) { 103 eof = true; 104 for (int i=0; i < boundaryLength; i++) { 105 if (boundary.charAt(i) != buf[count + i]) { 106 // Not the boundary! 107 eof = false; 108 break; 109 } 110 } 111 if (eof) { 112 break; 113 } 114 } 115 } 116 // success 117 count += read; 118 } 119 } 120 121 /** 122 * See the general contract of the <code>read</code> 123 * method of <code>InputStream</code>. 124 * <p> 125 * Returns <code>-1</code> (end of file) when the MIME 126 * boundary of this part is encountered. 127 * 128 * @return the next byte of data, or <code>-1</code> if the end of the 129 * stream is reached. 130 * @exception IOException if an I/O error occurs. 131 */ 132 public int read() throws IOException { 133 if (count - pos <= 2) { 134 fill(); 135 if (count - pos <= 2) { 136 return -1; 137 } 138 } 139 return buf[pos++] & 0xff; 140 } 141 142 /** 143 * See the general contract of the <code>read</code> 144 * method of <code>InputStream</code>. 145 * <p> 146 * Returns <code>-1</code> (end of file) when the MIME 147 * boundary of this part is encountered. 148 * 149 * @param b the buffer into which the data is read. 150 * @return the total number of bytes read into the buffer, or 151 * <code>-1</code> if there is no more data because the end 152 * of the stream has been reached. 153 * @exception IOException if an I/O error occurs. 154 */ 155 public int read(byte b[]) throws IOException { 156 return read(b, 0, b.length); 157 } 158 159 /** 160 * See the general contract of the <code>read</code> 161 * method of <code>InputStream</code>. 162 * <p> 163 * Returns <code>-1</code> (end of file) when the MIME 164 * boundary of this part is encountered. 165 * 166 * @param b the buffer into which the data is read. 167 * @param off the start offset of the data. 168 * @param len the maximum number of bytes read. 169 * @return the total number of bytes read into the buffer, or 170 * <code>-1</code> if there is no more data because the end 171 * of the stream has been reached. 172 * @exception IOException if an I/O error occurs. 173 */ 174 public int read(byte b[], int off, int len) throws IOException 175 { 176 int total = 0; 177 if (len == 0) { 178 return 0; 179 } 180 181 int avail = count - pos - 2; 182 if (avail <= 0) { 183 fill(); 184 avail = count - pos - 2; 185 if(avail <= 0) { 186 return -1; 187 } 188 } 189 int copy = Math.min(len, avail); 190 System.arraycopy(buf, pos, b, off, copy); 191 pos += copy; 192 total += copy; 193 194 while (total < len) { 195 fill(); 196 avail = count - pos - 2; 197 if(avail <= 0) { 198 return total; 199 } 200 copy = Math.min(len - total, avail); 201 System.arraycopy(buf, pos, b, off + total, copy); 202 pos += copy; 203 total += copy; 204 } 205 return total; 206 } 207 208 /** 209 * Returns the number of bytes that can be read from this input stream 210 * without blocking. This is a standard <code>InputStream</code> idiom 211 * to deal with buffering gracefully, and is not same as the length of the 212 * part arriving in this stream. 213 * 214 * @return the number of bytes that can be read from the input stream 215 * without blocking. 216 * @exception IOException if an I/O error occurs. 217 */ 218 public int available() throws IOException { 219 int avail = (count - pos - 2) + in.available(); 220 // Never return a negative value 221 return (avail < 0 ? 0 : avail); 222 } 223 224 /** 225 * Closes this input stream and releases any system resources 226 * associated with the stream. 227 * <p> 228 * This method will read any unread data in the MIME part so that the next 229 * part starts an an expected place in the parent <code>InputStream</code>. 230 * Note that if the client code forgets to call this method on error, 231 * <code>MultipartParser</code> will call it automatically if you call 232 * <code>readNextPart()</code>. 233 * 234 * @exception IOException if an I/O error occurs. 235 */ 236 public void close() throws IOException { 237 if (!eof) { 238 while (read(buf, 0, buf.length) != -1) 239 ; // do nothing 240 } 241 } 242}