KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > jforum > util > legacy > commons > fileupload > FileUploadBase


1 /*
2  * Copyright 2001-2004 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.jforum.util.legacy.commons.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
27 import javax.servlet.http.HttpServletRequest JavaDoc;
28
29 import net.jforum.util.legacy.commons.fileupload.servlet.ServletRequestContext;
30
31 /**
32  * <p>High level API for processing file uploads.</p>
33  *
34  * <p>This class handles multiple files per single HTML widget, sent using
35  * <code>multipart/mixed</code> encoding type, as specified by
36  * <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
37  * #parseRequest(HttpServletRequest)} to acquire a list of {@link
38  * org.apache.commons.fileupload.FileItem}s associated with a given HTML
39  * widget.</p>
40  *
41  * <p>How the data for individual parts is stored is determined by the factory
42  * used to create them; a given part may be in memory, on disk, or somewhere
43  * else.</p>
44  *
45  * @author <a HREF="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
46  * @author <a HREF="mailto:dlr@collab.net">Daniel Rall</a>
47  * @author <a HREF="mailto:jvanzyl@apache.org">Jason van Zyl</a>
48  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
49  * @author <a HREF="mailto:martinc@apache.org">Martin Cooper</a>
50  * @author Sean C. Sullivan
51  *
52  * @version $Id: FileUploadBase.java,v 1.3 2005/07/26 03:05:02 rafaelsteil Exp $
53  */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

219     public void setSizeMax(long sizeMax) {
220         this.sizeMax = sizeMax;
221     }
222
223
224     /**
225      * Retrieves the character encoding used when reading the headers of an
226      * individual part. When not specified, or <code>null</code>, the platform
227      * 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 parts. When not specified, or <code>null</code>, the platform
239      * default encoding is used.
240      *
241      * @param encoding The encoding used to read part headers.
242      */

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

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

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

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

388
389     /**
390      * Retrieves the boundary from the <code>Content-type</code> header.
391      *
392      * @param contentType The value of the content type header from which to
393      * extract the boundary value.
394      *
395      * @return The boundary, as a byte array.
396      */

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

425     protected String JavaDoc getFileName(Map JavaDoc /* String, String */ headers) {
426         String JavaDoc fileName = null;
427         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
428         if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT)) {
429             ParameterParser parser = new ParameterParser();
430             parser.setLowerCaseNames(true);
431             // Parameter parser can handle null input
432
Map JavaDoc params = parser.parse(cd, ';');
433             if (params.containsKey("filename")) {
434                 fileName = (String JavaDoc) params.get("filename");
435                 if (fileName != null) {
436                     fileName = fileName.trim();
437                 } else {
438                     // Even if there is no value, the parameter is present, so
439
// we return an empty file name rather than no file name.
440
fileName = "";
441                 }
442             }
443         }
444         return fileName;
445     }
446
447
448     /**
449      * Retrieves the field name from the <code>Content-disposition</code>
450      * header.
451      *
452      * @param headers A <code>Map</code> containing the HTTP request headers.
453      *
454      * @return The field name for the current <code>encapsulation</code>.
455      */

456     protected String JavaDoc getFieldName(Map JavaDoc /* String, String */ headers) {
457         String JavaDoc fieldName = null;
458         String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
459         if (cd != null && cd.startsWith(FORM_DATA)) {
460
461             ParameterParser parser = new ParameterParser();
462             parser.setLowerCaseNames(true);
463             // Parameter parser can handle null input
464
Map JavaDoc params = parser.parse(cd, ';');
465             fieldName = (String JavaDoc) params.get("name");
466             if (fieldName != null) {
467                 fieldName = fieldName.trim();
468             }
469         }
470         return fieldName;
471     }
472
473
474     /**
475      * Creates a new {@link FileItem} instance.
476      *
477      * @param headers A <code>Map</code> containing the HTTP request
478      * headers.
479      * @param isFormField Whether or not this item is a form field, as
480      * opposed to a file.
481      *
482      * @return A newly created <code>FileItem</code> instance.
483      *
484      * @exception FileUploadException if an error occurs.
485      */

486     protected FileItem createItem(Map JavaDoc headers,
487                                   boolean isFormField) {
488         return getFileItemFactory().createItem(getFieldName(headers),
489                 getHeader(headers, CONTENT_TYPE),
490                 isFormField,
491                 getFileName(headers));
492     }
493
494
495     /**
496      * <p> Parses the <code>header-part</code> and returns as key/value
497      * pairs.
498      *
499      * <p> If there are multiple headers of the same names, the name
500      * will map to a comma-separated list containing the values.
501      *
502      * @param headerPart The <code>header-part</code> of the current
503      * <code>encapsulation</code>.
504      *
505      * @return A <code>Map</code> containing the parsed HTTP request headers.
506      */

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

564     protected final String JavaDoc getHeader(Map JavaDoc /* String, String */ headers,
565                                      String JavaDoc name) {
566         return (String JavaDoc) headers.get(name.toLowerCase());
567     }
568
569
570     /**
571      * Thrown to indicate that the request is not a multipart request.
572      */

573     public static class InvalidContentTypeException
574         extends FileUploadException {
575         /**
576          * Constructs a <code>InvalidContentTypeException</code> with no
577          * detail message.
578          */

579         public InvalidContentTypeException() {
580             super();
581         }
582
583         /**
584          * Constructs an <code>InvalidContentTypeException</code> with
585          * the specified detail message.
586          *
587          * @param message The detail message.
588          */

589         public InvalidContentTypeException(String JavaDoc message) {
590             super(message);
591         }
592     }
593
594
595     /**
596      * Thrown to indicate that the request size is not specified.
597      */

598     public static class UnknownSizeException
599         extends FileUploadException {
600         /**
601          * Constructs a <code>UnknownSizeException</code> with no
602          * detail message.
603          */

604         public UnknownSizeException() {
605             super();
606         }
607
608         /**
609          * Constructs an <code>UnknownSizeException</code> with
610          * the specified detail message.
611          *
612          * @param message The detail message.
613          */

614         public UnknownSizeException(String JavaDoc message) {
615             super(message);
616         }
617     }
618
619
620     /**
621      * Thrown to indicate that the request size exceeds the configured maximum.
622      */

623     public static class SizeLimitExceededException
624         extends FileUploadException {
625         /**
626          * Constructs a <code>SizeExceededException</code> with no
627          * detail message.
628          */

629         public SizeLimitExceededException() {
630             super();
631         }
632
633         /**
634          * Constructs an <code>SizeExceededException</code> with
635          * the specified detail message.
636          *
637          * @param message The detail message.
638          */

639         public SizeLimitExceededException(String JavaDoc message) {
640             super(message);
641         }
642     }
643
644 }
645
Popular Tags