1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
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
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 }