View Javadoc

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     // If not null, send debugging out here.
127     private PrintWriter debug = null;
128 
129     private Hashtable htParameters = null;
130     private Hashtable htFiles = null;
131 
132     private String strBoundary = null;
133     
134     // If this Directory spec remains null, writing of files will be disabled...
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); // 2MB!
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     // Only used for file upload to memory.
173     public static final int CONTENTS = 3;
174 
175     // Only used for file uploaded to file system. This is the temporary unique filename of the saved file
176     public static final int TMP_FILENAME = 4;
177 
178     // The raw filename string as sent by the browser.
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             // This will test the encoding for validity.
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 &quot;Content-Type&quot; HTTP header value.
218      * @param intContentLength      The &quot;Content-Length&quot; 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, // debug 
234 			    strContentTypeText, 
235 				intContentLength, 
236 				in, 
237 				false, //loadIntoMemory
238 				strSaveDirectory,
239 				MultipartRequest.MAX_READ_BYTES, 
240 				MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
241 				null); //encoding
242     }
243     
244     /*** 
245      * Standard Constructor
246      *
247      * @param strContentTypeText    The &quot;Content-Type&quot; HTTP header value.
248      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
272 				strSaveDirectory,
273 				intMaxReadBytes, 
274 				MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
275 				null); //encoding
276     }
277 
278     /*** 
279      * Standard Constructor
280      *
281      * @param debug                 A PrintWriter that can be used for debugging.
282      * @param strContentTypeText    The &quot;Content-Type&quot; HTTP header value.
283      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
306 				strSaveDirectory,
307 				MultipartRequest.MAX_READ_BYTES, 
308 				MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
309 				null); //encoding
310     }
311 
312     /*** 
313      * Memory Constructor
314      *
315      * @param debug                 A PrintWriter that can be used for debugging.
316      * @param strContentTypeText    The &quot;Content-Type&quot; HTTP header value.
317      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
338 				null, //strSaveDirectory
339 				intMaxReadBytes, 
340 				MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
341 				null); //encoding
342     }
343 	
344 	/*** 
345      * Standard Constructor
346      *
347      * @param debug                 A PrintWriter that can be used for debugging.
348      * @param strContentTypeText    The &quot;Content-Type&quot; HTTP header value.
349      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
373 				strSaveDirectory,
374 				intMaxReadBytes, 
375 				MultipartRequest.ABORT_IF_MAX_BYES_EXCEEDED,
376 				null);//encoding
377     }
378 
379     /*** 
380      * Standard Constructor
381      *
382      * @param debug                 A PrintWriter that can be used for debugging.
383      * @param strContentTypeText    The &quot;Content-Type&quot; HTTP header value.
384      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
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 &quot;Content-Type&quot; HTTP header value.
422      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
445 				null, // strSaveDirectory
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 &quot;Content-Type&quot; HTTP header value.
456      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
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 &quot;Content-Type&quot; HTTP header value.
503      * @param intContentLength      The &quot;Content-Length&quot; 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, //loadIntoMemory
535 				null, // strSaveDirectory
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 &quot;Content-Type&quot; HTTP header value.
546      * @param intContentLength      The &quot;Content-Length&quot; 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         // save reference to debug stream for later.
580         this.debug = debug;
581 
582 		// If true - ignore the directory specification.		
583 		this.loadIntoMemory = loadIntoMemory;
584         
585 		// IF strSaveDirectory == NULL, then we should ignore any files uploaded.
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 		// Set the encoding.
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             // <mtl,jpell>
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)// FIX: 1.15
614         {
615             debug("ContentLength = " + this.intContentLength);
616             debug("MaxReadBytes = " + this.intMaxContentLength);
617             
618             throw new MaxReadBytesException(this.intContentLength, this.intMaxContentLength);
619         }
620 
621         // Instantiate the hashtable...
622         htParameters = new Hashtable();
623         htFiles = new Hashtable();
624         blockOfBytes = new byte[READ_LINE_BLOCK];
625 
626         // Even though this method would never normally be called more than once
627         // in the objects lifetime, we will initialise it here anyway.
628         intTotalRead=0; // <David Tuma> - fix: 1.20
629 
630 		// Now parse the data.
631 		parse(new BufferedInputStream(in));
632 
633         // No need for these once parse is complete.
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         // Can cast null, it will be ignored.
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) // file not available.
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(); // empty file, but lets return an inputstream anyway.
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) // file not available.
757 		{
758 			return null;
759 		}
760 		else
761 		{
762 			// Fix: If fileOutPutDirectory is null, then we are ignoring any file contents, so we must return null.
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 &lt;input type=file ...&gt; 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 &lt;input type=file ...&gt; 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) // file not available.
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         // Now ensure valid value.
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         // First run through, check that the first line is a boundary, otherwise throw a exception as format incorrect.
891         read = readLine(in, blockOfBytes);
892         strLine = read>0? new String(blockOfBytes, 0, read, charEncoding): null;
893 
894         // Must be boundary at top of loop, otherwise we have finished.
895         if (strLine==null || strLine.indexOf(this.strBoundary)==-1)
896 		{
897             throw new IOException("Invalid Form Data, no boundary encountered.");
898 		}
899 		
900         // At the top of loop, we assume that the Content-Disposition line is next, otherwise we are at the end.
901         while (true)
902         {
903             // Get Content-Disposition line.
904             read = readLine(in, blockOfBytes);
905             if (read<=0)
906                 break; // Nothing to do.
907             else
908             {
909                 strLine = new String(blockOfBytes, 0, read, charEncoding);
910                 
911                 // Mac IE4 adds extra line after last boundary - 1.21
912                 if(strLine==null || strLine.length() == 0 || strLine.trim().length() == 0)
913                     break;
914 
915                 // TODO: Improve performance by getting both the name and filename from strLine in one go...
916                 strName = trimQuotes(getValue("name", strLine));
917                 // If this is not null, it indicates that we are processing a filename.
918                 // Now if not null, strip it of any directory information.
919                 strFilename = trimQuotes(getValue("filename", strLine));
920 
921                 // No filename specified at all - parameter
922                 if (strFilename==null)
923                 {
924                     // Skip blank line.
925                     readLine(in, blockOfBytes);
926 
927 					String param = readParameter(in);
928 					addParameter(strName, param);
929                 }
930                 else// (strFilename!=null)
931                 {
932                     // Fix: did not check whether filename was empty string indicating a FILE was not passed.
933                     if (strFilename.length()==0)
934                     {
935                         // FIX 1.14: IE problem with empty filename.
936                         read = readLine(in, blockOfBytes);
937                         strLine = read>0? new String(blockOfBytes, 0, read, charEncoding): null;
938                         
939                         // FIX 1.14 IE Problem still: Check for content-type and extra line even though no file specified.
940                         if (strLine!=null && strLine.toLowerCase().startsWith("content-type:"))
941                             readLine(in, blockOfBytes);
942 
943                         // Skip blank line.
944                         readLine(in, blockOfBytes);
945 
946                         // Fix: FILE INPUT TYPE, but no file passed as input...
947                         addFileParameter(strName, new Object[] {null, null, null, null, null, null});
948 
949                         readLine(in, blockOfBytes); 
950                     }
951                     else // File uploaded, or at least a filename was specified, it could still be spurious.
952                     {
953                         // Need to get the content type.
954                         read = readLine(in, blockOfBytes);
955                         strLine = read>0? new String(blockOfBytes, 0, read, charEncoding): null;
956                         
957                         strContentType = "application/octet-stream";
958                         // Fix 1.11: If not null AND strLine.length() is long enough.
959                         // Modified in 1.19, as we should be checking if it is actually a Content-Type.
960                         if (strLine!=null && strLine.toLowerCase().startsWith("content-type:"))//Changed 1.19
961                         {
962                             strContentType = strLine.substring("content-type:".length()).trim();// Changed 1.13
963 
964                             // Skip blank line, but only if a Content-Type was specified.
965                             readLine(in, blockOfBytes);
966                         }
967 
968                         long filesize = -1;
969 
970                         // Will remain null for read onto file system uploads.
971                         byte[] contentsOfFile = null;
972 
973                         // Will remain null if files are loaded into memory.
974                         File outFile = null;
975                         
976                         // Get the BASENAME version of strFilename.
977                         strRawFilename = strFilename;
978                         strFilename = getBasename(strFilename);
979 
980 						 // Are we loading files into memory instead of the filesystem?
981 						if (loadIntoMemory)
982 						{
983 						    contentsOfFile = readFile(in);
984 						    if (contentsOfFile!=null)
985 						        filesize = contentsOfFile.length;
986 						}
987 						else// Read the file onto file system.
988 						{
989 						    // If nowhere to write file, then we pass a null file to 
990 						    // readAndWriteFile, in which case the uploaded file contents
991 						    // will silently be processed and discarded.
992 						    if(fileOutPutDirectory != null)
993 						    {
994 						        outFile = TempFile.createTempFile("multPartReq", null, fileOutPutDirectory);
995 						    }
996 						    filesize = readAndWriteFile(in, outFile);
997 						}
998 						
999                         // Fix 1.18 for multiple FILE parameter values.
1000                         if (filesize>0)
1001                             addFileParameter(strName, new Object[] {strFilename, strContentType, new Long(filesize), contentsOfFile, outFile, strRawFilename});
1002                         else // Zero length file.
1003 							addFileParameter(strName, new Object[] {strFilename, strContentType, new Long(0), null, null, strRawFilename});
1004 					}
1005 				}
1006             }
1007         }// while 
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         // Fix 1.16: for multiple parameter values.
1017         Object objParms = htParameters.get(strName);
1018 
1019 		// Add an new entry to the param vector.
1020         if (objParms instanceof Vector)
1021 		{
1022             ((Vector)objParms).addElement(value);
1023 		}
1024         else if (objParms instanceof String)// There is only one entry, so we create a vector!
1025         {
1026 			Vector vecParms = new Vector();
1027             vecParms.addElement(objParms);
1028             vecParms.addElement(value);
1029 
1030             htParameters.put(strName, vecParms);
1031         }
1032         else  // first entry for strName.
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         // Add an new entry to the param vector.
1049         if (objParms instanceof Vector)
1050             ((Vector)objParms).addElement(fileObj);
1051         else if (objParms instanceof Object[])// There is only one entry, so we create a vector!
1052         {
1053             Vector vecParms = new Vector();
1054             vecParms.addElement(objParms);
1055             vecParms.addElement(fileObj);
1056         
1057             htFiles.put(strName, vecParms);
1058         }
1059         else  // first entry for strName.
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             // Change v1.18: Only instantiate string once for performance reasons.
1083             line = new String(blockOfBytes, 0, read, charEncoding);
1084             if (read<blockOfBytes.length && line.indexOf(this.strBoundary)!=-1)
1085                 break; // Boundary found, we need to finish up.
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         // This variable will be assigned the bytes actually read.
1104         byte[] secondLineOfBytes = new byte[blockOfBytes.length];
1105         // So we do not have to keep creating the second array.
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             // Found boundary.
1115             if (read<blockOfBytes.length && new String(blockOfBytes, 0, read, charEncoding).indexOf(this.strBoundary)!=-1)
1116             {
1117                 // Write the line, minus any line ending bytes.
1118                 //The secondLineOfBytes will NEVER BE NON-NULL if out==null, so there is no need to included this in the test
1119                 if(sizeOfSecondArray!=0)
1120                 {
1121                     // Only used once, so declare here.
1122                     int actualLength = getLengthMinusEnding(secondLineOfBytes, sizeOfSecondArray);
1123                     if (actualLength>0 && out!=null)
1124                     {
1125                         out.write(secondLineOfBytes, 0, actualLength);
1126                         // Update file size.
1127                         fileSize+=actualLength;
1128                     }
1129                 }
1130                 break;
1131             }
1132             else
1133             {
1134                 // Write out previous line.
1135                 //The sizeOfSecondArray will NEVER BE ZERO if out==null, so there is no need to included this in the test
1136                 if(sizeOfSecondArray!=0)
1137                 {
1138                     out.write(secondLineOfBytes, 0, sizeOfSecondArray);
1139                     // Update file size.
1140                     fileSize+=sizeOfSecondArray;
1141                 }
1142 
1143                 // out will always be null, so there is no need to reset sizeOfSecondArray to zero each time.
1144                 if(out!=null)
1145                 {
1146                     //Copy the read bytes into the array.
1147                     System.arraycopy(blockOfBytes,0,secondLineOfBytes,0,read);
1148                     // That is how many bytes to read from the secondLineOfBytes
1149                     sizeOfSecondArray=read;
1150                 }
1151             }
1152         }
1153 
1154         //Return the number of bytes written to outstream.
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 			// 1.30rc1 - if max content is larger or equal to actual content length, then we
1173 			// can continue, otherwise, all file content should be silently ignored.
1174 			if(this.intContentLength <= this.intMaxContentLength) // file not available.
1175 			{
1176 				// Because the outFile should be a temporary file provided by the TempFile
1177 		        // class, it should already exist and should be writable.
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             // Count would NOT be larger than zero if 'out' was null.
1186             if (count==0)
1187 	        {
1188                 // Delete file as empty.
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 		// 1.30rc1 - if max content is larger or equal to actual content length, then we
1216 		// can continue, otherwise, all file content should be silently ignored.
1217 		if(this.intContentLength <= this.intMaxContentLength)
1218 		{
1219 			// In this case, we do not need to worry about a outputdirectory.
1220 			out = new ByteArrayOutputStream();
1221 		}
1222 		
1223         long count = readAndWrite(in, out);
1224         // Count would NOT be larger than zero if out was null.
1225         if (count>0)
1226         {
1227             // Return contents of file to parse method for inclusion in htFiles object.
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         // Ensure that there is still stuff to read...
1252         if(this.intTotalRead >= this.intContentLength) 
1253             return -1;
1254 		
1255         // Get the length of what we are wanting to read.
1256         int length = bytesToBeRead.length;
1257 
1258         // End of content, but some servers (apparently) may not realise this and end the InputStream, so
1259         // we cover ourselves this way.
1260         if (length > (this.intContentLength - this.intTotalRead))
1261 		{
1262             length = (int) (this.intContentLength - this.intTotalRead);  // So we only read the data that is left.
1263 		}
1264 		
1265         int result = readLine(in, bytesToBeRead, 0, length);
1266         // Only if we actually read something, otherwise something weird has happened, such as the end of stream.
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         // Saves having to go any further....
1332         if (strItem==null || strItem.indexOf("\"")==-1)
1333             return strItem;
1334         
1335         // Get rid of any whitespace..
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             // Ensure either first name, or a space or ; precedes it.
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) // May return an empty string...
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 }