KickJava   Java API By Example, From Geeks To Geeks.

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


1 /*
2  * $Header: /cvsroot/director168/director_standard_portlets/src/org/apache/commons/fileupload/PortletFileUploadBase.java,v 1.1 2003/10/01 22:21:43 jsackett Exp $
3  * $Revision: 1.1 $
4  * $Date: 2003/10/01 22:21:43 $
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
74 import javax.portlet.ActionRequest;
75
76
77 /**
78  * <p>High level API for processing file uploads.</p>
79  *
80  * <p>This class handles multiple files per single HTML widget, sent using
81  * <code>multipart/mixed</code> encoding type, as specified by
82  * <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
83  * #parseRequest(HttpServletRequest)} to acquire a list of {@link
84  * org.apache.commons.fileupload.FileItem}s associated with a given HTML
85  * widget.</p>
86  *
87  * <p>How the data for individual parts is stored is determined by the factory
88  * used to create them; a given part may be in memory, on disk, or somewhere
89  * else.</p>
90  *
91  * @author <a HREF="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
92  * @author <a HREF="mailto:dlr@collab.net">Daniel Rall</a>
93  * @author <a HREF="mailto:jvanzyl@apache.org">Jason van Zyl</a>
94  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
95  * @author <a HREF="mailto:martinc@apache.org">Martin Cooper</a>
96  * @author Sean C. Sullivan
97  *
98  * @version $Id: PortletFileUploadBase.java,v 1.1 2003/10/01 22:21:43 jsackett Exp $
99  */

100 public abstract class PortletFileUploadBase
101 {
102
103     // ---------------------------------------------------------- Class methods
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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