KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sapia > soto > state > cocoon > util > CocoonFileUploadBase


1 package org.sapia.soto.state.cocoon.util;
2
3 import org.apache.cocoon.environment.http.HttpRequest;
4
5 import org.apache.commons.fileupload.FileItem;
6 import org.apache.commons.fileupload.FileItemFactory;
7 import org.apache.commons.fileupload.FileUploadException;
8 import org.apache.commons.fileupload.MultipartStream;
9
10 import java.io.IOException JavaDoc;
11 import java.io.InputStream JavaDoc;
12 import java.io.OutputStream JavaDoc;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.HashMap JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Map JavaDoc;
18
19
20 /**
21  * <p><b>The code from this class has beed copied from Jakarta's FileUploadBase
22  * class and adapted to Cocoon's HttpRequest class.</b></p>
23  *
24  * <p>High level API for processing file uploads.</p>
25  *
26  * <p>This class handles multiple files per single HTML widget, sent using
27  * <code>multipart/mixed</code> encoding type, as specified by
28  * <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
29  * #parseRequest(HttpRequest)} to acquire a list of {@link
30  * org.apache.commons.fileupload.FileItem}s associated with a given HTML
31  * widget.</p>
32  *
33  * <p>How the data for individual parts is stored is determined by the factory
34  * used to create them; a given part may be in memory, on disk, or somewhere
35  * else.</p>
36  *
37  * @author <a HREF="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
38  * @author <a HREF="mailto:dlr@collab.net">Daniel Rall</a>
39  * @author <a HREF="mailto:jvanzyl@apache.org">Jason van Zyl</a>
40  * @author <a HREF="mailto:jmcnally@collab.net">John McNally</a>
41  * @author <a HREF="mailto:martinc@apache.org">Martin Cooper</a>
42  * @author Sean C. Sullivan
43  * @author Yanick Duchesne (Sapia Open Source)
44  *
45  * <dl>
46  * <dt><b>Copyright:</b><dd>Copyright &#169; 2002-2004 <a HREF="http://www.sapia-oss.org">Sapia Open Source Software</a>. All Rights Reserved.</dd></dt>
47  * <dt><b>License:</b><dd>Read the license.txt file of the jar or visit the
48  * <a HREF="http://www.sapia-oss.org/license.html">license page</a> at the Sapia OSS web site</dd></dt>
49  * </dl>
50  */

51 public abstract class CocoonFileUploadBase {
52   // ----------------------------------------------------- Manifest constants
53

54   /**
55    * HTTP content type header name.
56    */

57   public static final String JavaDoc CONTENT_TYPE = "Content-type";
58
59   /**
60    * HTTP content disposition header name.
61    */

62   public static final String JavaDoc CONTENT_DISPOSITION = "Content-disposition";
63
64   /**
65    * Content-disposition value for form data.
66    */

67   public static final String JavaDoc FORM_DATA = "form-data";
68
69   /**
70    * Content-disposition value for file attachment.
71    */

72   public static final String JavaDoc ATTACHMENT = "attachment";
73
74   /**
75    * Part of HTTP content type header.
76    */

77   public static final String JavaDoc MULTIPART = "multipart/";
78
79   /**
80    * HTTP content type header for multipart forms.
81    */

82   public static final String JavaDoc MULTIPART_FORM_DATA = "multipart/form-data";
83
84   /**
85    * HTTP content type header for multiple uploads.
86    */

87   public static final String JavaDoc MULTIPART_MIXED = "multipart/mixed";
88
89   /**
90    * The maximum length of a single header line that will be parsed
91    * (1024 bytes).
92    */

93   public static final int MAX_HEADER_SIZE = 1024;
94
95   // ----------------------------------------------------------- Data members
96

97   /**
98    * The maximum size permitted for an uploaded file. A value of -1 indicates
99    * no maximum.
100    */

101   private long sizeMax = -1;
102
103   /**
104    * The content encoding to use when reading part headers.
105    */

106   private String JavaDoc headerEncoding;
107
108   // ---------------------------------------------------------- Class methods
109

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

119   public static final boolean isMultipartContent(HttpRequest req) {
120     String JavaDoc contentType = req.getHeader(CONTENT_TYPE);
121
122     if (contentType == null) {
123       return false;
124     }
125
126     if (contentType.startsWith(MULTIPART)) {
127       return true;
128     }
129
130     return false;
131   }
132
133   // ----------------------------------------------------- Property accessors
134

135   /**
136    * Returns the factory class used when creating file items.
137    *
138    * @return The factory class for new file items.
139    */

140   public abstract FileItemFactory getFileItemFactory();
141
142   /**
143    * Sets the factory class to use when creating file items.
144    *
145    * @param factory The factory class for new file items.
146    */

147   public abstract void setFileItemFactory(FileItemFactory factory);
148
149   /**
150    * Returns the maximum allowed upload size.
151    *
152    * @return The maximum allowed size, in bytes.
153    *
154    * @see #setSizeMax(long)
155    *
156    */

157   public long getSizeMax() {
158     return sizeMax;
159   }
160
161   /**
162    * Sets the maximum allowed upload size. If negative, there is no maximum.
163    *
164    * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
165    *
166    * @see #getSizeMax()
167    *
168    */

169   public void setSizeMax(long sizeMax) {
170     this.sizeMax = sizeMax;
171   }
172
173   /**
174    * Retrieves the character encoding used when reading the headers of an
175    * individual part. When not specified, or <code>null</code>, the platform
176    * default encoding is used.
177    *
178    * @return The encoding used to read part headers.
179    */

180   public String JavaDoc getHeaderEncoding() {
181     return headerEncoding;
182   }
183
184   /**
185    * Specifies the character encoding to be used when reading the headers of
186    * individual parts. When not specified, or <code>null</code>, the platform
187    * default encoding is used.
188    *
189    * @param encoding The encoding used to read part headers.
190    */

191   public void setHeaderEncoding(String JavaDoc encoding) {
192     headerEncoding = encoding;
193   }
194
195   // --------------------------------------------------------- Public methods
196

197   /**
198    * Processes an <a HREF="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
199    * compliant <code>multipart/form-data</code> stream. If files are stored
200    * on disk, the path is given by <code>getRepository()</code>.
201    *
202    * @param req The servlet request to be parsed.
203    *
204    * @return A list of <code>FileItem</code> instances parsed from the
205    * request, in the order that they were transmitted.
206    *
207    * @exception FileUploadException if there are problems reading/parsing
208    * the request or storing files.
209    */

210   public List JavaDoc /* FileItem */ parseRequest(HttpRequest req)
211     throws FileUploadException {
212     if (null == req) {
213       throw new NullPointerException JavaDoc("req parameter");
214     }
215
216     ArrayList JavaDoc items = new ArrayList JavaDoc();
217     String JavaDoc contentType = req.getHeader(CONTENT_TYPE);
218
219     if ((null == contentType) || (!contentType.startsWith(MULTIPART))) {
220       throw new InvalidContentTypeException("the request doesn't contain a " +
221         MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED +
222         " stream, content type header is " + contentType);
223     }
224
225     int requestSize = req.getContentLength();
226
227     if (requestSize == -1) {
228       throw new UnknownSizeException(
229         "the request was rejected because it's size is unknown");
230     }
231
232     if ((sizeMax >= 0) && (requestSize > sizeMax)) {
233       throw new SizeLimitExceededException("the request was rejected because " +
234         "it's size exceeds allowed range");
235     }
236
237     try {
238       int boundaryIndex = contentType.indexOf("boundary=");
239
240       if (boundaryIndex < 0) {
241         throw new FileUploadException("the request was rejected because " +
242           "no multipart boundary was found");
243       }
244
245       byte[] boundary = contentType.substring(boundaryIndex + 9)
246                                             .getBytes();
247
248       InputStream JavaDoc input = req.getInputStream();
249
250       MultipartStream multi = new MultipartStream(input, boundary);
251       multi.setHeaderEncoding(headerEncoding);
252
253       boolean nextPart = multi.skipPreamble();
254
255       while (nextPart) {
256         Map JavaDoc headers = parseHeaders(multi.readHeaders());
257         String JavaDoc fieldName = getFieldName(headers);
258
259         if (fieldName != null) {
260           String JavaDoc subContentType = getHeader(headers, CONTENT_TYPE);
261
262           if ((subContentType != null) &&
263                 subContentType.startsWith(MULTIPART_MIXED)) {
264             // Multiple files.
265
byte[] subBoundary = subContentType.substring(subContentType.indexOf(
266                   "boundary=") + 9).getBytes();
267             multi.setBoundary(subBoundary);
268
269             boolean nextSubPart = multi.skipPreamble();
270
271             while (nextSubPart) {
272               headers = parseHeaders(multi.readHeaders());
273
274               if (getFileName(headers) != null) {
275                 FileItem item = createItem(headers, false);
276                 OutputStream JavaDoc os = item.getOutputStream();
277
278                 try {
279                   multi.readBodyData(os);
280                 } finally {
281                   os.close();
282                 }
283
284                 items.add(item);
285               } else {
286                 // Ignore anything but files inside
287
// multipart/mixed.
288
multi.discardBodyData();
289               }
290
291               nextSubPart = multi.readBoundary();
292             }
293
294             multi.setBoundary(boundary);
295           } else {
296             FileItem item = createItem(headers, getFileName(headers) == null);
297             OutputStream JavaDoc os = item.getOutputStream();
298
299             try {
300               multi.readBodyData(os);
301             } finally {
302               os.close();
303             }
304
305             items.add(item);
306           }
307         } else {
308           // Skip this part.
309
multi.discardBodyData();
310         }
311
312         nextPart = multi.readBoundary();
313       }
314     } catch (IOException JavaDoc e) {
315       throw new FileUploadException("Processing of " + MULTIPART_FORM_DATA +
316         " request failed. " + e.getMessage());
317     }
318
319     return items;
320   }
321
322   // ------------------------------------------------------ Protected methods
323

324   /**
325    * Retrieves the file name from the <code>Content-disposition</code>
326    * header.
327    *
328    * @param headers A <code>Map</code> containing the HTTP request headers.
329    *
330    * @return The file name for the current <code>encapsulation</code>.
331    */

332   protected String JavaDoc getFileName(Map JavaDoc /* String, String */ headers) {
333     String JavaDoc fileName = null;
334     String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
335
336     if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT)) {
337       int start = cd.indexOf("filename=\"");
338       int end = cd.indexOf('"', start + 10);
339
340       if ((start != -1) && (end != -1)) {
341         fileName = cd.substring(start + 10, end).trim();
342       }
343     }
344
345     return fileName;
346   }
347
348   /**
349    * Retrieves the field name from the <code>Content-disposition</code>
350    * header.
351    *
352    * @param headers A <code>Map</code> containing the HTTP request headers.
353    *
354    * @return The field name for the current <code>encapsulation</code>.
355    */

356   protected String JavaDoc getFieldName(Map JavaDoc /* String, String */ headers) {
357     String JavaDoc fieldName = null;
358     String JavaDoc cd = getHeader(headers, CONTENT_DISPOSITION);
359
360     if ((cd != null) && cd.startsWith(FORM_DATA)) {
361       int start = cd.indexOf("name=\"");
362       int end = cd.indexOf('"', start + 6);
363
364       if ((start != -1) && (end != -1)) {
365         fieldName = cd.substring(start + 6, end);
366       }
367     }
368
369     return fieldName;
370   }
371
372   /**
373    * Creates a new {@link FileItem} instance.
374    *
375    * @param headers A <code>Map</code> containing the HTTP request
376    * headers.
377    * @param isFormField Whether or not this item is a form field, as
378    * opposed to a file.
379    *
380    * @return A newly created <code>FileItem</code> instance.
381    *
382    * @exception FileUploadException if an error occurs.
383    */

384   protected FileItem createItem(Map JavaDoc /* String, String */ headers,
385     boolean isFormField) throws FileUploadException {
386     return getFileItemFactory().createItem(getFieldName(headers),
387       getHeader(headers, CONTENT_TYPE), isFormField, getFileName(headers));
388   }
389
390   /**
391    * <p> Parses the <code>header-part</code> and returns as key/value
392    * pairs.
393    *
394    * <p> If there are multiple headers of the same names, the name
395    * will map to a comma-separated list containing the values.
396    *
397    * @param headerPart The <code>header-part</code> of the current
398    * <code>encapsulation</code>.
399    *
400    * @return A <code>Map</code> containing the parsed HTTP request headers.
401    */

402   protected Map JavaDoc /* String, String */ parseHeaders(String JavaDoc headerPart) {
403     Map JavaDoc headers = new HashMap JavaDoc();
404     char[] buffer = new char[MAX_HEADER_SIZE];
405     boolean done = false;
406     int j = 0;
407     int i;
408     String JavaDoc header;
409     String JavaDoc headerName;
410     String JavaDoc headerValue;
411
412     try {
413       while (!done) {
414         i = 0;
415
416         // Copy a single line of characters into the buffer,
417
// omitting trailing CRLF.
418
while ((i < 2) || (buffer[i - 2] != '\r') || (buffer[i - 1] != '\n')) {
419           buffer[i++] = headerPart.charAt(j++);
420         }
421
422         header = new String JavaDoc(buffer, 0, i - 2);
423
424         if (header.equals("")) {
425           done = true;
426         } else {
427           if (header.indexOf(':') == -1) {
428             // This header line is malformed, skip it.
429
continue;
430           }
431
432           headerName = header.substring(0, header.indexOf(':')).trim()
433                                .toLowerCase();
434           headerValue = header.substring(header.indexOf(':') + 1).trim();
435
436           if (getHeader(headers, headerName) != null) {
437             // More that one heder of that name exists,
438
// append to the list.
439
headers.put(headerName,
440               getHeader(headers, headerName) + ',' + headerValue);
441           } else {
442             headers.put(headerName, headerValue);
443           }
444         }
445       }
446     } catch (IndexOutOfBoundsException JavaDoc e) {
447       // Headers were malformed. continue with all that was
448
// parsed.
449
}
450
451     return headers;
452   }
453
454   /**
455    * Returns the header with the specified name from the supplied map. The
456    * header lookup is case-insensitive.
457    *
458    * @param headers A <code>Map</code> containing the HTTP request headers.
459    * @param name The name of the header to return.
460    *
461    * @return The value of specified header, or a comma-separated list if
462    * there were multiple headers of that name.
463    */

464   protected final String JavaDoc getHeader(Map JavaDoc /* String, String */ headers, String JavaDoc name) {
465     return (String JavaDoc) headers.get(name.toLowerCase());
466   }
467
468   /**
469    * Thrown to indicate that the request is not a multipart request.
470    */

471   public static class InvalidContentTypeException extends FileUploadException {
472     /**
473      * Constructs a <code>InvalidContentTypeException</code> with no
474      * detail message.
475      */

476     public InvalidContentTypeException() {
477       super();
478     }
479
480     /**
481      * Constructs an <code>InvalidContentTypeException</code> with
482      * the specified detail message.
483      *
484      * @param message The detail message.
485      */

486     public InvalidContentTypeException(String JavaDoc message) {
487       super(message);
488     }
489   }
490
491   /**
492    * Thrown to indicate that the request size is not specified.
493    */

494   public static class UnknownSizeException extends FileUploadException {
495     /**
496      * Constructs a <code>UnknownSizeException</code> with no
497      * detail message.
498      */

499     public UnknownSizeException() {
500       super();
501     }
502
503     /**
504      * Constructs an <code>UnknownSizeException</code> with
505      * the specified detail message.
506      *
507      * @param message The detail message.
508      */

509     public UnknownSizeException(String JavaDoc message) {
510       super(message);
511     }
512   }
513
514   /**
515    * Thrown to indicate that the request size exceeds the configured maximum.
516    */

517   public static class SizeLimitExceededException extends FileUploadException {
518     /**
519      * Constructs a <code>SizeExceededException</code> with no
520      * detail message.
521      */

522     public SizeLimitExceededException() {
523       super();
524     }
525
526     /**
527      * Constructs an <code>SizeExceededException</code> with
528      * the specified detail message.
529      *
530      * @param message The detail message.
531      */

532     public SizeLimitExceededException(String JavaDoc message) {
533       super(message);
534     }
535   }
536 }
537
Popular Tags