KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tomcat > util > http > fileupload > FileUploadBase


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.tomcat.util.http.fileupload;
20
21
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.io.OutputStream JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Map JavaDoc;
29 import javax.servlet.http.HttpServletRequest JavaDoc;
30
31
32 /**
33  * <p>High level API for processing file uploads.</p>
34  *
35  * <p>This class handles multiple files per single HTML widget, sent using
36  * <code>multipart/mixed</code> encoding type, as specified by
37  * <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
38  * #parseRequest(HttpServletRequest)} to acquire a list of {@link
39  * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
40  * widget.</p>
41  *
42  * <p>How the data for individual parts is stored is determined by the factory
43  * used to create them; a given part may be in memory, on disk, or somewhere
44  * else.</p>
45  *
46  * @author <a HREF="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
47  * @author <a HREF="mailto:dlr@collab.net">Daniel Rall</a>
48  * @author <a HREF="mailto:jvanzyl@apache.org">Jason van Zyl</a>
49  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
50  * @author <a HREF="mailto:martinc@apache.org">Martin Cooper</a>
51  * @author Sean C. Sullivan
52  *
53  * @version $Id: FileUploadBase.java 467222 2006-10-24 03:17:11Z markt $
54  */

55 public abstract class FileUploadBase
56 {
57
58     // ---------------------------------------------------------- Class methods
59

60
61     /**
62      * Utility method that determines whether the request contains multipart
63      * content.
64      *
65      * @param req The servlet request to be evaluated. Must be non-null.
66      *
67      * @return <code>true</code> if the request is multipart;
68      * <code>false</code> otherwise.
69      */

70     public static final boolean isMultipartContent(HttpServletRequest JavaDoc req)
71     {
72         String JavaDoc contentType = req.getHeader(CONTENT_TYPE);
73         if (contentType == null)
74         {
75             return false;
76         }
77         if (contentType.startsWith(MULTIPART))
78         {
79             return true;
80         }
81         return false;
82     }
83
84
85     // ----------------------------------------------------- Manifest constants
86

87
88     /**
89      * HTTP content type header name.
90      */

91     public static final String JavaDoc CONTENT_TYPE = "Content-type";
92
93
94     /**
95      * HTTP content disposition header name.
96      */

97     public static final String JavaDoc CONTENT_DISPOSITION = "Content-disposition";
98
99
100     /**
101      * Content-disposition value for form data.
102      */

103     public static final String JavaDoc FORM_DATA = "form-data";
104
105
106     /**
107      * Content-disposition value for file attachment.
108      */

109     public static final String JavaDoc ATTACHMENT = "attachment";
110
111
112     /**
113      * Part of HTTP content type header.
114      */

115     public static final String JavaDoc MULTIPART = "multipart/";
116
117
118     /**
119      * HTTP content type header for multipart forms.
120      */

121     public static final String JavaDoc MULTIPART_FORM_DATA = "multipart/form-data";
122
123
124     /**
125      * HTTP content type header for multiple uploads.
126      */

127     public static final String JavaDoc MULTIPART_MIXED = "multipart/mixed";
128
129
130     /**
131      * The maximum length of a single header line that will be parsed
132      * (1024 bytes).
133      */

134     public static final int MAX_HEADER_SIZE = 1024;
135
136
137     // ----------------------------------------------------------- Data members
138

139
140     /**
141      * The maximum size permitted for an uploaded file. A value of -1 indicates
142      * no maximum.
143      */

144     private long sizeMax = -1;
145
146
147     /**
148      * The content encoding to use when reading part headers.
149      */

150     private String JavaDoc headerEncoding;
151
152
153     // ----------------------------------------------------- Property accessors
154

155
156     /**
157      * Returns the factory class used when creating file items.
158      *
159      * @return The factory class for new file items.
160      */

161     public abstract FileItemFactory getFileItemFactory();
162
163
164     /**
165      * Sets the factory class to use when creating file items.
166      *
167      * @param factory The factory class for new file items.
168      */

169     public abstract void setFileItemFactory(FileItemFactory factory);
170
171
172     /**
173      * Returns the maximum allowed upload size.
174      *
175      * @return The maximum allowed size, in bytes.
176      *
177      * @see #setSizeMax(long)
178      *
179      */

180     public long getSizeMax()
181     {
182         return sizeMax;
183     }
184
185
186     /**
187      * Sets the maximum allowed upload size. If negative, there is no maximum.
188      *
189      * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
190      *
191      * @see #getSizeMax()
192      *
193      */

194     public void setSizeMax(long sizeMax)
195     {
196         this.sizeMax = sizeMax;
197     }
198
199
200     /**
201      * Retrieves the character encoding used when reading the headers of an
202      * individual part. When not specified, or <code>null</code>, the platform
203      * default encoding is used.
204      *
205      * @return The encoding used to read part headers.
206      */

207     public String JavaDoc getHeaderEncoding()
208     {
209         return headerEncoding;
210     }
211
212
213     /**
214      * Specifies the character encoding to be used when reading the headers of
215      * individual parts. When not specified, or <code>null</code>, the platform
216      * default encoding is used.
217      *
218      * @param encoding The encoding used to read part headers.
219      */

220     public void setHeaderEncoding(String JavaDoc encoding)
221     {
222         headerEncoding = encoding;
223     }
224
225
226     // --------------------------------------------------------- Public methods
227

228
229     /**
230      * Processes an <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
231      * compliant <code>multipart/form-data</code> stream. If files are stored
232      * on disk, the path is given by <code>getRepository()</code>.
233      *
234      * @param req The servlet request to be parsed.
235      *
236      * @return A list of <code>FileItem</code> instances parsed from the
237      * request, in the order that they were transmitted.
238      *
239      * @exception FileUploadException if there are problems reading/parsing
240      * the request or storing files.
241      */

242     public List JavaDoc /* FileItem */ parseRequest(HttpServletRequest JavaDoc req)
243         throws FileUploadException
244     {
245         if (null == req)
246         {
247             throw new NullPointerException JavaDoc("req parameter");
248         }
249
250         ArrayList JavaDoc items = new ArrayList JavaDoc();
251         String JavaDoc contentType = req.getHeader(CONTENT_TYPE);
252
253         if ((null == contentType) || (!contentType.startsWith(MULTIPART)))
254         {
255             throw new InvalidContentTypeException(
256                 "the request doesn't contain a "
257                 + MULTIPART_FORM_DATA
258                 + " or "
259                 + MULTIPART_MIXED
260                 + " stream, content type header is "
261                 + contentType);
262         }
263         int requestSize = req.getContentLength();
264
265         if (requestSize == -1)
266         {
267             throw new UnknownSizeException(
268                 "the request was rejected because it's size is unknown");
269         }
270
271         if (sizeMax >= 0 && requestSize > sizeMax)
272         {
273             throw new SizeLimitExceededException(
274                 "the request was rejected because "
275                 + "it's size exceeds allowed range");
276         }
277
278         try
279         {
280             int boundaryIndex = contentType.indexOf("boundary=");
281             if (boundaryIndex < 0)
282             {
283                 throw new FileUploadException(
284                         "the request was rejected because "
285                         + "no multipart boundary was found");
286             }
287             byte[] boundary = contentType.substring(
288                     boundaryIndex + 9).getBytes();
289
290             InputStream JavaDoc input = req.getInputStream();
291
292             MultipartStream multi = new MultipartStream(input, boundary);
293             multi.setHeaderEncoding(headerEncoding);
294
295             boolean nextPart = multi.skipPreamble();
296             while (nextPart)
297             {
298                 Map JavaDoc headers = parseHeaders(multi.readHeaders());
299                 String JavaDoc fieldName = getFieldName(headers);
300                 if (fieldName != null)
301                 {
302                     String JavaDoc subContentType = getHeader(headers, CONTENT_TYPE);
303                     if (subContentType != null && subContentType
304                                                 .startsWith(MULTIPART_MIXED))
305                     {
306                         // Multiple files.
307
byte[] subBoundary =
308                             subContentType.substring(
309                                 subContentType
310                                 .indexOf("boundary=") + 9).getBytes();
311                         multi.setBoundary(subBoundary);
312                         boolean nextSubPart = multi.skipPreamble();
313                         while (nextSubPart)
314                         {
315                             headers = parseHeaders(multi.readHeaders());
316                             if (getFileName(headers) != null)
317                             {
318                                 FileItem item =
319                                         createItem(headers, false);
320                                 OutputStream JavaDoc os = item.getOutputStream();
321                                 try
322                                 {
323                                     multi.readBodyData(os);
324                                 }
325                                 finally
326                                 {
327                                     os.close();
328                                 }
329                                 items.add(item);
330                             }
331                             else
332                             {
333                                 // Ignore anything but files inside
334
// multipart/mixed.
335
multi.discardBodyData();
336                             }
337                             nextSubPart = multi.readBoundary();
338                         }
339                         multi.setBoundary(boundary);
340                     }
341                     else
342                     {
343                         if (getFileName(headers) != null)
344                         {
345                             // A single file.
346
FileItem item = createItem(headers, false);
347                             OutputStream JavaDoc os = item.getOutputStream();
348                             try
349                             {
350                                 multi.readBodyData(os);
351                             }
352                             finally
353                             {
354                                 os.close();
355                             }
356                             items.add(item);
357                         }
358                         else
359                         {
360                             // A form field.
361
FileItem item = createItem(headers, true);
362                             OutputStream JavaDoc os = item.getOutputStream();
363                             try
364                             {
365                                 multi.readBodyData(os);
366                             }
367                             finally
368                             {
369                                 os.close();
370                             }
371                             items.add(item);
372                         }
373                     }
374                 }
375                 else
376                 {
377                     // Skip this part.
378
multi.discardBodyData();
379                 }
380                 nextPart = multi.readBoundary();
381             }
382         }
383         catch (IOException JavaDoc e)
384         {
385             throw new FileUploadException(
386                 "Processing of " + MULTIPART_FORM_DATA
387                     + " request failed. " + e.getMessage());
388         }
389
390         return items;
391     }
392
393
394     // ------------------------------------------------------ Protected methods
395

396
397     /**
398      * Retrieves the file name from the <code>Content-disposition</code>
399      * header.
400      *
401      * @param headers A <code>Map</code> containing the HTTP request headers.
402      *
403      * @return The file name for the current <code>encapsulation</code>.
404      */

405     protected String JavaDoc getFileName(Map JavaDoc /* String, String */ headers)
406     {
407         String JavaDoc fileName = null;
408         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
409         if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
410         {
411             int start = cd.indexOf("filename=\"");
412             int end = cd.indexOf('"', start + 10);
413             if (start != -1 && end != -1)
414             {
415                 fileName = cd.substring(start + 10, end).trim();
416             }
417         }
418         return fileName;
419     }
420
421
422     /**
423      * Retrieves the field name from the <code>Content-disposition</code>
424      * header.
425      *
426      * @param headers A <code>Map</code> containing the HTTP request headers.
427      *
428      * @return The field name for the current <code>encapsulation</code>.
429      */

430     protected String JavaDoc getFieldName(Map JavaDoc /* String, String */ headers)
431     {
432         String JavaDoc fieldName = null;
433         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
434         if (cd != null && cd.startsWith(FORM_DATA))
435         {
436             int start = cd.indexOf("name=\"");
437             int end = cd.indexOf('"', start + 6);
438             if (start != -1 && end != -1)
439             {
440                 fieldName = cd.substring(start + 6, end);
441             }
442         }
443         return fieldName;
444     }
445
446
447     /**
448      * Creates a new {@link FileItem} instance.
449      *
450      * @param headers A <code>Map</code> containing the HTTP request
451      * headers.
452      * @param isFormField Whether or not this item is a form field, as
453      * opposed to a file.
454      *
455      * @return A newly created <code>FileItem</code> instance.
456      *
457      * @exception FileUploadException if an error occurs.
458      */

459     protected FileItem createItem(Map JavaDoc /* String, String */ headers,
460                                   boolean isFormField)
461         throws FileUploadException
462     {
463         return getFileItemFactory().createItem(getFieldName(headers),
464                 getHeader(headers, CONTENT_TYPE),
465                 isFormField,
466                 getFileName(headers));
467     }
468
469
470     /**
471      * <p> Parses the <code>header-part</code> and returns as key/value
472      * pairs.
473      *
474      * <p> If there are multiple headers of the same names, the name
475      * will map to a comma-separated list containing the values.
476      *
477      * @param headerPart The <code>header-part</code> of the current
478      * <code>encapsulation</code>.
479      *
480      * @return A <code>Map</code> containing the parsed HTTP request headers.
481      */

482     protected Map JavaDoc /* String, String */ parseHeaders(String JavaDoc headerPart)
483     {
484         Map JavaDoc headers = new HashMap JavaDoc();
485         char buffer[] = new char[MAX_HEADER_SIZE];
486         boolean done = false;
487         int j = 0;
488         int i;
489         String JavaDoc header, headerName, headerValue;
490         try
491         {
492             while (!done)
493             {
494                 i = 0;
495                 // Copy a single line of characters into the buffer,
496
// omitting trailing CRLF.
497
while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')
498                 {
499                     buffer[i++] = headerPart.charAt(j++);
500                 }
501                 header = new String JavaDoc(buffer, 0, i - 2);
502                 if (header.equals(""))
503                 {
504                     done = true;
505                 }
506                 else
507                 {
508                     if (header.indexOf(':') == -1)
509                     {
510                         // This header line is malformed, skip it.
511
continue;
512                     }
513                     headerName = header.substring(0, header.indexOf(':'))
514                         .trim().toLowerCase();
515                     headerValue =
516                         header.substring(header.indexOf(':') + 1).trim();
517                     if (getHeader(headers, headerName) != null)
518                     {
519                         // More that one heder of that name exists,
520
// append to the list.
521
headers.put(headerName,
522                                     getHeader(headers, headerName) + ','
523                                         + headerValue);
524                     }
525                     else
526                     {
527                         headers.put(headerName, headerValue);
528                     }
529                 }
530             }
531         }
532         catch (IndexOutOfBoundsException JavaDoc e)
533         {
534             // Headers were malformed. continue with all that was
535
// parsed.
536
}
537         return headers;
538     }
539
540
541     /**
542      * Returns the header with the specified name from the supplied map. The
543      * header lookup is case-insensitive.
544      *
545      * @param headers A <code>Map</code> containing the HTTP request headers.
546      * @param name The name of the header to return.
547      *
548      * @return The value of specified header, or a comma-separated list if
549      * there were multiple headers of that name.
550      */

551     protected final String JavaDoc getHeader(Map JavaDoc /* String, String */ headers,
552                                      String JavaDoc name)
553     {
554         return (String JavaDoc) headers.get(name.toLowerCase());
555     }
556
557
558     /**
559      * Thrown to indicate that the request is not a multipart request.
560      */

561     public static class InvalidContentTypeException
562         extends FileUploadException
563     {
564         /**
565          * Constructs a <code>InvalidContentTypeException</code> with no
566          * detail message.
567          */

568         public InvalidContentTypeException()
569         {
570             super();
571         }
572
573         /**
574          * Constructs an <code>InvalidContentTypeException</code> with
575          * the specified detail message.
576          *
577          * @param message The detail message.
578          */

579         public InvalidContentTypeException(String JavaDoc message)
580         {
581             super(message);
582         }
583     }
584
585
586     /**
587      * Thrown to indicate that the request size is not specified.
588      */

589     public static class UnknownSizeException
590         extends FileUploadException
591     {
592         /**
593          * Constructs a <code>UnknownSizeException</code> with no
594          * detail message.
595          */

596         public UnknownSizeException()
597         {
598             super();
599         }
600
601         /**
602          * Constructs an <code>UnknownSizeException</code> with
603          * the specified detail message.
604          *
605          * @param message The detail message.
606          */

607         public UnknownSizeException(String JavaDoc message)
608         {
609             super(message);
610         }
611     }
612
613
614     /**
615      * Thrown to indicate that the request size exceeds the configured maximum.
616      */

617     public static class SizeLimitExceededException
618         extends FileUploadException
619     {
620         /**
621          * Constructs a <code>SizeExceededException</code> with no
622          * detail message.
623          */

624         public SizeLimitExceededException()
625         {
626             super();
627         }
628
629         /**
630          * Constructs an <code>SizeExceededException</code> with
631          * the specified detail message.
632          *
633          * @param message The detail message.
634          */

635         public SizeLimitExceededException(String JavaDoc message)
636         {
637             super(message);
638         }
639     }
640
641 }
642
Popular Tags