1 package http.utils.multipartrequest;
2
3 import java.util.Hashtable;
4 import java.io.BufferedOutputStream;
5 import java.io.BufferedInputStream;
6 import java.io.OutputStream;
7 import java.io.InputStream;
8 import java.io.PrintWriter;
9 import java.io.ByteArrayOutputStream;
10 import java.io.ByteArrayInputStream;
11 import java.io.FileOutputStream;
12 import java.io.UnsupportedEncodingException;
13 import java.io.IOException;
14 import java.util.Enumeration;
15 import java.util.Vector;
16 import java.io.File;
17
18 /***
19 A Multipart form data parser. Parses an input stream and writes out any files found,
20 making available a hashtable of other url parameters. As of version 1.17 the files can
21 be saved to memory, and optionally written to a database, etc.
22
23 <BR>
24 <BR>
25 Copyright (c)2001-2003 Jason Pell.
26 <BR>
27
28 <PRE>
29 This library is free software; you can redistribute it and/or
30 modify it under the terms of the GNU Lesser General Public
31 License as published by the Free Software Foundation; either
32 version 2.1 of the License, or (at your option) any later version.
33 <BR>
34 This library is distributed in the hope that it will be useful,
35 but WITHOUT ANY WARRANTY; without even the implied warranty of
36 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
37 Lesser General Public License for more details.
38 <BR>
39 You should have received a copy of the GNU Lesser General Public
40 License along with this library; if not, write to the Free Software
41 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
42 <BR>
43 Email: jasonpell@hotmail.com
44 Url: http://www.geocities.com/jasonpell
45 </PRE>
46
47 @author Jason Pell
48
49 @version 1.30rc1- Updated the getFileContent() method to return an instance of EmptyInputStream,
50 where a filename was provided in the upload process, but the file was empty.
51 - Updated trimQuotes(...) method to fix bug #693432
52 @version 1.30b4 - Minor fixes to setEncoding(String enc), thanks to Rob Gaunt & ZiP (zip_dev)
53 - Fix to readAndWriteFile(...) method to ensure the out file is always closed, even if an
54 exception occurs, thanks to David Thompson.
55 - Revise some of the changes made in 1.3-jspWiki
56 @version 1.3-jspWiki 2003-01-07, Torsten Hildebrandt (THildebrandt@gmx.de)
57 Fixes
58 - TempFile.createTempFile() caused an endless loop on Windows if filename is a
59 URL with Parameters.
60 - using fixed scheme for temporary file-names now ("multPartReq"<integer>".tmp"). Old scheme
61 not only triggered the endless loop above, but also throws an exception with short filenames
62 (like "ab.txt").
63 Enhancements
64 - Encoding can be specified for each object independently.
65 - method getRawFilename() added, that returns the filename exactly as transmitted by the browser
66
67 @version 1.30b2 A minor fix to readAndWriteFile(...) method. We need to check for whether 'out' object is null
68 before trying to call the close method.
69 @version 1.30b1 Added functionality to save uploaded files under a temporary file name. I 'borrowed' the TempFile
70 class functionality from Java 2 java.io.File to ensure that this class is still compatible with
71 JDK 1.1.x. I also modified some of the string comparisons ('Content-Type') to be case-insensitive,
72 just in case some idiot decides to write a browser which does not initcap the Content and the Type
73 words.
74 @version 1.21 A minor fix for IE4 on Mac which added extra newline after last boundary. This 'fooled' previous
75 versions of MultipartRequest into thinking another block was present.
76 @version 1.20 A very minor fix, but one apparently that was causing some serious issues for some users. The intTotalRead
77 integer was being set to -1, which meant that it was always off by one when comparing to Content-Length.
78 Thanks to David Tuma for this fix. 19/10/2001
79 @version 1.19 Moved the MultipartRequest into a package, and thus into a java archive for easier dissemination.
80 Fixed a bug, where in netscape if a file was uploaded with an unrecognised extension, no Content-Type
81 was specified, so the first line of the file was chopped off. Also modified the structure of the parse
82 method to make it easier to manage. I was checking strFileName length and nullness in two if blocks,
83 so I moved them together. This should make the parse(...) method easier to understand as well. 26/07/2001
84 @version 1.18 Fixed some serious bugs. A new method readAndWrite(InputStream in, OutputStream out) which now does
85 the generic processing in common for readAndWriteFile and readFile. The differences are that now
86 the two extra bytes at the end of a file upload are processed once, instead of after each line. Also
87 if an empty file is encountered, an outputstream is opened, but then deleted if no data written to it.
88 The getCharArray() method has been removed. Replaced by the new String(bytes, encoding) method using
89 ISO8859_1 encoding to ensure that extended characters are supported. All creation of strings is done
90 using this encoding. The addition of static methods setEncoding(String) and getEncoding() to allow the
91 use of MultipartRequest with a specific encoding type. All instances of MultipartRequest will utilise
92 the static charEncoding variable value, that the setEncoding() method can be used to set.
93 Hopefully this will not introduce any latent problems. Started to introduce support for multiple file
94 uploads with the same form field name, but not completed for v1.18. 26/06/2001
95 @version 1.17 A few _very_ minor fixes. Plus a cool new feature added. The ability to save files into memory.
96 <b>Thanks to Mark Latham for the idea and some of the code.</b> 11/04/2001
97 @version 1.16 Added support for multiple parameter values. Also fixed getCharArray(...) method to support
98 parameters with non-english ascii values (ascii above 127). Thanks to Stefan Schmidt &
99 Michael Elvers for this. (No fix yet for reported problems with Tomcat 3.2 or a single extra
100 byte appended to uploads of certain files). By 1.17 hopefully will have a resolution for the
101 second problem. 14/03/2001
102 @version 1.15 A new parameter added, intMaxReadBytes, to allow arbitrary length files. Released under
103 the LGPL (Lesser General Public License). 03/02/2001
104 @version 1.14 Fix for IE problem with filename being empty. This is because IE includes a default Content-Type
105 even when no file is uploaded. 16/02/2001
106 @version 1.13 If an upload directory is not specified, then all file contents are sent into oblivion, but the
107 rest of the parsing works as normal.
108 @version 1.12 Fix, was allowing zero length files. Will not even create the output file until there is
109 something to write. getFile(String) now returns null, if a zero length file was specified. 06/11/2000
110 @version 1.11 Fix, in case Content-type is not specified.
111 @version 1.1 Removed dependence on Servlets. Now passes in a generic InputStream instead.
112 "Borrowed" readLine from Tomcat 3.1 ServletInputStream class,
113 so we can remove some of the dependencies on ServletInputStream.
114 Fixed bug where a empty INPUT TYPE="FILE" value, would cause an exception.
115 @version 1.0 Initial Release.
116 */
117
118 public class MultipartRequest
119 {
120 /***
121 Define Character Encoding method here.
122 */
123 private String charEncoding;
124 public static final String DEF_ENCODING = "ISO-8859-1";
125
126
127 private PrintWriter debug = null;
128
129 private Hashtable htParameters = null;
130 private Hashtable htFiles = null;
131
132 private String strBoundary = null;
133
134
135 private File fileOutPutDirectory = null;
136 private boolean loadIntoMemory = false;
137
138 private long intMaxContentLength;
139 private long intContentLength;
140 private int maxBytesExceededMode;
141
142 /***
143 * This stores the actual total read including all multipart boundaries, etc.
144 */
145 private long intTotalRead;
146
147 /***
148 Prevent a denial of service by defining this, will never read more data.
149 If Content-Length is specified to be more than this, will throw an exception.
150
151 This limits the maximum number of bytes to the value of an int, which is 2 Gigabytes.
152 */
153 public static final int MAX_READ_BYTES = 2 * (1024 * 1024);
154
155 /***
156 Defines the number of bytes to read per readLine call. 128K
157 */
158 public static final int READ_LINE_BLOCK = 1024 * 128;
159
160 /***
161 Store a read from the input stream here. Global so we do not keep creating new arrays each read.
162 */
163 private byte[] blockOfBytes = null;
164
165 /***
166 Define the array indexes for the htFiles Object array.
167 */
168 public static final int FILENAME = 0;
169 public static final int CONTENT_TYPE = 1;
170 public static final int SIZE = 2;
171
172
173 public static final int CONTENTS = 3;
174
175
176 public static final int TMP_FILENAME = 4;
177
178
179 public static final int RAW_FILENAME = 5;
180
181 /***
182 * Mode
183 */
184 public static final int ABORT_IF_MAX_BYES_EXCEEDED = 100;
185 public static final int IGNORE_FILES_IF_MAX_BYES_EXCEEDED = 101;
186
187 /***
188 Sets up the encoding for this instance of multipartrequest. You can set the encoding
189 to null, in which case the default encoding will be applied. The default encoding if
190 this method is not called has been set to ISO-8859-1, which seems to offer the best hope
191 of support for international characters, such as german "Umlaut" characters.
192 */
193 public synchronized void setEncoding(String enc)throws UnsupportedEncodingException
194 {
195 if (enc==null || enc.trim()=="")
196 charEncoding = System.getProperty("file.encoding");
197 else
198 {
199
200 new String(new byte[]{(byte)'\n'}, enc);
201
202 charEncoding = enc;
203 }
204 }
205
206 /***
207 Returns the current encoding method.
208 */
209 public String getEncoding()
210 {
211 return charEncoding;
212 }
213
214 /***
215 * Standard Constructor
216 *
217 * @param strContentTypeText The "Content-Type" HTTP header value.
218 * @param intContentLength The "Content-Length" HTTP header value.
219 * @param in The InputStream to read and parse.
220 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
221 *
222 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
223 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
224 *
225 * @see #MAX_READ_BYTES
226 * @deprecated Replaced by MultipartRequest(PrintWriter, String, int, InputStream, String, int, boolean, String)
227 */
228 public MultipartRequest(String strContentTypeText,
229 int intContentLength,
230 InputStream in,
231 String strSaveDirectory) throws IllegalArgumentException, IOException
232 {
233 initParser(null,
234 strContentTypeText,
235 intContentLength,
236 in,
237 false,
238 strSaveDirectory,
239 MultipartRequest.MAX_READ_BYTES,
240 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
241 null);
242 }
243
244 /***
245 * Standard Constructor
246 *
247 * @param strContentTypeText The "Content-Type" HTTP header value.
248 * @param intContentLength The "Content-Length" HTTP header value.
249 * @param in The InputStream to read and parse.
250 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
251 * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
252 * will be silently ignored.</B>
253 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
254 *
255 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
256 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
257 *
258 * @see #MAX_READ_BYTES
259 * @deprecated Replaced by MultipartRequest(PrintWriter, String, int, InputStream, String, int, boolean, String)
260 */
261 public MultipartRequest(String strContentTypeText,
262 int intContentLength,
263 InputStream in,
264 String strSaveDirectory,
265 int intMaxReadBytes) throws IllegalArgumentException, IOException
266 {
267 initParser(null,
268 strContentTypeText,
269 intContentLength,
270 in,
271 false,
272 strSaveDirectory,
273 intMaxReadBytes,
274 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
275 null);
276 }
277
278 /***
279 * Standard Constructor
280 *
281 * @param debug A PrintWriter that can be used for debugging.
282 * @param strContentTypeText The "Content-Type" HTTP header value.
283 * @param intContentLength The "Content-Length" HTTP header value.
284 * @param in The InputStream to read and parse.
285 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
286 * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
287 * will be silently ignored.</B>
288 *
289 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
290 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
291 *
292 * @see #MAX_READ_BYTES
293 * @deprecated Replaced by MultipartRequest(PrintWriter, String, int, InputStream, String, int, boolean, String)
294 */
295 public MultipartRequest(PrintWriter debug,
296 String strContentTypeText,
297 int intContentLength,
298 InputStream in,
299 String strSaveDirectory) throws IllegalArgumentException, IOException
300 {
301 initParser(debug,
302 strContentTypeText,
303 intContentLength,
304 in,
305 false,
306 strSaveDirectory,
307 MultipartRequest.MAX_READ_BYTES,
308 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
309 null);
310 }
311
312 /***
313 * Memory Constructor
314 *
315 * @param debug A PrintWriter that can be used for debugging.
316 * @param strContentTypeText The "Content-Type" HTTP header value.
317 * @param intContentLength The "Content-Length" HTTP header value.
318 * @param in The InputStream to read and parse.
319 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
320 *
321 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
322 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
323 *
324 * @see #MAX_READ_BYTES
325 * @deprecated Replaced by MultipartRequest(PrintWriter, String, int, InputStream, int, boolean, String)
326 */
327 public MultipartRequest(PrintWriter debug,
328 String strContentTypeText,
329 int intContentLength,
330 InputStream in,
331 int intMaxReadBytes) throws IllegalArgumentException, IOException
332 {
333 initParser(debug,
334 strContentTypeText,
335 intContentLength,
336 in,
337 true,
338 null,
339 intMaxReadBytes,
340 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
341 null);
342 }
343
344 /***
345 * Standard Constructor
346 *
347 * @param debug A PrintWriter that can be used for debugging.
348 * @param strContentTypeText The "Content-Type" HTTP header value.
349 * @param intContentLength The "Content-Length" HTTP header value.
350 * @param in The InputStream to read and parse.
351 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
352 * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
353 * will be silently ignored.</B>
354 *
355 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
356 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
357 *
358 * @see #MAX_READ_BYTES
359 * @deprecated Replaced by MultipartRequest(PrintWriter, String, int, InputStream, String, int, boolean, String)
360 */
361 public MultipartRequest(PrintWriter debug,
362 String strContentTypeText,
363 int intContentLength,
364 InputStream in,
365 String strSaveDirectory,
366 int intMaxReadBytes) throws IllegalArgumentException, IOException, UnsupportedEncodingException
367 {
368 initParser(debug,
369 strContentTypeText,
370 intContentLength,
371 in,
372 false,
373 strSaveDirectory,
374 intMaxReadBytes,
375 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
376 null);
377 }
378
379 /***
380 * Standard Constructor
381 *
382 * @param debug A PrintWriter that can be used for debugging.
383 * @param strContentTypeText The "Content-Type" HTTP header value.
384 * @param intContentLength The "Content-Length" HTTP header value.
385 * @param in The InputStream to read and parse.
386 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
387 * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
388 * will be silently ignored.</B>
389 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
390 * @param encoding Sets the encoding to use. If null, ISO-8859-1 will be used.
391 *
392 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
393 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
394 * @exception UnsupportedEncodingException If the encoding is invalid.
395 *
396 * @see #MAX_READ_BYTES
397 */
398 public MultipartRequest(PrintWriter debug,
399 String strContentTypeText,
400 int intContentLength,
401 InputStream in,
402 String strSaveDirectory,
403 int intMaxReadBytes,
404 String encoding) throws IllegalArgumentException, IOException, UnsupportedEncodingException
405 {
406 initParser(debug,
407 strContentTypeText,
408 intContentLength,
409 in,
410 false,
411 strSaveDirectory,
412 intMaxReadBytes,
413 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
414 encoding);
415 }
416
417 /***
418 * Memory Constructor
419 *
420 * @param debug A PrintWriter that can be used for debugging.
421 * @param strContentTypeText The "Content-Type" HTTP header value.
422 * @param intContentLength The "Content-Length" HTTP header value.
423 * @param in The InputStream to read and parse.
424 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
425 * @param encoding Sets the encoding to use. If null, ISO-8859-1 will be used.
426 *
427 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
428 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
429 * @exception UnsupportedEncodingException If the encoding is invalid.
430 *
431 * @see #MAX_READ_BYTES
432 */
433 public MultipartRequest(PrintWriter debug,
434 String strContentTypeText,
435 int intContentLength,
436 InputStream in,
437 int intMaxReadBytes,
438 String encoding) throws IllegalArgumentException, IOException, UnsupportedEncodingException
439 {
440 initParser(debug,
441 strContentTypeText,
442 intContentLength,
443 in,
444 true,
445 null,
446 intMaxReadBytes,
447 MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
448 encoding);
449 }
450
451 /***
452 * Standard Constructor
453 *
454 * @param debug A PrintWriter that can be used for debugging.
455 * @param strContentTypeText The "Content-Type" HTTP header value.
456 * @param intContentLength The "Content-Length" HTTP header value.
457 * @param in The InputStream to read and parse.
458 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
459 * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
460 * will be silently ignored.</B>
461 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
462 * @param maxBytesExceededMode This controls how the parser will process a request which is in excess of the intMaxReadBytes
463 * parameter. The possible modes are:
464 * <ul>
465 * <li>MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED - The parser will throw a MaxBytesReadException</li>
466 * <li>MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED - All parameters will be processed, but any file
467 * content will be discarded. <b><u>Be warned that there is still potential for Denial-of-Service, if an attacker decided to send
468 * megabytes of non-file form data.</u></b></li>
469 * <ul>
470 * @param encoding Sets the encoding to use. If null, ISO-8859-1 will be used.
471 *
472 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
473 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
474 * @exception UnsupportedEncodingException If the encoding is invalid.
475 *
476 * @see #MAX_READ_BYTES
477 */
478 public MultipartRequest(PrintWriter debug,
479 String strContentTypeText,
480 int intContentLength,
481 InputStream in,
482 String strSaveDirectory,
483 int intMaxReadBytes,
484 int maxBytesExceededMode,
485 String encoding) throws IllegalArgumentException, IOException, UnsupportedEncodingException
486 {
487 initParser(debug,
488 strContentTypeText,
489 intContentLength,
490 in,
491 false,
492 strSaveDirectory,
493 intMaxReadBytes,
494 maxBytesExceededMode,
495 encoding);
496 }
497
498 /***
499 * Memory Constructor
500 *
501 * @param debug A PrintWriter that can be used for debugging.
502 * @param strContentTypeText The "Content-Type" HTTP header value.
503 * @param intContentLength The "Content-Length" HTTP header value.
504 * @param in The InputStream to read and parse.
505 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
506 * @param maxBytesExceededMode This controls how the parser will process a request which is in excess of the intMaxReadBytes
507 * parameter. The possible modes are:
508 * <ul>
509 * <li>MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED - The parser will throw a MaxBytesReadException</li>
510 * <li>MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED - All parameters will be processed, but any file
511 * content will be discarded. <b><u>WARNING: There is still potential for a Denial-of-Service. For instance, an attacker can send
512 * many megabytes of non-file form data.</u></b></li>
513 * <ul>
514 * @param encoding Sets the encoding to use. If null, ISO-8859-1 will be used.
515 *
516 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
517 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
518 * @exception UnsupportedEncodingException If the encoding is invalid.
519 *
520 * @see #MAX_READ_BYTES
521 */
522 public MultipartRequest(PrintWriter debug,
523 String strContentTypeText,
524 int intContentLength,
525 InputStream in,
526 int intMaxReadBytes,
527 int maxBytesExceededMode,
528 String encoding) throws IllegalArgumentException, IOException, UnsupportedEncodingException
529 {
530 initParser(debug,
531 strContentTypeText,
532 intContentLength,
533 in,
534 true,
535 null,
536 intMaxReadBytes,
537 maxBytesExceededMode,
538 encoding);
539 }
540
541 /***
542 * Initialise the parser.
543 *
544 * @param debug A PrintWriter that can be used for debugging.
545 * @param strContentTypeText The "Content-Type" HTTP header value.
546 * @param intContentLength The "Content-Length" HTTP header value.
547 * @param in The InputStream to read and parse.
548 * @param loadIntoMemory Is this parser loading its output into memory only.
549 * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
550 * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
551 * will be silently ignored.</B>
552 * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
553 * @param maxBytesExceededMode This controls how the parser will process a request which is in excess of the intMaxReadBytes
554 * parameter. The possible modes are:
555 * <ul>
556 * <li>MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED - The parser will throw a MaxBytesReadException</li>
557 * <li>MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED - All parameters will be processed, but any file
558 * content will be discarded. <b><u>WARNING: There is still potential for a Denial-of-Service. For instance, an attacker can send
559 * many megabytes of non-file form data.</u></b></li>
560 * <ul>
561 * @param encoding Sets the encoding to use. If null, ISO-8859-1 will be used.
562 *
563 * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
564 * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
565 * @exception UnsupportedEncodingException If the encoding is invalid.
566 *
567 * @see #MAX_READ_BYTES
568 */
569 private void initParser(PrintWriter debug,
570 String strContentTypeText,
571 int intContentLength,
572 InputStream in,
573 boolean loadIntoMemory,
574 String strSaveDirectory,
575 int intMaxReadBytes,
576 int maxBytesExceededMode,
577 String encoding) throws IllegalArgumentException, IOException, UnsupportedEncodingException
578 {
579
580 this.debug = debug;
581
582
583 this.loadIntoMemory = loadIntoMemory;
584
585
586 if (!loadIntoMemory && strSaveDirectory!=null)
587 {
588 this.fileOutPutDirectory = new File(strSaveDirectory);
589 if (!fileOutPutDirectory.exists())
590 throw new IOException("Directory ["+strSaveDirectory+"] is invalid.");
591 else if (!fileOutPutDirectory.canWrite())
592 throw new IOException("Directory ["+strSaveDirectory+"] is readonly.");
593 }
594
595
596 setEncoding( (encoding!=null) ? encoding : DEF_ENCODING );
597
598 if (strContentTypeText!=null && strContentTypeText.startsWith("multipart/form-data") && strContentTypeText.indexOf("boundary=")!=-1)
599 {
600 strBoundary = strContentTypeText.substring(strContentTypeText.indexOf("boundary=")+"boundary=".length()).trim();
601 }
602 else
603 {
604
605 debug("ContentType = " + strContentTypeText);
606 throw new IllegalArgumentException("Invalid Content Type.");
607 }
608
609 this.intContentLength = intContentLength;
610 this.intMaxContentLength = intMaxReadBytes;
611 this.maxBytesExceededMode = maxBytesExceededMode;
612
613 if(maxBytesExceededMode == MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED && this.intContentLength > this.intMaxContentLength)
614 {
615 debug("ContentLength = " + this.intContentLength);
616 debug("MaxReadBytes = " + this.intMaxContentLength);
617
618 throw new MaxReadBytesException(this.intContentLength, this.intMaxContentLength);
619 }
620
621
622 htParameters = new Hashtable();
623 htFiles = new Hashtable();
624 blockOfBytes = new byte[READ_LINE_BLOCK];
625
626
627
628 intTotalRead=0;
629
630
631 parse(new BufferedInputStream(in));
632
633
634 this.blockOfBytes=null;
635 this.debug = null;
636 this.strBoundary=null;
637 }
638
639 /***
640 * If this class was constructed with a maxBytesExceeded mode of
641 * MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED, this method
642 * will indicate whether the process is ignoring file content because
643 * the content-length was exceeded.
644 */
645 public boolean isMaxBytesExceeded()
646 {
647 return (this.intContentLength > this.intMaxContentLength);
648 }
649
650 /***
651 Return the value of the strName URLParameter.
652 If more than one value for a particular Parameter, will return the first.
653 If an error occurs will return null.
654 */
655 public String getURLParameter(String strName)
656 {
657 Object value = htParameters.get(strName);
658 if (value instanceof Vector)
659 return (String) ((Vector)value).firstElement();
660 else
661 return (String) htParameters.get(strName);
662 }
663
664 /***
665 Return an enumeration of all values for the strName parameter.
666 Even if a single value for, will always return an enumeration, although
667 it may actually be empty if not value was encountered for strName or
668 it is an invalid parameter name.
669 */
670 public Enumeration getURLParameters(String strName)
671 {
672 Object value = htParameters.get(strName);
673 if (value instanceof Vector)
674 return ((Vector)value).elements();
675 else
676 {
677 Vector vector = new Vector();
678 if(value!=null)
679 vector.addElement(value);
680 return vector.elements();
681 }
682 }
683
684 /***
685 An enumeration of all URL Parameters for the current HTTP Request.
686 */
687 public Enumeration getParameterNames()
688 {
689 return htParameters.keys();
690 }
691
692 /***
693 This enumeration will return all INPUT TYPE=FILE parameter NAMES as encountered
694 during the upload.
695 */
696 public Enumeration getFileParameterNames()
697 {
698 return htFiles.keys();
699 }
700
701 /***
702 Returns the Content-Type of a file.
703
704 @see #getFileParameter(java.lang.String, int)
705 */
706 public String getContentType(String strName)
707 {
708
709 return (String)getFileParameter(strName, CONTENT_TYPE);
710 }
711
712 /***
713 If files were uploaded into memory, this method will retrieve the contents
714 of the file as a InputStream.
715
716 @return the contents of the file as a InputStream, or null if not file uploaded,
717 or file uploaded to file system directory.
718
719 @see #getFileParameter(java.lang.String, int)
720 */
721 public InputStream getFileContents(String strName)
722 {
723 if(this.maxBytesExceededMode == MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED &&
724 this.intContentLength >= this.intMaxContentLength)
725 {
726 return null;
727 }
728 else
729 {
730 Object obj = getFileParameter(strName, CONTENTS);
731 if (obj!=null)
732 return new ByteArrayInputStream((byte[])obj);
733 else if(getBaseFilename(strName)!=null)
734 return new EmptyInputStream();
735 else
736 return null;
737 }
738 }
739
740 /***
741 Returns a File reference to the uploaded file. This reference is to the files uploaded location,
742 and allows you to read/move/delete the file.
743
744 This method is only of use, if files were uploaded to the file system. Will return null if
745 uploaded to memory, in which case you should use getFileContents(strName) instead.
746
747 @return Returns a null file reference if a call to getFileSize(strName) returns zero or files were
748 uploaded to memory.
749
750 @see #getFileSize(java.lang.String)
751 @see #getFileContents(java.lang.String)
752 */
753 public File getFile(String strName)
754 {
755 if(this.maxBytesExceededMode == MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED &&
756 this.intContentLength >= this.intMaxContentLength)
757 {
758 return null;
759 }
760 else
761 {
762
763 if(getFileSize(strName)>0 && fileOutPutDirectory!=null)
764 return (File)getFileParameter(strName, TMP_FILENAME);
765 else
766 return null;
767 }
768 }
769
770 /***
771 @deprecated Replaced by getBaseFilename(String)
772 */
773 public String getFileSystemName(String strName)
774 {
775 return getBaseFilename(strName);
776 }
777
778 /***
779 Get the uploaded file basename. This is the basename of the file which
780 was provided by the browser itself when the file was chosen using the
781 'Browse...' button of the <input type=file ...> input field.
782
783 @return null if strName not found.
784
785 @see #getFileParameter(java.lang.String, int)
786 */
787 public String getBaseFilename(String strName)
788 {
789 return (String)getFileParameter(strName, FILENAME);
790 }
791
792 /***
793 Get the uploaded file basename. This is the file which
794 was provided by the browser itself when the file was chosen using the
795 'Browse...' button of the <input type=file ...> input field.
796
797 @return null if strName not found.
798
799 @see #getFileParameter(java.lang.String, int)
800 */
801 public String getRawFilename(String strName)
802 {
803 return (String)getFileParameter(strName, RAW_FILENAME);
804 }
805
806 /***
807 Returns the File Size of a uploaded file.
808
809 @return -1 if file size not defined.
810
811 @see #getFileParameter(java.lang.String, int)
812 */
813 public long getFileSize(String strName)
814 {
815 if(this.maxBytesExceededMode == MultipartRequest.IGNORE_FILES_IF_MAX_BYES_EXCEEDED &&
816 this.intContentLength >= this.intMaxContentLength)
817 {
818 return (long)-1;
819 }
820 else
821 {
822 Object obj = getFileParameter(strName, SIZE);
823 if (obj!=null)
824 return ((Long)obj).longValue();
825 else
826 return (long)-1;
827 }
828 }
829
830 /***
831 Access an attribute of a file upload parameter record.
832
833 @param strName is the form field name, used to upload the file. This identifies
834 the formfield location in the storage facility.
835
836 @param strFilename This is the FileSystemName of the file
837 @param type What attribute you want from the File Parameter.
838 <pre>
839 The following types are supported:
840 MultipartRequest.FILENAME,
841 MultipartRequest.CONTENT_TYPE,
842 MultipartRequest.SIZE,
843 MultipartRequest.CONTENTS,
844 MultipartRequest.TMP_FILENAME
845 MultipartRequest.RAW_FILENAME
846 </pre>
847
848 <p>The getBaseFilename(),getFile(),getFileSize(),getContentType(),getContents() methods
849 all use this method passing in a different type argument.</p>
850
851 <p><b>Note: </b>This class has been changed to provide for future functionality where you
852 will be able to access all files uploaded, even if they are uploaded using the same
853 form field name. At this point however, only the first file uploaded via a form
854 field name is accessible.</p>
855
856 @see #getContentType(java.lang.String)
857 @see #getFileSize(java.lang.String)
858 @see #getFileContents(java.lang.String)
859 @see #getFile(java.lang.String)
860 @see #getBaseFilename(java.lang.String)
861 */
862 public Object getFileParameter(String strName, int type)
863 {
864 Object[] objArray = null;
865 Object value = htFiles.get(strName);
866 if (value instanceof Vector)
867 objArray = (Object[]) ((Vector)value).firstElement();
868 else
869 objArray = (Object[]) htFiles.get(strName);
870
871
872 if (objArray!=null && type>=FILENAME && type<=RAW_FILENAME)
873 return objArray[type];
874 else
875 return null;
876 }
877
878 /***
879 This is the main parse method.
880 */
881 private void parse(InputStream in) throws IOException
882 {
883 String strContentType = null;
884 String strName = null;
885 String strFilename = null;
886 String strRawFilename = null;
887 String strLine = null;
888 int read = -1;
889
890
891 read = readLine(in, blockOfBytes);
892 strLine = read>0? new String(blockOfBytes, 0, read, charEncoding): null;
893
894
895 if (strLine==null || strLine.indexOf(this.strBoundary)==-1)
896 {
897 throw new IOException("Invalid Form Data, no boundary encountered.");
898 }
899
900
901 while (true)
902 {
903
904 read = readLine(in, blockOfBytes);
905 if (read<=0)
906 break;
907 else
908 {
909 strLine = new String(blockOfBytes, 0, read, charEncoding);
910
911
912 if(strLine==null || strLine.length() == 0 || strLine.trim().length() == 0)
913 break;
914
915
916 strName = trimQuotes(getValue("name", strLine));
917
918
919 strFilename = trimQuotes(getValue("filename", strLine));
920
921
922 if (strFilename==null)
923 {
924
925 readLine(in, blockOfBytes);
926
927 String param = readParameter(in);
928 addParameter(strName, param);
929 }
930 else
931 {
932
933 if (strFilename.length()==0)
934 {
935
936 read = readLine(in, blockOfBytes);
937 strLine = read>0? new String(blockOfBytes, 0, read, charEncoding): null;
938
939
940 if (strLine!=null && strLine.toLowerCase().startsWith("content-type:"))
941 readLine(in, blockOfBytes);
942
943
944 readLine(in, blockOfBytes);
945
946
947 addFileParameter(strName, new Object[] {null, null, null, null, null, null});
948
949 readLine(in, blockOfBytes);
950 }
951 else
952 {
953
954 read = readLine(in, blockOfBytes);
955 strLine = read>0? new String(blockOfBytes, 0, read, charEncoding): null;
956
957 strContentType = "application/octet-stream";
958
959
960 if (strLine!=null && strLine.toLowerCase().startsWith("content-type:"))
961 {
962 strContentType = strLine.substring("content-type:".length()).trim();
963
964
965 readLine(in, blockOfBytes);
966 }
967
968 long filesize = -1;
969
970
971 byte[] contentsOfFile = null;
972
973
974 File outFile = null;
975
976
977 strRawFilename = strFilename;
978 strFilename = getBasename(strFilename);
979
980
981 if (loadIntoMemory)
982 {
983 contentsOfFile = readFile(in);
984 if (contentsOfFile!=null)
985 filesize = contentsOfFile.length;
986 }
987 else
988 {
989
990
991
992 if(fileOutPutDirectory != null)
993 {
994 outFile = TempFile.createTempFile("multPartReq", null, fileOutPutDirectory);
995 }
996 filesize = readAndWriteFile(in, outFile);
997 }
998
999
1000 if (filesize>0)
1001 addFileParameter(strName, new Object[] {strFilename, strContentType, new Long(filesize), contentsOfFile, outFile, strRawFilename});
1002 else
1003 addFileParameter(strName, new Object[] {strFilename, strContentType, new Long(0), null, null, strRawFilename});
1004 }
1005 }
1006 }
1007 }
1008 }
1009
1010 /***
1011 So we can put the logic for supporting multiple parameters with the same
1012 form field name in the one location.
1013 */
1014 private void addParameter(String strName, String value)
1015 {
1016
1017 Object objParms = htParameters.get(strName);
1018
1019
1020 if (objParms instanceof Vector)
1021 {
1022 ((Vector)objParms).addElement(value);
1023 }
1024 else if (objParms instanceof String)
1025 {
1026 Vector vecParms = new Vector();
1027 vecParms.addElement(objParms);
1028 vecParms.addElement(value);
1029
1030 htParameters.put(strName, vecParms);
1031 }
1032 else
1033 {
1034 htParameters.put(strName, value);
1035 }
1036 }
1037
1038 /***
1039 So we can put the logic for supporting multiple files with the same
1040 form field name in the one location.
1041
1042 Assumes that this method will never be called with a null fileObj or strFilename.
1043 */
1044 private void addFileParameter(String strName, Object[] fileObj)
1045 {
1046 Object objParms = htFiles.get(strName);
1047
1048
1049 if (objParms instanceof Vector)
1050 ((Vector)objParms).addElement(fileObj);
1051 else if (objParms instanceof Object[])
1052 {
1053 Vector vecParms = new Vector();
1054 vecParms.addElement(objParms);
1055 vecParms.addElement(fileObj);
1056
1057 htFiles.put(strName, vecParms);
1058 }
1059 else
1060 htFiles.put(strName, fileObj);
1061 }
1062
1063 /***
1064 Read parameters, assume already passed Content-Disposition and blank line.
1065
1066 @return the value read in.
1067 */
1068 private String readParameter(InputStream in) throws IOException
1069 {
1070 StringBuffer buf = new StringBuffer();
1071 int read=-1;
1072
1073 String line = null;
1074 while(true)
1075 {
1076 read = readLine(in, blockOfBytes);
1077 if (read<0)
1078 {
1079 throw new IOException("Stream ended prematurely.");
1080 }
1081
1082
1083 line = new String(blockOfBytes, 0, read, charEncoding);
1084 if (read<blockOfBytes.length && line.indexOf(this.strBoundary)!=-1)
1085 break;
1086 else
1087 buf.append(line);
1088 }
1089
1090 if (buf.length()>0)
1091 buf.setLength(getLengthMinusEnding(buf));
1092 return buf.toString();
1093 }
1094
1095 /***
1096 Read from in, write to out, minus last two line ending bytes.
1097 */
1098 private long readAndWrite(InputStream in, OutputStream out) throws IOException
1099 {
1100 long fileSize = 0;
1101 int read = -1;
1102
1103
1104 byte[] secondLineOfBytes = new byte[blockOfBytes.length];
1105
1106 int sizeOfSecondArray = 0;
1107
1108 while(true)
1109 {
1110 read = readLine(in, blockOfBytes);
1111 if (read<0)
1112 throw new IOException("Stream ended prematurely.");
1113
1114
1115 if (read<blockOfBytes.length && new String(blockOfBytes, 0, read, charEncoding).indexOf(this.strBoundary)!=-1)
1116 {
1117
1118
1119 if(sizeOfSecondArray!=0)
1120 {
1121
1122 int actualLength = getLengthMinusEnding(secondLineOfBytes, sizeOfSecondArray);
1123 if (actualLength>0 && out!=null)
1124 {
1125 out.write(secondLineOfBytes, 0, actualLength);
1126
1127 fileSize+=actualLength;
1128 }
1129 }
1130 break;
1131 }
1132 else
1133 {
1134
1135
1136 if(sizeOfSecondArray!=0)
1137 {
1138 out.write(secondLineOfBytes, 0, sizeOfSecondArray);
1139
1140 fileSize+=sizeOfSecondArray;
1141 }
1142
1143
1144 if(out!=null)
1145 {
1146
1147 System.arraycopy(blockOfBytes,0,secondLineOfBytes,0,read);
1148
1149 sizeOfSecondArray=read;
1150 }
1151 }
1152 }
1153
1154
1155 return fileSize;
1156 }
1157
1158 /***
1159 Read a Multipart section that is a file type. Assumes that the Content-Disposition/Content-Type and blank line
1160 have already been processed. So we read until we hit a boundary, then close file and return.
1161
1162 @exception IOException if an error occurs writing the file.
1163
1164 @return the number of bytes read.
1165 */
1166 private long readAndWriteFile(InputStream in, File outFile) throws IOException
1167 {
1168 BufferedOutputStream out = null;
1169
1170 try
1171 {
1172
1173
1174 if(this.intContentLength <= this.intMaxContentLength)
1175 {
1176
1177
1178 if(outFile!=null && outFile.exists() && outFile.canWrite())
1179 {
1180 out = new BufferedOutputStream(new FileOutputStream(outFile));
1181 }
1182 }
1183
1184 long count = readAndWrite(in, out);
1185
1186 if (count==0)
1187 {
1188
1189 if (outFile != null)
1190 outFile.delete();
1191 }
1192
1193 return count;
1194 }
1195 finally
1196 {
1197 if(out!=null)
1198 {
1199 out.flush();
1200 out.close();
1201 }
1202 }
1203 }
1204
1205 /***
1206 * If the fileOutPutDirectory wasn't specified, just read the file to memory.
1207 *
1208 * @param strName - Url parameter this file was loaded under.
1209 * @return contents of file, from which you can garner the size as well.
1210 */
1211 private byte[] readFile(InputStream in) throws IOException
1212 {
1213 ByteArrayOutputStream out = null;
1214
1215
1216
1217 if(this.intContentLength <= this.intMaxContentLength)
1218 {
1219
1220 out = new ByteArrayOutputStream();
1221 }
1222
1223 long count = readAndWrite(in, out);
1224
1225 if (count>0)
1226 {
1227
1228 if(out!=null)
1229 return out.toByteArray();
1230 else
1231 return null;
1232 }
1233 else
1234 return null;
1235 }
1236
1237 /***
1238 Reads at most READ_BLOCK blocks of data, or a single line whichever is smaller.
1239 Returns -1, if nothing to read, or we have reached the specified content-length.
1240
1241 Assumes that bytToBeRead.length indicates the block size to read.
1242
1243 @return -1 if stream has ended, before a newline encountered (should never happen) OR
1244 we have read past the Content-Length specified. (Should also not happen). Otherwise
1245 return the number of characters read. You can test whether the number returned is less
1246 than bytesToBeRead.length, which indicates that we have read the last line of a file or parameter or
1247 a border line, or some other formatting stuff.
1248 */
1249 private int readLine(InputStream in, byte[] bytesToBeRead) throws IOException
1250 {
1251
1252 if(this.intTotalRead >= this.intContentLength)
1253 return -1;
1254
1255
1256 int length = bytesToBeRead.length;
1257
1258
1259
1260 if (length > (this.intContentLength - this.intTotalRead))
1261 {
1262 length = (int) (this.intContentLength - this.intTotalRead);
1263 }
1264
1265 int result = readLine(in, bytesToBeRead, 0, length);
1266
1267 if (result > 0)
1268 {
1269 this.intTotalRead += result;
1270 }
1271
1272 return result;
1273 }
1274
1275 /***
1276 Returns the length of the line minus line ending.
1277
1278 @param endOfArray This is because in many cases the byteLine will have garbage data at the end, so we
1279 act as though the actual end of the array is this parameter. If you want to process
1280 the complete byteLine, specify byteLine.length as the endOfArray parameter.
1281 */
1282 private static final int getLengthMinusEnding(byte byteLine[], int endOfArray)
1283 {
1284 if (byteLine==null)
1285 return 0;
1286
1287 if (endOfArray>=2 && byteLine[endOfArray-2] == '\r' && byteLine[endOfArray-1] == '\n')
1288 return endOfArray-2;
1289 else if (endOfArray>=1 && byteLine[endOfArray-1] == '\n' || byteLine[endOfArray-1] == '\r')
1290 return endOfArray-1;
1291 else
1292 return endOfArray;
1293 }
1294
1295 private static final int getLengthMinusEnding(StringBuffer buf)
1296 {
1297 if (buf.length()>=2 && buf.charAt(buf.length()-2) == '\r' && buf.charAt(buf.length()-1) == '\n')
1298 return buf.length()-2;
1299 else if (buf.length()>=1 && buf.charAt(buf.length()-1) == '\n' || buf.charAt(buf.length()-1) == '\r')
1300 return buf.length()-1;
1301 else
1302 return buf.length();
1303 }
1304
1305 /***
1306 This needs to support the possibility of a / or a \ separator.
1307
1308 Returns strFilename after removing all characters before the last
1309 occurence of / or \.
1310 */
1311 private static final String getBasename(String strFilename)
1312 {
1313 if (strFilename==null)
1314 return strFilename;
1315
1316 int intIndex = strFilename.lastIndexOf("/");
1317 if (intIndex==-1 || strFilename.lastIndexOf("//")>intIndex)
1318 intIndex = strFilename.lastIndexOf("//");
1319
1320 if (intIndex!=-1)
1321 return strFilename.substring(intIndex+1);
1322 else
1323 return strFilename;
1324 }
1325
1326 /***
1327 trimQuotes trims any quotes from the start and end of a string and returns the trimmed string...
1328 */
1329 private static final String trimQuotes (String strItem)
1330 {
1331
1332 if (strItem==null || strItem.indexOf("\"")==-1)
1333 return strItem;
1334
1335
1336 strItem = strItem.trim();
1337
1338 if (strItem.length()>0 && strItem.charAt(0) == '\"')
1339 strItem = strItem.substring(1);
1340
1341 if (strItem.length()>0 && strItem.charAt(strItem.length()-1) == '\"')
1342 strItem = strItem.substring(0, strItem.length()-1);
1343
1344 return strItem;
1345 }
1346
1347 /***
1348 Format of string name=value; name=value;
1349
1350 If not found, will return null.
1351 */
1352 private static final String getValue(String strName, String strToDecode)
1353 {
1354 strName = strName + "=";
1355
1356 int startIndexOf=0;
1357 while (startIndexOf<strToDecode.length())
1358 {
1359 int indexOf = strToDecode.indexOf(strName, startIndexOf);
1360
1361 if (indexOf!=-1)
1362 {
1363 if (indexOf==0 || Character.isWhitespace(strToDecode.charAt(indexOf-1)) || strToDecode.charAt(indexOf-1)==';')
1364 {
1365 int endIndexOf = strToDecode.indexOf(";", indexOf+strName.length());
1366 if (endIndexOf==-1)
1367 return strToDecode.substring(indexOf+strName.length());
1368 else
1369 return strToDecode.substring(indexOf+strName.length(), endIndexOf);
1370 }
1371 else
1372 startIndexOf=indexOf+strName.length();
1373 }
1374 else
1375 return null;
1376 }
1377 return null;
1378 }
1379
1380 /***
1381 * <I>Tomcat's ServletInputStream.readLine(byte[],int,int) Slightly Modified to utilise in.read()</I>
1382 * <BR>
1383 * Reads the input stream, one line at a time. Starting at an
1384 * offset, reads bytes into an array, until it reads a certain number
1385 * of bytes or reaches a newline character, which it reads into the
1386 * array as well.
1387 *
1388 * <p>This method <u><b>does not</b></u> returns -1 if it reaches the end of the input
1389 * stream before reading the maximum number of bytes, it returns -1, if no bytes read.
1390 *
1391 * @param b an array of bytes into which data is read
1392 *
1393 * @param off an integer specifying the character at which
1394 * this method begins reading
1395 *
1396 * @param len an integer specifying the maximum number of
1397 * bytes to read
1398 *
1399 * @return an integer specifying the actual number of bytes
1400 * read, or -1 if the end of the stream is reached
1401 *
1402 * @exception IOException if an input or output exception has occurred
1403 *
1404
1405 Note: We have a problem with Tomcat reporting an erroneous number of bytes, so we need to check this.
1406 This is the method where we get an infinite loop, but only with binary files.
1407 */
1408 private int readLine(InputStream in, byte[] b, int off, int len) throws IOException
1409 {
1410 if (len <= 0)
1411 return 0;
1412
1413 int count = 0, c;
1414
1415 while ((c = in.read()) != -1)
1416 {
1417 b[off++] = (byte)c;
1418 count++;
1419 if (c == '\n' || count == len)
1420 break;
1421 }
1422
1423 return count > 0 ? count : -1;
1424 }
1425
1426 /***
1427 Use when debugging this object.
1428 */
1429 protected void debug(String x)
1430 {
1431 if (debug!=null)
1432 {
1433 debug.println(x);
1434 debug.flush();
1435 }
1436 }
1437
1438 /***
1439 For debugging.
1440
1441 Be aware that if you have a form with multiple FILE input types of the same name, only the first one
1442 will actually be returned from the getFileParameter(...) method, which all the file access methods call.
1443
1444 So if your upload file was actually uploaded against the second file input field, then it will not be accessible,
1445 via the methods.
1446 */
1447 public String getHtmlTable()
1448 {
1449 StringBuffer sbReturn = new StringBuffer();
1450
1451 sbReturn.append("<h2>Parameters</h2>");
1452 sbReturn.append("\n<table border=3><tr><td><b>Name</b></td><td><b>Value</b></td></tr>");
1453 for (Enumeration e = getParameterNames() ; e.hasMoreElements() ;)
1454 {
1455 String strName = (String) e.nextElement();
1456 sbReturn.append("\n<tr>" +
1457 "<td>" + strName + "</td>");
1458
1459 sbReturn.append("<td><table border=1><tr>");
1460 for (Enumeration f = getURLParameters(strName); f.hasMoreElements() ;)
1461 {
1462 String value = (String)f.nextElement();
1463 sbReturn.append("<td>"+value+"</td>");
1464 }
1465 sbReturn.append("</tr></table></td></tr>");
1466 }
1467 sbReturn.append("</table>");
1468
1469 sbReturn.append("<h2>File Parameters</h2>");
1470
1471 if(this.isMaxBytesExceeded())
1472 {
1473 sbReturn.append("<p><font color=red>Max Bytes exceeded ("+this.intContentLength+" > "+this.intMaxContentLength+") all file uploads ignored.</font></p>");
1474 }
1475
1476 sbReturn.append("\n<table border=2><tr><td><b>Name</b></td><td><b>Base Filename</b></td><td><b>Raw Filename</b></td><td><b>Temporary File</b></td><td><b>Content Type</b></td><td><b>Size</b></td></tr>");
1477 for (Enumeration e = getFileParameterNames() ; e.hasMoreElements() ;)
1478 {
1479 String strName = (String) e.nextElement();
1480
1481 sbReturn.append("\n<tr>" +
1482 "<td>" + strName + "</td>" +
1483 "<td>" + (getBaseFilename(strName)!=null?getBaseFilename(strName):"") + "</td>"+
1484 "<td>" + (getRawFilename(strName)!=null?getRawFilename(strName):"") + "</td>");
1485
1486 if (loadIntoMemory)
1487 sbReturn.append("<td>" + (getFileSize(strName)>0?"<i>in memory</i>":"") + "</td>");
1488 else
1489 sbReturn.append("<td>" + (getFile(strName)!=null?getFile(strName).getAbsolutePath():"") + "</td>");
1490
1491 sbReturn.append("<td>" + (getContentType(strName)!=null?getContentType(strName):"") + "</td>" +
1492 "<td>" + (getFileSize(strName)!=-1?getFileSize(strName)+"":"") + "</td>" +
1493 "</tr>");
1494 }
1495 sbReturn.append("</table>");
1496
1497 return sbReturn.toString();
1498 }
1499 }