Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/jcorporate/expresso/core/misc/upload/Uploader.java


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;
77  import javax.servlet.http.HttpServletRequest;
78  import java.io.IOException;
79  import java.io.InputStream;
80  import java.io.OutputStream;
81  import java.util.Enumeration;
82  import java.util.Hashtable;
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 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 req, ParameterParser params,
132                              String path)
133             throws ControllerException {
134         log.debug("Parse request begins");
135 
136         String 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 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 fieldName = getFieldName();
157 
158                 if (fieldName != null) {
159                     String 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 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 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 ops = item.getOutputStream();
208                             multi.readBodyData(ops);
209                             ops.close();
210 
211                             String fieldData = new String(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 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 getFieldName() {
241         String 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 getFileName() {
264         String 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 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 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 headerPart) {
320         if (headers == null) {
321             headers = new Hashtable();
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 header;
331         String headerName;
332         String 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(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) headers.get(headerName) + "," +
364                                 headerValue);
365                     } else {
366                         headers.put(headerName, headerValue);
367                     }
368                 }
369             }
370         } catch (IndexOutOfBoundsException 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 getHeader(String name) {
385         return (String) 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 request)
426             throws ServletException {
427         myParser = new DefaultParameterParser();
428 
429         String tempDir = null;
430 
431         try {
432             tempDir = Setup.getValueRequired("default", "TempDir");
433         } catch (DBException de) {
434             log.error(de);
435             throw new ServletException("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) request.getAttribute("UserName")));
443             }
444 
445             parseRequest(request, myParser, tempDir);
446         } catch (ControllerException ce) {
447             log.error(ce);
448             throw new ServletException(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 getTextElements() {
459         Hashtable textElements = new Hashtable();
460         String oneKey = null;
461 
462         for (Enumeration ee = myParser.keys(); ee.hasMoreElements();) {
463             oneKey = (String) 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 getFileElements() {
481         Hashtable fileElements = new Hashtable();
482         String oneKey = null;
483 
484         for (Enumeration ee = myParser.keys(); ee.hasMoreElements();) {
485             oneKey = (String) 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 getAllElements() {
501         if (myParser == null) {
502             throw new IllegalArgumentException("Parser not set");
503         }
504 
505         Hashtable allElements = new Hashtable();
506         String oneKey = null;
507 
508         for (Enumeration ee = myParser.keys(); ee.hasMoreElements();) {
509             oneKey = (String) ee.nextElement();
510 
511             Object 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 }