KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > fileupload > FileUploadBase


1 /*
2  * $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/FileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
3  * $Revision: 1.3 $
4  * $Date: 2003/06/01 00:18:13 $
5  *
6  * ====================================================================
7  *
8  * The Apache Software License, Version 1.1
9  *
10  * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
11  * reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  *
17  * 1. Redistributions of source code must retain the above copyright
18  * notice, this list of conditions and the following disclaimer.
19  *
20  * 2. Redistributions in binary form must reproduce the above copyright
21  * notice, this list of conditions and the following disclaimer in
22  * the documentation and/or other materials provided with the
23  * distribution.
24  *
25  * 3. The end-user documentation included with the redistribution, if
26  * any, must include the following acknowlegement:
27  * "This product includes software developed by the
28  * Apache Software Foundation (http://www.apache.org/)."
29  * Alternately, this acknowlegement may appear in the software itself,
30  * if and wherever such third-party acknowlegements normally appear.
31  *
32  * 4. The names "The Jakarta Project", "Commons", and "Apache Software
33  * Foundation" must not be used to endorse or promote products derived
34  * from this software without prior written permission. For written
35  * permission, please contact apache@apache.org.
36  *
37  * 5. Products derived from this software may not be called "Apache"
38  * nor may "Apache" appear in their names without prior written
39  * permission of the Apache Group.
40  *
41  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
42  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
45  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
46  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
47  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
48  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
49  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
51  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
52  * SUCH DAMAGE.
53  * ====================================================================
54  *
55  * This software consists of voluntary contributions made by many
56  * individuals on behalf of the Apache Software Foundation. For more
57  * information on the Apache Software Foundation, please see
58  * <http://www.apache.org/>.
59  *
60  */

61
62
63 package org.apache.commons.fileupload;
64
65
66 import java.io.IOException JavaDoc;
67 import java.io.InputStream JavaDoc;
68 import java.io.OutputStream JavaDoc;
69 import java.util.ArrayList JavaDoc;
70 import java.util.HashMap JavaDoc;
71 import java.util.List JavaDoc;
72 import java.util.Map JavaDoc;
73 import javax.servlet.http.HttpServletRequest JavaDoc;
74
75
76 /**
77  * <p>High level API for processing file uploads.</p>
78  *
79  * <p>This class handles multiple files per single HTML widget, sent using
80  * <code>multipart/mixed</code> encoding type, as specified by
81  * <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
82  * #parseRequest(HttpServletRequest)} to acquire a list of {@link
83  * org.apache.commons.fileupload.FileItem}s associated with a given HTML
84  * widget.</p>
85  *
86  * <p>How the data for individual parts is stored is determined by the factory
87  * used to create them; a given part may be in memory, on disk, or somewhere
88  * else.</p>
89  *
90  * @author <a HREF="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
91  * @author <a HREF="mailto:dlr@collab.net">Daniel Rall</a>
92  * @author <a HREF="mailto:jvanzyl@apache.org">Jason van Zyl</a>
93  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
94  * @author <a HREF="mailto:martinc@apache.org">Martin Cooper</a>
95  * @author Sean C. Sullivan
96  *
97  * @version $Id: FileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
98  */

99 public abstract class FileUploadBase
100 {
101
102     // ---------------------------------------------------------- Class methods
103

104
105     /**
106      * Utility method that determines whether the request contains multipart
107      * content.
108      *
109      * @param req The servlet request to be evaluated. Must be non-null.
110      *
111      * @return <code>true</code> if the request is multipart;
112      * <code>false</code> otherwise.
113      */

114     public static final boolean isMultipartContent(HttpServletRequest JavaDoc req)
115     {
116         String JavaDoc contentType = req.getHeader(CONTENT_TYPE);
117         if (contentType == null)
118         {
119             return false;
120         }
121         if (contentType.startsWith(MULTIPART))
122         {
123             return true;
124         }
125         return false;
126     }
127
128
129     // ----------------------------------------------------- Manifest constants
130

131
132     /**
133      * HTTP content type header name.
134      */

135     public static final String JavaDoc CONTENT_TYPE = "Content-type";
136
137
138     /**
139      * HTTP content disposition header name.
140      */

141     public static final String JavaDoc CONTENT_DISPOSITION = "Content-disposition";
142
143
144     /**
145      * Content-disposition value for form data.
146      */

147     public static final String JavaDoc FORM_DATA = "form-data";
148
149
150     /**
151      * Content-disposition value for file attachment.
152      */

153     public static final String JavaDoc ATTACHMENT = "attachment";
154
155
156     /**
157      * Part of HTTP content type header.
158      */

159     public static final String JavaDoc MULTIPART = "multipart/";
160
161
162     /**
163      * HTTP content type header for multipart forms.
164      */

165     public static final String JavaDoc MULTIPART_FORM_DATA = "multipart/form-data";
166
167
168     /**
169      * HTTP content type header for multiple uploads.
170      */

171     public static final String JavaDoc MULTIPART_MIXED = "multipart/mixed";
172
173
174     /**
175      * The maximum length of a single header line that will be parsed
176      * (1024 bytes).
177      */

178     public static final int MAX_HEADER_SIZE = 1024;
179
180
181     // ----------------------------------------------------------- Data members
182

183
184     /**
185      * The maximum size permitted for an uploaded file. A value of -1 indicates
186      * no maximum.
187      */

188     private long sizeMax = -1;
189
190
191     /**
192      * The content encoding to use when reading part headers.
193      */

194     private String JavaDoc headerEncoding;
195
196
197     // ----------------------------------------------------- Property accessors
198

199
200     /**
201      * Returns the factory class used when creating file items.
202      *
203      * @return The factory class for new file items.
204      */

205     public abstract FileItemFactory getFileItemFactory();
206
207
208     /**
209      * Sets the factory class to use when creating file items.
210      *
211      * @param factory The factory class for new file items.
212      */

213     public abstract void setFileItemFactory(FileItemFactory factory);
214
215
216     /**
217      * Returns the maximum allowed upload size.
218      *
219      * @return The maximum allowed size, in bytes.
220      *
221      * @see #setSizeMax(long)
222      *
223      */

224     public long getSizeMax()
225     {
226         return sizeMax;
227     }
228
229
230     /**
231      * Sets the maximum allowed upload size. If negative, there is no maximum.
232      *
233      * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
234      *
235      * @see #getSizeMax()
236      *
237      */

238     public void setSizeMax(long sizeMax)
239     {
240         this.sizeMax = sizeMax;
241     }
242
243
244     /**
245      * Retrieves the character encoding used when reading the headers of an
246      * individual part. When not specified, or <code>null</code>, the platform
247      * default encoding is used.
248      *
249      * @return The encoding used to read part headers.
250      */

251     public String JavaDoc getHeaderEncoding()
252     {
253         return headerEncoding;
254     }
255
256
257     /**
258      * Specifies the character encoding to be used when reading the headers of
259      * individual parts. When not specified, or <code>null</code>, the platform
260      * default encoding is used.
261      *
262      * @param encoding The encoding used to read part headers.
263      */

264     public void setHeaderEncoding(String JavaDoc encoding)
265     {
266         headerEncoding = encoding;
267     }
268
269
270     // --------------------------------------------------------- Public methods
271

272
273     /**
274      * Processes an <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
275      * compliant <code>multipart/form-data</code> stream. If files are stored
276      * on disk, the path is given by <code>getRepository()</code>.
277      *
278      * @param req The servlet request to be parsed.
279      *
280      * @return A list of <code>FileItem</code> instances parsed from the
281      * request, in the order that they were transmitted.
282      *
283      * @exception FileUploadException if there are problems reading/parsing
284      * the request or storing files.
285      */

286     public List JavaDoc /* FileItem */ parseRequest(HttpServletRequest JavaDoc req)
287         throws FileUploadException
288     {
289         if (null == req)
290         {
291             throw new NullPointerException JavaDoc("req parameter");
292         }
293
294         ArrayList JavaDoc items = new ArrayList JavaDoc();
295         String JavaDoc contentType = req.getHeader(CONTENT_TYPE);
296
297         if ((null == contentType) || (!contentType.startsWith(MULTIPART)))
298         {
299             throw new InvalidContentTypeException(
300                 "the request doesn't contain a "
301                 + MULTIPART_FORM_DATA
302                 + " or "
303                 + MULTIPART_MIXED
304                 + " stream, content type header is "
305                 + contentType);
306         }
307         int requestSize = req.getContentLength();
308
309         if (requestSize == -1)
310         {
311             throw new UnknownSizeException(
312                 "the request was rejected because it's size is unknown");
313         }
314
315         if (sizeMax >= 0 && requestSize > sizeMax)
316         {
317             throw new SizeLimitExceededException(
318                 "the request was rejected because "
319                 + "it's size exceeds allowed range");
320         }
321
322         try
323         {
324             int boundaryIndex = contentType.indexOf("boundary=");
325             if (boundaryIndex < 0)
326             {
327                 throw new FileUploadException(
328                         "the request was rejected because "
329                         + "no multipart boundary was found");
330             }
331             byte[] boundary = contentType.substring(
332                     boundaryIndex + 9).getBytes();
333
334             InputStream JavaDoc input = req.getInputStream();
335
336             MultipartStream multi = new MultipartStream(input, boundary);
337             multi.setHeaderEncoding(headerEncoding);
338
339             boolean nextPart = multi.skipPreamble();
340             while (nextPart)
341             {
342                 Map JavaDoc headers = parseHeaders(multi.readHeaders());
343                 String JavaDoc fieldName = getFieldName(headers);
344                 if (fieldName != null)
345                 {
346                     String JavaDoc subContentType = getHeader(headers, CONTENT_TYPE);
347                     if (subContentType != null && subContentType
348                                                 .startsWith(MULTIPART_MIXED))
349                     {
350                         // Multiple files.
351
byte[] subBoundary =
352                             subContentType.substring(
353                                 subContentType
354                                 .indexOf("boundary=") + 9).getBytes();
355                         multi.setBoundary(subBoundary);
356                         boolean nextSubPart = multi.skipPreamble();
357                         while (nextSubPart)
358                         {
359                             headers = parseHeaders(multi.readHeaders());
360                             if (getFileName(headers) != null)
361                             {
362                                 FileItem item =
363                                         createItem(headers, false);
364                                 OutputStream JavaDoc os = item.getOutputStream();
365                                 try
366                                 {
367                                     multi.readBodyData(os);
368                                 }
369                                 finally
370                                 {
371                                     os.close();
372                                 }
373                                 items.add(item);
374                             }
375                             else
376                             {
377                                 // Ignore anything but files inside
378
// multipart/mixed.
379
multi.discardBodyData();
380                             }
381                             nextSubPart = multi.readBoundary();
382                         }
383                         multi.setBoundary(boundary);
384                     }
385                     else
386                     {
387                         if (getFileName(headers) != null)
388                         {
389                             // A single file.
390
FileItem item = createItem(headers, false);
391                             OutputStream JavaDoc os = item.getOutputStream();
392                             try
393                             {
394                                 multi.readBodyData(os);
395                             }
396                             finally
397                             {
398                                 os.close();
399                             }
400                             items.add(item);
401                         }
402                         else
403                         {
404                             // A form field.
405
FileItem item = createItem(headers, true);
406                             OutputStream JavaDoc os = item.getOutputStream();
407                             try
408                             {
409                                 multi.readBodyData(os);
410                             }
411                             finally
412                             {
413                                 os.close();
414                             }
415                             items.add(item);
416                         }
417                     }
418                 }
419                 else
420                 {
421                     // Skip this part.
422
multi.discardBodyData();
423                 }
424                 nextPart = multi.readBoundary();
425             }
426         }
427         catch (IOException JavaDoc e)
428         {
429             throw new FileUploadException(
430                 "Processing of " + MULTIPART_FORM_DATA
431                     + " request failed. " + e.getMessage());
432         }
433
434         return items;
435     }
436
437
438     // ------------------------------------------------------ Protected methods
439

440
441     /**
442      * Retrieves the file name from the <code>Content-disposition</code>
443      * header.
444      *
445      * @param headers A <code>Map</code> containing the HTTP request headers.
446      *
447      * @return The file name for the current <code>encapsulation</code>.
448      */

449     protected String JavaDoc getFileName(Map JavaDoc /* String, String */ headers)
450     {
451         String JavaDoc fileName = null;
452         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
453         if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
454         {
455             int start = cd.indexOf("filename=\"");
456             int end = cd.indexOf('"', start + 10);
457             if (start != -1 && end != -1)
458             {
459                 fileName = cd.substring(start + 10, end).trim();
460             }
461         }
462         return fileName;
463     }
464
465
466     /**
467      * Retrieves the field name from the <code>Content-disposition</code>
468      * header.
469      *
470      * @param headers A <code>Map</code> containing the HTTP request headers.
471      *
472      * @return The field name for the current <code>encapsulation</code>.
473      */

474     protected String JavaDoc getFieldName(Map JavaDoc /* String, String */ headers)
475     {
476         String JavaDoc fieldName = null;
477         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
478         if (cd != null && cd.startsWith(FORM_DATA))
479         {
480             int start = cd.indexOf("name=\"");
481             int end = cd.indexOf('"', start + 6);
482             if (start != -1 && end != -1)
483             {
484                 fieldName = cd.substring(start + 6, end);
485             }
486         }
487         return fieldName;
488     }
489
490
491     /**
492      * Creates a new {@link FileItem} instance.
493      *
494      * @param headers A <code>Map</code> containing the HTTP request
495      * headers.
496      * @param isFormField Whether or not this item is a form field, as
497      * opposed to a file.
498      *
499      * @return A newly created <code>FileItem</code> instance.
500      *
501      * @exception FileUploadException if an error occurs.
502      */

503     protected FileItem createItem(Map JavaDoc /* String, String */ headers,
504                                   boolean isFormField)
505         throws FileUploadException
506     {
507         return getFileItemFactory().createItem(getFieldName(headers),
508                 getHeader(headers, CONTENT_TYPE),
509                 isFormField,
510                 getFileName(headers));
511     }
512
513
514     /**
515      * <p> Parses the <code>header-part</code> and returns as key/value
516      * pairs.
517      *
518      * <p> If there are multiple headers of the same names, the name
519      * will map to a comma-separated list containing the values.
520      *
521      * @param headerPart The <code>header-part</code> of the current
522      * <code>encapsulation</code>.
523      *
524      * @return A <code>Map</code> containing the parsed HTTP request headers.
525      */

526     protected Map JavaDoc /* String, String */ parseHeaders(String JavaDoc headerPart)
527     {
528         Map JavaDoc headers = new HashMap JavaDoc();
529         char buffer[] = new char[MAX_HEADER_SIZE];
530         boolean done = false;
531         int j = 0;
532         int i;
533         String JavaDoc header, headerName, headerValue;
534         try
535         {
536             while (!done)
537             {
538                 i = 0;
539                 // Copy a single line of characters into the buffer,
540
// omitting trailing CRLF.
541
while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')
542                 {
543                     buffer[i++] = headerPart.charAt(j++);
544                 }
545                 header = new String JavaDoc(buffer, 0, i - 2);
546                 if (header.equals(""))
547                 {
548                     done = true;
549                 }
550                 else
551                 {
552                     if (header.indexOf(':') == -1)
553                     {
554                         // This header line is malformed, skip it.
555
continue;
556                     }
557                     headerName = header.substring(0, header.indexOf(':'))
558                         .trim().toLowerCase();
559                     headerValue =
560                         header.substring(header.indexOf(':') + 1).trim();
561                     if (getHeader(headers, headerName) != null)
562                     {
563                         // More that one heder of that name exists,
564
// append to the list.
565
headers.put(headerName,
566                                     getHeader(headers, headerName) + ','
567                                         + headerValue);
568                     }
569                     else
570                     {
571                         headers.put(headerName, headerValue);
572                     }
573                 }
574             }
575         }
576         catch (IndexOutOfBoundsException JavaDoc e)
577         {
578             // Headers were malformed. continue with all that was
579
// parsed.
580
}
581         return headers;
582     }
583
584
585     /**
586      * Returns the header with the specified name from the supplied map. The
587      * header lookup is case-insensitive.
588      *
589      * @param headers A <code>Map</code> containing the HTTP request headers.
590      * @param name The name of the header to return.
591      *
592      * @return The value of specified header, or a comma-separated list if
593      * there were multiple headers of that name.
594      */

595     protected final String JavaDoc getHeader(Map JavaDoc /* String, String */ headers,
596                                      String JavaDoc name)
597     {
598         return (String JavaDoc) headers.get(name.toLowerCase());
599     }
600
601
602     /**
603      * Thrown to indicate that the request is not a multipart request.
604      */

605     public static class InvalidContentTypeException
606         extends FileUploadException
607     {
608         /**
609          * Constructs a <code>InvalidContentTypeException</code> with no
610          * detail message.
611          */

612         public InvalidContentTypeException()
613         {
614             super();
615         }
616
617         /**
618          * Constructs an <code>InvalidContentTypeException</code> with
619          * the specified detail message.
620          *
621          * @param message The detail message.
622          */

623         public InvalidContentTypeException(String JavaDoc message)
624         {
625             super(message);
626         }
627     }
628
629
630     /**
631      * Thrown to indicate that the request size is not specified.
632      */

633     public static class UnknownSizeException
634         extends FileUploadException
635     {
636         /**
637          * Constructs a <code>UnknownSizeException</code> with no
638          * detail message.
639          */

640         public UnknownSizeException()
641         {
642             super();
643         }
644
645         /**
646          * Constructs an <code>UnknownSizeException</code> with
647          * the specified detail message.
648          *
649          * @param message The detail message.
650          */

651         public UnknownSizeException(String JavaDoc message)
652         {
653             super(message);
654         }
655     }
656
657
658     /**
659      * Thrown to indicate that the request size exceeds the configured maximum.
660      */

661     public static class SizeLimitExceededException
662         extends FileUploadException
663     {
664         /**
665          * Constructs a <code>SizeExceededException</code> with no
666          * detail message.
667          */

668         public SizeLimitExceededException()
669         {
670             super();
671         }
672
673         /**
674          * Constructs an <code>SizeExceededException</code> with
675          * the specified detail message.
676          *
677          * @param message The detail message.
678          */

679         public SizeLimitExceededException(String JavaDoc message)
680         {
681             super(message);
682         }
683     }
684
685 }
686
Popular Tags