KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > myvietnam > mvncore > web > fileupload > FileUploadBase


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

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

53 public abstract class FileUploadBase {
54
55     // ---------------------------------------------------------- Class methods
56

57
58     /**
59      * <p>Utility method that determines whether the request contains multipart
60      * content.</p>
61      *
62      * <p><strong>NOTE:</strong>This method will be moved to the
63      * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
64      * Unfortunately, since this method is static, it is not possible to
65      * provide its replacement until this method is removed.</p>
66      *
67      * @param ctx The request context to be evaluated. Must be non-null.
68      *
69      * @return <code>true</code> if the request is multipart;
70      * <code>false</code> otherwise.
71      */

72     public static final boolean isMultipartContent(RequestContext ctx) {
73         String JavaDoc contentType = ctx.getContentType();
74         if (contentType == null) {
75             return false;
76         }
77         if (contentType.toLowerCase().startsWith(MULTIPART)) {
78             return true;
79         }
80         return false;
81     }
82
83
84     /**
85      * Utility method that determines whether the request contains multipart
86      * content.
87      *
88      * @param req The servlet request to be evaluated. Must be non-null.
89      *
90      * @return <code>true</code> if the request is multipart;
91      * <code>false</code> otherwise.
92      *
93      * @deprecated Use the method on <code>ServletFileUpload</code> instead.
94      */

95     public static final boolean isMultipartContent(HttpServletRequest JavaDoc req) {
96         if (!"post".equals(req.getMethod().toLowerCase())) {
97             return false;
98         }
99         String JavaDoc contentType = req.getContentType();
100         if (contentType == null) {
101             return false;
102         }
103         if (contentType.toLowerCase().startsWith(MULTIPART)) {
104             return true;
105         }
106         return false;
107     }
108
109
110     // ----------------------------------------------------- Manifest constants
111

112
113     /**
114      * HTTP content type header name.
115      */

116     public static final String JavaDoc CONTENT_TYPE = "Content-type";
117
118
119     /**
120      * HTTP content disposition header name.
121      */

122     public static final String JavaDoc CONTENT_DISPOSITION = "Content-disposition";
123
124
125     /**
126      * Content-disposition value for form data.
127      */

128     public static final String JavaDoc FORM_DATA = "form-data";
129
130
131     /**
132      * Content-disposition value for file attachment.
133      */

134     public static final String JavaDoc ATTACHMENT = "attachment";
135
136
137     /**
138      * Part of HTTP content type header.
139      */

140     public static final String JavaDoc MULTIPART = "multipart/";
141
142
143     /**
144      * HTTP content type header for multipart forms.
145      */

146     public static final String JavaDoc MULTIPART_FORM_DATA = "multipart/form-data";
147
148
149     /**
150      * HTTP content type header for multiple uploads.
151      */

152     public static final String JavaDoc MULTIPART_MIXED = "multipart/mixed";
153
154
155     /**
156      * The maximum length of a single header line that will be parsed
157      * (1024 bytes).
158      */

159     public static final int MAX_HEADER_SIZE = 1024;
160
161
162     // ----------------------------------------------------------- Data members
163

164
165     /**
166      * The maximum size permitted for an uploaded file. A value of -1 indicates
167      * no maximum.
168      */

169     private long sizeMax = -1;
170
171
172     /**
173      * The content encoding to use when reading part headers.
174      */

175     private String JavaDoc headerEncoding;
176
177
178     // ----------------------------------------------------- Property accessors
179

180
181     /**
182      * Returns the factory class used when creating file items.
183      *
184      * @return The factory class for new file items.
185      */

186     public abstract FileItemFactory getFileItemFactory();
187
188
189     /**
190      * Sets the factory class to use when creating file items.
191      *
192      * @param factory The factory class for new file items.
193      */

194     public abstract void setFileItemFactory(FileItemFactory factory);
195
196
197     /**
198      * Returns the maximum allowed upload size.
199      *
200      * @return The maximum allowed size, in bytes.
201      *
202      * @see #setSizeMax(long)
203      *
204      */

205     public long getSizeMax() {
206         return sizeMax;
207     }
208
209
210     /**
211      * Sets the maximum allowed upload size. If negative, there is no maximum.
212      *
213      * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
214      *
215      * @see #getSizeMax()
216      *
217      */

218     public void setSizeMax(long sizeMax) {
219         this.sizeMax = sizeMax;
220     }
221
222
223     /**
224      * Retrieves the character encoding used when reading the headers of an
225      * individual part. When not specified, or <code>null</code>, the request
226      * encoding is used. If that is also not specified, or <code>null</code>,
227      * the platform default encoding is used.
228      *
229      * @return The encoding used to read part headers.
230      */

231     public String JavaDoc getHeaderEncoding() {
232         return headerEncoding;
233     }
234
235
236     /**
237      * Specifies the character encoding to be used when reading the headers of
238      * individual part. When not specified, or <code>null</code>, the request
239      * encoding is used. If that is also not specified, or <code>null</code>,
240      * the platform default encoding is used.
241      *
242      * @param encoding The encoding used to read part headers.
243      */

244     public void setHeaderEncoding(String JavaDoc encoding) {
245         headerEncoding = encoding;
246     }
247
248
249     // --------------------------------------------------------- Public methods
250

251
252     /**
253      * Processes an <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
254      * compliant <code>multipart/form-data</code> stream.
255      *
256      * @param req The servlet request to be parsed.
257      *
258      * @return A list of <code>FileItem</code> instances parsed from the
259      * request, in the order that they were transmitted.
260      *
261      * @throws FileUploadException if there are problems reading/parsing
262      * the request or storing files.
263      *
264      * @deprecated Use the method in <code>ServletFileUpload</code> instead.
265      */

266     public List JavaDoc /* FileItem */ parseRequest(HttpServletRequest JavaDoc req)
267             throws FileUploadException {
268         return parseRequest(new ServletRequestContext(req));
269     }
270
271     /**
272      * Processes an <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
273      * compliant <code>multipart/form-data</code> stream.
274      *
275      * @param ctx The context for the request to be parsed.
276      *
277      * @return A list of <code>FileItem</code> instances parsed from the
278      * request, in the order that they were transmitted.
279      *
280      * @throws FileUploadException if there are problems reading/parsing
281      * the request or storing files.
282      */

283     public List JavaDoc /* FileItem */ parseRequest(RequestContext ctx)
284             throws FileUploadException {
285         if (ctx == null) {
286             throw new NullPointerException JavaDoc("ctx parameter");
287         }
288
289         ArrayList JavaDoc items = new ArrayList JavaDoc();
290         String JavaDoc contentType = ctx.getContentType();
291
292         if ((null == contentType)
293             || (!contentType.toLowerCase().startsWith(MULTIPART))) {
294             throw new InvalidContentTypeException(
295                 "the request doesn't contain a "
296                 + MULTIPART_FORM_DATA
297                 + " or "
298                 + MULTIPART_MIXED
299                 + " stream, content type header is "
300                 + contentType);
301         }
302         int requestSize = ctx.getContentLength();
303
304         if (requestSize == -1) {
305             throw new UnknownSizeException(
306                 "the request was rejected because its size is unknown");
307         }
308
309         if (sizeMax >= 0 && requestSize > sizeMax) {
310             throw new SizeLimitExceededException(
311                 "the request was rejected because its size (" + requestSize
312                 + ") exceeds the configured maximum (" + sizeMax + ")",
313                 requestSize, sizeMax);
314         }
315
316         String JavaDoc charEncoding = headerEncoding;
317         if (charEncoding == null) {
318             charEncoding = ctx.getCharacterEncoding();
319         }
320
321         try {
322             byte[] boundary = getBoundary(contentType);
323             if (boundary == null) {
324                 throw new FileUploadException(
325                         "the request was rejected because "
326                         + "no multipart boundary was found");
327             }
328
329             InputStream JavaDoc input = ctx.getInputStream();
330
331             MultipartStream multi = new MultipartStream(input, boundary);
332             multi.setHeaderEncoding(charEncoding);
333
334             boolean nextPart = multi.skipPreamble();
335             while (nextPart) {
336                 Map JavaDoc headers = parseHeaders(multi.readHeaders());
337                 String JavaDoc fieldName = getFieldName(headers);
338                 if (fieldName != null) {
339                     String JavaDoc subContentType = getHeader(headers, CONTENT_TYPE);
340                     if (subContentType != null && subContentType
341                         .toLowerCase().startsWith(MULTIPART_MIXED)) {
342                         // Multiple files.
343
byte[] subBoundary = getBoundary(subContentType);
344                         multi.setBoundary(subBoundary);
345                         boolean nextSubPart = multi.skipPreamble();
346                         while (nextSubPart) {
347                             headers = parseHeaders(multi.readHeaders());
348                             if (getFileName(headers) != null) {
349                                 FileItem item =
350                                         createItem(headers, false);
351                                 OutputStream JavaDoc os = item.getOutputStream();
352                                 try {
353                                     multi.readBodyData(os);
354                                 } finally {
355                                     os.close();
356                                 }
357                                 items.add(item);
358                             } else {
359                                 // Ignore anything but files inside
360
// multipart/mixed.
361
multi.discardBodyData();
362                             }
363                             nextSubPart = multi.readBoundary();
364                         }
365                         multi.setBoundary(boundary);
366                     } else {
367                         FileItem item = createItem(headers,
368                                 getFileName(headers) == null);
369                         OutputStream JavaDoc os = item.getOutputStream();
370                         try {
371                             multi.readBodyData(os);
372                         } finally {
373                             os.close();
374                         }
375                         items.add(item);
376                     }
377                 } else {
378                     // Skip this part.
379
multi.discardBodyData();
380                 }
381                 nextPart = multi.readBoundary();
382             }
383         } catch (IOException JavaDoc e) {
384             throw new FileUploadException(
385                 "Processing of " + MULTIPART_FORM_DATA
386                     + " request failed. " + e.getMessage());
387         }
388
389         return items;
390     }
391
392
393     // ------------------------------------------------------ Protected methods
394

395
396     /**
397      * Retrieves the boundary from the <code>Content-type</code> header.
398      *
399      * @param contentType The value of the content type header from which to
400      * extract the boundary value.
401      *
402      * @return The boundary, as a byte array.
403      */

404     protected byte[] getBoundary(String JavaDoc contentType) {
405         ParameterParser parser = new ParameterParser();
406         parser.setLowerCaseNames(true);
407         // Parameter parser can handle null input
408
Map JavaDoc params = parser.parse(contentType, ';');
409         String JavaDoc boundaryStr = (String JavaDoc) params.get("boundary");
410
411         if (boundaryStr == null) {
412             return null;
413         }
414         byte[] boundary;
415         try {
416             boundary = boundaryStr.getBytes("ISO-8859-1");
417         } catch (UnsupportedEncodingException JavaDoc e) {
418             boundary = boundaryStr.getBytes();
419         }
420         return boundary;
421     }
422
423
424     /**
425      * Retrieves the file name from the <code>Content-disposition</code>
426      * header.
427      *
428      * @param headers A <code>Map</code> containing the HTTP request headers.
429      *
430      * @return The file name for the current <code>encapsulation</code>.
431      */

432     protected String JavaDoc getFileName(Map JavaDoc /* String, String */ headers) {
433         String JavaDoc fileName = null;
434         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
435         if (cd != null) {
436             String JavaDoc cd_lower = cd.toLowerCase(); // minhnn: fix lower filename (thread=3485) (Thanks Serobit)
437
if (cd_lower.startsWith(FORM_DATA) || cd_lower.startsWith(ATTACHMENT)) {
438                 ParameterParser parser = new ParameterParser();
439                 parser.setLowerCaseNames(true);
440                 // Parameter parser can handle null input
441
Map JavaDoc params = parser.parse(cd, ';');
442                 if (params.containsKey("filename")) {
443                     fileName = (String JavaDoc) params.get("filename");
444                     if (fileName != null) {
445                         fileName = fileName.trim();
446                     } else {
447                         // Even if there is no value, the parameter is present,
448
// so we return an empty file name rather than no file
449
// name.
450
fileName = "";
451                     }
452                 }
453             }
454         }
455         return fileName;
456     }
457
458
459     /**
460      * Retrieves the field name from the <code>Content-disposition</code>
461      * header.
462      *
463      * @param headers A <code>Map</code> containing the HTTP request headers.
464      *
465      * @return The field name for the current <code>encapsulation</code>.
466      */

467     protected String JavaDoc getFieldName(Map JavaDoc /* String, String */ headers) {
468         String JavaDoc fieldName = null;
469         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
470         if (cd != null && cd.toLowerCase().startsWith(FORM_DATA)) {
471
472             ParameterParser parser = new ParameterParser();
473             parser.setLowerCaseNames(true);
474             // Parameter parser can handle null input
475
Map JavaDoc params = parser.parse(cd, ';');
476             fieldName = (String JavaDoc) params.get("name");
477             if (fieldName != null) {
478                 fieldName = fieldName.trim();
479             }
480         }
481         return fieldName;
482     }
483
484
485     /**
486      * Creates a new {@link FileItem} instance.
487      *
488      * @param headers A <code>Map</code> containing the HTTP request
489      * headers.
490      * @param isFormField Whether or not this item is a form field, as
491      * opposed to a file.
492      *
493      * @return A newly created <code>FileItem</code> instance.
494      *
495      * @throws FileUploadException if an error occurs.
496      */

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

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

576     protected final String JavaDoc getHeader(Map JavaDoc /* String, String */ headers,
577                                      String JavaDoc name) {
578         return (String JavaDoc) headers.get(name.toLowerCase());
579     }
580
581
582     /**
583      * Thrown to indicate that the request is not a multipart request.
584      */

585     public static class InvalidContentTypeException
586         extends FileUploadException {
587         /**
588          * Constructs a <code>InvalidContentTypeException</code> with no
589          * detail message.
590          */

591         public InvalidContentTypeException() {
592             super();
593         }
594
595         /**
596          * Constructs an <code>InvalidContentTypeException</code> with
597          * the specified detail message.
598          *
599          * @param message The detail message.
600          */

601         public InvalidContentTypeException(String JavaDoc message) {
602             super(message);
603         }
604     }
605
606
607     /**
608      * Thrown to indicate that the request size is not specified.
609      */

610     public static class UnknownSizeException
611         extends FileUploadException {
612         /**
613          * Constructs a <code>UnknownSizeException</code> with no
614          * detail message.
615          */

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

626         public UnknownSizeException(String JavaDoc message) {
627             super(message);
628         }
629     }
630
631
632     /**
633      * Thrown to indicate that the request size exceeds the configured maximum.
634      */

635     public static class SizeLimitExceededException
636         extends FileUploadException {
637         /**
638          * The actual size of the request.
639          */

640         private long actual;
641
642         /**
643          * The maximum permitted size of the request.
644          */

645         private long permitted;
646
647         /**
648          * Constructs a <code>SizeExceededException</code> with no
649          * detail message.
650          */

651         public SizeLimitExceededException() {
652             super();
653         }
654
655         /**
656          * Constructs a <code>SizeExceededException</code> with
657          * the specified detail message.
658          *
659          * @param message The detail message.
660          */

661         public SizeLimitExceededException(String JavaDoc message) {
662             super(message);
663         }
664
665         /**
666          * Constructs a <code>SizeExceededException</code> with
667          * the specified detail message, and actual and permitted sizes.
668          *
669          * @param message The detail message.
670          * @param actual The actual request size.
671          * @param permitted The maximum permitted request size.
672          */

673         public SizeLimitExceededException(String JavaDoc message, long actual,
674                 long permitted) {
675             super(message);
676             this.actual = actual;
677             this.permitted = permitted;
678         }
679
680         /**
681          * Retrieves the actual size of the request.
682          *
683          * @return The actual size of the request.
684          */

685         public long getActualSize() {
686             return actual;
687         }
688
689         /**
690          * Retrieves the permitted size of the request.
691          *
692          * @return The permitted size of the request.
693          */

694         public long getPermittedSize() {
695             return permitted;
696         }
697     }
698
699 }
700
Popular Tags