KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > jcorporate > expresso > core > misc > upload > Uploader


1 /* ====================================================================
2  * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
3  *
4  * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  * notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  * notice, this list of conditions and the following disclaimer in
15  * the documentation and/or other materials provided with the
16  * distribution.
17  *
18  * 3. The end-user documentation included with the redistribution,
19  * if any, must include the following acknowledgment:
20  * "This product includes software developed by Jcorporate Ltd.
21  * (http://www.jcorporate.com/)."
22  * Alternately, this acknowledgment may appear in the software itself,
23  * if and wherever such third-party acknowledgments normally appear.
24  *
25  * 4. "Jcorporate" and product names such as "Expresso" must
26  * not be used to endorse or promote products derived from this
27  * software without prior written permission. For written permission,
28  * please contact info@jcorporate.com.
29  *
30  * 5. Products derived from this software may not be called "Expresso",
31  * or other Jcorporate product names; nor may "Expresso" or other
32  * Jcorporate product names appear in their name, without prior
33  * written permission of Jcorporate Ltd.
34  *
35  * 6. No product derived from this software may compete in the same
36  * market space, i.e. framework, without prior written permission
37  * of Jcorporate Ltd. For written permission, please contact
38  * partners@jcorporate.com.
39  *
40  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
41  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
42  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
43  * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
44  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
45  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
46  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
47  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
49  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
50  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
51  * SUCH DAMAGE.
52  * ====================================================================
53  *
54  * This software consists of voluntary contributions made by many
55  * individuals on behalf of the Jcorporate Ltd. Contributions back
56  * to the project(s) are encouraged when you make modifications.
57  * Please send them to support@jcorporate.com. For more information
58  * on Jcorporate Ltd. and its products, please see
59  * <http://www.jcorporate.com/>.
60  *
61  * Portions of this software are based upon other open source
62  * products and are subject to their respective licenses.
63  */

64
65 package com.jcorporate.expresso.core.misc.upload;
66
67 import com.jcorporate.expresso.core.controller.ControllerException;
68 import com.jcorporate.expresso.core.db.DBException;
69 import com.jcorporate.expresso.core.misc.StringUtil;
70 import com.jcorporate.expresso.services.dbobj.Setup;
71 import org.apache.log4j.Logger;
72 import org.apache.struts.action.ActionMapping;
73 import org.apache.struts.action.ActionServlet;
74 import org.apache.struts.upload.MultipartRequestHandler;
75
76 import javax.servlet.ServletException JavaDoc;
77 import javax.servlet.http.HttpServletRequest JavaDoc;
78 import java.io.IOException JavaDoc;
79 import java.io.InputStream JavaDoc;
80 import java.io.OutputStream JavaDoc;
81 import java.util.Enumeration JavaDoc;
82 import java.util.Hashtable JavaDoc;
83
84
85 /**
86  * <p> Files will be stored in temporary disk storage
87  * <p/>
88  * <p>This implementation of {@link Uploader} handles multiple
89  * files per single html widget, sent using multipar/mixed encoding
90  * type, as specified by RFC 1867. Use {@link
91  * org.apache.turbine.util.ParameterParser#getFileItems(String)} to
92  * acquire an array of {@link
93  * org.apache.turbine.util.upload.FileItem}s associated with given
94  * html widget.
95  *
96  * @author <a HREF="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
97  */

98 public class Uploader
99         implements MultipartRequestHandler {
100
101     /**
102      * A maximum lenght of a single header line that will be
103      * parsed. (1024 bytes).
104      */

105     public static final int MAX_HEADER_SIZE = 1024;
106
107     /**
108      * Stores parsed headers as key - value pairs.
109      */

110     private Hashtable JavaDoc headers;
111     private DefaultParameterParser myParser = null;
112     private ActionMapping myMapping = null;
113     private ActionServlet myActionServlet = null;
114     private static Logger log = Logger.getLogger(Uploader.class);
115
116     public Uploader() {
117
118     }
119
120     /**
121      * Processes an <a HREF="http://rf.cx/rfc1867.html">RFC
122      * 1867</a> compliant <code>multipart/form-data</code> stream.
123      *
124      * @param req The servlet request to be parsed.
125      * @param params The ParameterParser instance to insert form
126      * fields into.
127      * @param path The location where the files should be stored.
128      * @throws ControllerException If there are problems reading/parsing
129      * the request or storing files.
130      */

131     public void parseRequest(HttpServletRequest JavaDoc req, ParameterParser params,
132                              String JavaDoc path)
133             throws ControllerException {
134         log.debug("Parse request begins");
135
136         String JavaDoc contentType = req.getHeader("Content-type");
137
138         if (!contentType.startsWith("multipart/form-data")) {
139             throw new ControllerException("Request doesn't contain multipart/form-data stream");
140         }
141
142         int requestSize = req.getContentLength();
143
144         if (requestSize == -1) {
145             throw new ControllerException("Request was rejected because it's size is unknown");
146         }
147         try {
148             byte[] boundary = contentType.substring(contentType.indexOf("boundary=") + 9).getBytes();
149             InputStream JavaDoc input = req.getInputStream();
150             MultipartStream multi = new MultipartStream(input, boundary);
151             boolean nextPart = multi.skipPreamble();
152
153             while (nextPart) {
154                 parseHeaders(multi.readHeaders());
155
156                 String JavaDoc fieldName = getFieldName();
157
158                 if (fieldName != null) {
159                     String JavaDoc subContentType = getHeader("Content-type");
160
161                     if (subContentType != null &&
162                             subContentType.startsWith("multipart/mixed")) {
163
164                         // Multiple files.
165
byte[] subBoundary = subContentType.substring(subContentType.indexOf("boundary=") + 9).getBytes();
166                         multi.setBoundary(subBoundary);
167
168                         boolean nextSubPart = multi.skipPreamble();
169
170                         while (nextSubPart) {
171                             parseHeaders(multi.readHeaders());
172
173                             if (getFileName() != null) {
174                                 FileItem item = createItem(path, requestSize,
175                                         true);
176                                 OutputStream JavaDoc ops = item.getOutputStream();
177                                 multi.readBodyData(ops);
178                                 ops.close();
179                                 params.append(getFieldName(), item);
180                             } else {
181
182                                 // Ignore anything but files inside
183
// multipart/mixed.
184
multi.discardBodyData();
185                             }
186
187                             nextSubPart = multi.readBoundary();
188                         }
189
190                         multi.setBoundary(boundary);
191                     } else {
192                         if (getFileName() != null) {
193
194                             // A single file.
195
FileItem item = createItem(path, requestSize, true);
196                             OutputStream JavaDoc ops = item.getOutputStream();
197                             multi.readBodyData(ops);
198                             ops.close();
199                             params.append(getFieldName(), item);
200                             log.debug("Read a file " + getFileName());
201                         } else {
202
203                             // A form field.
204

205                             FileItem item = createItem(path, requestSize,
206                                     false);
207                             OutputStream JavaDoc ops = item.getOutputStream();
208                             multi.readBodyData(ops);
209                             ops.close();
210
211                             String JavaDoc fieldData = new String JavaDoc(item.get());
212                             params.append(getFieldName(), fieldData);
213                             log.debug("Read a Field:" + getFieldName() +
214                                     ", value:" + fieldData);
215                         }
216                     }
217                 } else {
218
219                     // Skip this part.
220
multi.discardBodyData();
221                 }
222
223                 nextPart = multi.readBoundary();
224             }
225         } catch (IOException JavaDoc e) {
226             log.error("I/O Exception parsing upload", e);
227             throw new ControllerException("Processing of multipart/form-data request failed",
228                     e);
229         }
230
231         log.debug("Finished parsing");
232     }
233
234     /**
235      * <p> Retrieves field name from 'Content-disposition' header.
236      *
237      * @return A String with the field name for the current
238      * <code>encapsulation</code>.
239      */

240     protected String JavaDoc getFieldName() {
241         String JavaDoc cd = getHeader("Content-disposition");
242
243         if (cd == null || !cd.startsWith("form-data")) {
244             return null;
245         }
246
247         int start = cd.indexOf("name=\"");
248         int end = cd.indexOf('"', start + 6);
249
250         if (start == -1 || end == -1) {
251             return null;
252         }
253
254         return cd.substring(start + 6, end);
255     }
256
257     /**
258      * <p> Retrieves file name from 'Content-disposition' header.
259      *
260      * @return A String with the file name for the current
261      * <code>encapsulation</code>.
262      */

263     protected String JavaDoc getFileName() {
264         String JavaDoc cd = getHeader("Content-disposition");
265
266         if (log.isDebugEnabled()) {
267             log.debug("Disposition says " + cd);
268         }
269         if (!cd.startsWith("form-data") && !cd.startsWith("attachment")) {
270             return null;
271         }
272
273         int start = cd.indexOf("filename=\"");
274
275         /* Find the next quote */
276         int end = cd.indexOf('"', start + 10);
277
278         if (start == -1 || end == -1 || ((start + 10) == end)) {
279             return null;
280         }
281
282         String JavaDoc str = cd.substring(start + 10, end).trim();
283
284         if (str.length() == 0) {
285             return null;
286         } else {
287             if (log.isDebugEnabled()) {
288                 log.debug("Got a filename '" + str + "'");
289             }
290
291             return str;
292         }
293     }
294
295     /**
296      * <p> Creates a new instance of a FileItem.
297      *
298      * @param path The path for the FileItem.
299      * @param requestSize The size of the request.
300      * @return A newly created <code>FileItem</code>.
301      */

302     protected FileItem createItem(String JavaDoc path, int requestSize,
303                                   boolean storeAsFile) {
304         return FileItem.newInstance(path, getFileName(),
305                 getHeader("Content-type"), requestSize,
306                 storeAsFile);
307     }
308
309     /**
310      * <p> Parses the <code>header-part</code> and stores as key -
311      * value pairs.
312      * <p/>
313      * <p> If there are multiple headers of the same names, the name
314      * will map to a comma-separated list containing the values.
315      *
316      * @param headerPart The <code>header-part</code> of the current
317      * <code>encapsulation</code>.
318      */

319     protected void parseHeaders(String JavaDoc headerPart) {
320         if (headers == null) {
321             headers = new Hashtable JavaDoc();
322         } else {
323             headers.clear();
324         }
325
326         char[] buffer = new char[MAX_HEADER_SIZE];
327         boolean done = false;
328         int j = 0;
329         int i;
330         String JavaDoc header;
331         String JavaDoc headerName;
332         String JavaDoc headerValue;
333
334         try {
335             while (!done) {
336                 i = 0;
337
338                 // Copy a single line of characters into the buffer,
339
// omitting trailing CRLF.
340
while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n') {
341                     buffer[i++] = headerPart.charAt(j++);
342                 }
343
344                 header = new String JavaDoc(buffer, 0, i - 2);
345
346                 if (header.equals("")) {
347                     done = true;
348                 } else {
349                     if (header.indexOf(':') == -1) {
350
351                         // This header line is malformed, skip it.
352
continue;
353                     }
354
355                     headerName = header.substring(0, header.indexOf(':')).trim().toLowerCase();
356                     headerValue = header.substring(header.indexOf(':') + 1).trim();
357
358                     if (headers.get(headerName) != null) {
359
360                         // More that one heder of that name exists,
361
// append to the list.
362
headers.put(headerName,
363                                 (String JavaDoc) headers.get(headerName) + "," +
364                                 headerValue);
365                     } else {
366                         headers.put(headerName, headerValue);
367                     }
368                 }
369             }
370         } catch (IndexOutOfBoundsException JavaDoc e) {
371
372             // Headers were malformed. continue with all that was
373
// parsed.
374
}
375     }
376
377     /**
378      * <p> Returns a header with specified name.
379      *
380      * @param name The name of the header to fetch.
381      * @return The value of specified header, or a comma-separated
382      * list if there were multiple headers of that name.
383      */

384     protected String JavaDoc getHeader(String JavaDoc name) {
385         return (String JavaDoc) headers.get(name.toLowerCase());
386     }
387     /*** Below here are the methods required by the Struts MultipartRequestHandler interface */
388     /**
389      * Convienience method to set a reference to a working
390      * ActionServlet instance.
391      */

392     public void setServlet(ActionServlet servlet) {
393         myActionServlet = servlet;
394     }
395
396     /**
397      * Convienience method to set a reference to a working
398      * ActionMapping instance.
399      */

400     public void setMapping(ActionMapping mapping) {
401         myMapping = mapping;
402     }
403
404     /**
405      * Get the ActionServlet instance
406      */

407     public ActionServlet getServlet() {
408         return myActionServlet;
409     }
410
411     /**
412      * Get the ActionMapping instance for this request
413      */

414     public ActionMapping getMapping() {
415         return myMapping;
416     }
417
418     /**
419      * After constructed, this is the first method called on
420      * by ActionServlet. Use this method for all your
421      * data-parsing of the ServletInputStream in the request
422      *
423      * @throws ServletException thrown if something goes wrong
424      */

425     public void handleRequest(HttpServletRequest JavaDoc request)
426             throws ServletException JavaDoc {
427         myParser = new DefaultParameterParser();
428
429         String JavaDoc tempDir = null;
430
431         try {
432             tempDir = Setup.getValueRequired("default", "TempDir");
433         } catch (DBException de) {
434             log.error(de);
435             throw new ServletException JavaDoc("Unable to get temp dir:" +
436                     de.getMessage());
437         }
438         try {
439             if (log.isDebugEnabled()) {
440                 log.debug("About to parse request - tempDir is " + tempDir);
441                 log.debug("Username in request is '" +
442                         StringUtil.notNull((String JavaDoc) request.getAttribute("UserName")));
443             }
444
445             parseRequest(request, myParser, tempDir);
446         } catch (ControllerException ce) {
447             log.error(ce);
448             throw new ServletException JavaDoc(ce.getMessage());
449         }
450     }
451
452     /**
453      * This method is called on to retrieve all the text
454      * input elements of the request.
455      *
456      * @return A Hashtable where the keys and values are the names and values of the request input parameters
457      */

458     public Hashtable JavaDoc getTextElements() {
459         Hashtable JavaDoc textElements = new Hashtable JavaDoc();
460         String JavaDoc oneKey = null;
461
462         for (Enumeration JavaDoc ee = myParser.keys(); ee.hasMoreElements();) {
463             oneKey = (String JavaDoc) ee.nextElement();
464
465             if (!myParser.hasFileItem(oneKey)) {
466                 textElements.put(oneKey, StringUtil.notNull(myParser.get(oneKey)));
467             }
468         }
469
470         return textElements;
471     }
472
473     /**
474      * This method is called on to retrieve all the FormFile
475      * input elements of the request.
476      *
477      * @return A Hashtable where the keys are the input names of the files and the values are FormFile objects
478      * @see org.apache.struts.upload.FormFile
479      */

480     public Hashtable JavaDoc getFileElements() {
481         Hashtable JavaDoc fileElements = new Hashtable JavaDoc();
482         String JavaDoc oneKey = null;
483
484         for (Enumeration JavaDoc ee = myParser.keys(); ee.hasMoreElements();) {
485             oneKey = (String JavaDoc) ee.nextElement();
486
487             if (myParser.hasFileItem(oneKey)) {
488                 fileElements.put(oneKey, myParser.getFileItem(oneKey));
489             }
490         }
491
492         return fileElements;
493     }
494
495     /**
496      * This method returns all elements of a multipart request.
497      *
498      * @return A Hashtable where the keys are input names and values are either Strings or FormFiles
499      */

500     public Hashtable JavaDoc getAllElements() {
501         if (myParser == null) {
502             throw new IllegalArgumentException JavaDoc("Parser not set");
503         }
504
505         Hashtable JavaDoc allElements = new Hashtable JavaDoc();
506         String JavaDoc oneKey = null;
507
508         for (Enumeration JavaDoc ee = myParser.keys(); ee.hasMoreElements();) {
509             oneKey = (String JavaDoc) ee.nextElement();
510
511             Object JavaDoc o = myParser.get(oneKey);
512
513             if (o != null) {
514                 allElements.put(oneKey, o);
515             }
516         }
517
518         return allElements;
519     }
520
521     /**
522      * This method is called on when there's some sort of problem
523      * and the form post needs to be rolled back. Providers
524      * should remove any FormFiles used to hold information
525      * by setting them to null and also physically delete
526      * them if the implementation calls for writing directly
527      * to disk.
528      * NOTE: Currently implemented but not automatically
529      * supported, ActionForm implementors must call rollback()
530      * manually for rolling back file uploads.
531      */

532     public void rollback() {
533
534         /* Not supported with this Uloader class */
535     }
536
537     /**
538      * This method is called on when a successful form post
539      * has been made. Some implementations will use this
540      * to destroy temporary files or write to a database
541      * or something of that nature
542      */

543     public void finish() {
544
545         /* Not used here */
546     }
547
548 }
Popular Tags