View Javadoc

1   /*
2    * $Id: MultipartInputStream.java 127 2004-11-06 10:15:26Z josem $
3    *
4    * Tarsis
5    * Copyright (C) 2002 Talika Open Source Group
6    *
7    * This program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with this program; if not, write to the Free Software
19   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   *
21   */
22  
23  package org.talika.tarsis.filters.upload;
24  
25  import java.io.InputStream;
26  import java.io.FilterInputStream;
27  import java.io.PushbackInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.IOException;
30  
31  /**
32   * Defines a specialiced <code>InputStream</code> to process a multipart stream.<br>
33   * <br>
34   * It provides two specialiced methods, readLine and readData.
35   *
36   * @author  Jose M. Palomar
37   * @version $Revision: 127 $
38   */
39  public final class MultipartInputStream extends FilterInputStream {
40  
41      // Constants
42      /**
43       * Default charset encoding.
44       */
45      private static final String DEFAULT_ENCODING = "iso-8859-1";
46  
47      /**
48       * Buffer size.
49       */
50      private static final int TEXT_BUFFER_SIZE = 1024;
51  
52      /**
53       * Carriage return.
54       */
55      private static final int CR = ((int) '\r');
56  
57      /**
58       * Linefeed.
59       */
60      private static final int LF = ((int) '\n');
61  
62      // Fields
63      /**
64       * End of file reached?.
65       */
66      private boolean eof;
67  
68      /**
69       * End of line reached?.
70       */
71      private boolean eol;
72  
73      /**
74       * Carriage return found?.
75       */
76      private boolean cr;
77  
78      /**
79       * Linefeed found?.
80       */
81      private boolean lf;
82  
83      /**
84       * Boundary found?.
85       */
86      private boolean boundaryFound;
87  
88      /**
89       * Boundary.
90       */
91      private byte[] boundary;
92  
93      /**
94       * Charset encoding.
95       */
96      private String encoding;
97  
98      // Constructors
99      /**
100      * Creates a new <code>MultipartInputStream</code>.
101      *
102      * @param in InputStream  the input stream from which bytes will be read.
103      * @param boundary String boundary of multipart.
104      * @param encoding String encoding of multipart.
105      * @throws IOException if an I/O error occurs.
106      */
107     public MultipartInputStream(InputStream in, String boundary, String encoding)
108     throws IOException {
109         super(new PushbackInputStream(in, boundary.length() + 3));
110 
111         if (encoding != null) {
112             this.encoding = encoding;
113         }
114         else {
115             this.encoding = DEFAULT_ENCODING;
116         }
117         this.boundary = ("--" + boundary).getBytes(encoding);
118 
119     }
120 
121     /**
122      * Reads up to <code>len</code> bytes of data from the input stream into an array
123      * of bytes. An attempt is made to read as many as <code>len</code> bytes, but a
124      * smaller number may be read, possibly zero. The number of bytes actually read
125      * is returned as an integer.
126      *
127      * @param buffer byte[] the buffer into which the data is read.
128      * @param off int the start offset in array b  at which the data is written.
129      * @param len int the maximum number of bytes to read.
130      * @return int the total number of bytes read into the buffer, or <code>-1</code>
131      * if there is no more data because the end of the stream has been reached.
132      * @throws IOException if an I/O error occurs.
133      */
134     public int read(byte[] buffer, int off, int len) throws IOException {
135 
136         if (this.eof) {
137             return -1;
138         }
139 
140         int count = 0;
141         int data = 0;
142         while ((data = read()) != -1) {
143 
144             buffer[off + count] = (byte) data;
145             count++;
146 
147             if (this.eol) {
148                 break;
149             }
150 
151             if (count >= len) {
152                 break;
153             }
154 
155         }
156 
157         if ((count == 0) && this.eof) {
158             return -1;
159         }
160 
161         if (this.cr) {
162             ((PushbackInputStream) in).unread(CR);
163             this.cr = false;
164             count--;
165         }
166 
167         return count;
168 
169     }
170 
171     /**
172      * Reads the next byte of data from the input stream. The value byte is returned
173      * as an int in the range <code>0</code> to <code>255</code>. If no byte is
174      * available because the end of the stream has been reached, the value
175      * <code>-1</code> is returned. This method blocks until input data is available,
176      * the end of the stream is detected, or an exception is thrown.
177      *
178      * @return int the next byte of data, or <code>-1</code> if the end of the stream
179      * is reached.
180      * @throws IOException if an I/O error occurs.
181      */
182     public int read() throws IOException {
183 
184         int data = in.read();
185 
186         if (data == CR) {
187             this.lf = false;
188             this.cr = true;
189             this.eol = false;
190         }
191         else if (data == LF) {
192             this.lf = true;
193             if (this.cr) {
194                 this.cr = false;
195                 this.lf = false;
196                 this.eol = true;
197             }
198             else {
199                 this.eol = false;
200             }
201         }
202         else if (data == -1) {
203             this.cr = false;
204             this.lf = false;
205             this.eol = false;
206             this.eof = true;
207         }
208         else {
209             this.cr = false;
210             this.lf = false;
211             this.eol = false;
212         }
213 
214         return data;
215 
216     }
217 
218     /**
219      * Read a line of text. A line is considered to be terminated by a carriage
220      * return ('\r') followed immediately by a linefeed ('\n').
221      *
222      * @return String A String containing the contents of the line, not including any
223      * line-termination characters, or <code>null</code> if the end of the stream has
224      * been reached.
225      * @throws IOException if an I/O error occurs.
226      */
227     public String readLine() throws IOException {
228         return readLine(this.encoding);
229     }
230 
231     /**
232      * Read a line of text using given encoding. A line is considered to be
233      * terminated by a carriage return ('\r') followed immediately by a linefeed
234      * ('\n').
235      *
236      * @param encoding String charset encoding.
237      * @return String A String containing the contents of the line, not including any
238      * line-termination characters, or <code>null</code> if the end of the stream has
239      * been reached.
240      * @throws IOException if an I/O error occurs.
241      */
242     public String readLine(String encoding) throws IOException {
243 
244         ByteArrayOutputStream baos = new ByteArrayOutputStream(TEXT_BUFFER_SIZE);
245         byte[] buffer = new byte[TEXT_BUFFER_SIZE];
246         int count = read(buffer, 0, buffer.length);
247         while (!this.eol && !this.eof) {
248             baos.write(buffer, 0, count);
249             count = read(buffer, 0, buffer.length);
250         }
251 
252         if (this.eol) {
253             count -= 2;
254         }
255 
256         if (count > 0) {
257             baos.write(buffer, 0, count);
258         }
259 
260         baos.close();
261 
262         return baos.toString(encoding);
263 
264     }
265 
266     /**
267      * Reads up to <code>len</code> bytes of data from the input stream into an array
268      * of bytes. An attempt is made to read as many as <code>len</code> bytes, but a
269      * smaller number may be read, possibly zero. The number of bytes actually read
270      * is returned as an integer.
271      *
272      * @param buffer byte[] the buffer into which the data is read.
273      * @param off int the start offset in array b  at which the data is written.
274      * @param len int the maximum number of bytes to read.
275      * @return int the total number of bytes read into the buffer, or <code>-1</code>
276      * if there is no more data because the end of the stream has been reached.
277      * @throws IOException if an I/O error occurs.
278      */
279     public int readData(byte[] buffer, int off, int len) throws IOException {
280 
281         if (this.eof) {
282             return -1;
283         }
284 
285         if (this.boundaryFound) {
286             this.boundaryFound = false;
287             return -1;
288         }
289 
290         int count = 0;
291         int data = 0;
292         while ((data = read()) != -1) {
293 
294             buffer[off + count] = (byte) data;
295             count++;
296 
297             if (this.eol) {
298                 break;
299             }
300 
301             if (count >= len) {
302                 break;
303             }
304 
305         }
306 
307         if (this.cr) {
308             ((PushbackInputStream) in).unread(CR);
309             this.cr = false;
310             count--;
311         }
312 
313         if (this.eol) {
314             byte[] boundaryBuffer = new byte[this.boundary.length];
315             int boundaryCount = read(boundaryBuffer, 0, boundaryBuffer.length);
316             if ((boundaryCount == this.boundary.length) &&
317                equalToBoundary(boundaryBuffer)) {
318                 this.boundaryFound = true;
319                 count -= 2;
320             }
321             ((PushbackInputStream) in).unread(boundaryBuffer, 0, boundaryCount);
322         }
323 
324         if ((count == 0) && (this.eof || this.boundaryFound)) {
325             if (this.boundaryFound) {
326                 this.boundaryFound = false;
327             }
328             return -1;
329         }
330 
331         return count;
332 
333     }
334 
335     /**
336      * Returns <code>true</code> if buffer is equal to boundary.
337      *
338      * @param buffer byte[] buffer to compare.
339      * @return boolean <code>true</code> if buffer is equal to boundary.
340      */
341     private boolean equalToBoundary(byte[] buffer) {
342 
343         for (int i = 0; i < this.boundary.length; i++) {
344             if (this.boundary[i] != buffer[i]) {
345                 return false;
346             }
347         }
348 
349         return true;
350 
351     }
352 
353 }