001// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
002// All rights reserved.  Use of this class is limited.
003// Please see the LICENSE for more information.
004
005package com.oreilly.servlet;
006
007import java.io.*;
008import java.util.*;
009import javax.servlet.*;
010import javax.servlet.http.*;
011
012import com.oreilly.servlet.multipart.MultipartParser;
013import com.oreilly.servlet.multipart.Part;
014import com.oreilly.servlet.multipart.FilePart;
015import com.oreilly.servlet.multipart.ParamPart;
016import com.oreilly.servlet.multipart.FileRenamePolicy;
017
018/** 
019 * A utility class to handle <code>multipart/form-data</code> requests,
020 * the kind of requests that support file uploads.  This class emulates the 
021 * interface of <code>HttpServletRequest</code>, making it familiar to use. 
022 * It uses a "push" model where any incoming files are read and saved directly
023 * to disk in the constructor. If you wish to have more flexibility, e.g. 
024 * write the files to a database, use the "pull" model 
025 * <code>MultipartParser</code> instead.
026 * <p>
027 * This class can receive arbitrarily large files (up to an artificial limit 
028 * you can set), and fairly efficiently too.  
029 * It cannot handle nested data (multipart content within multipart content).
030 * It <b>can</b> now with the latest release handle internationalized content
031 * (such as non Latin-1 filenames).
032 * <p>
033 * To avoid collisions and have fine control over file placement, there's a
034 * constructor variety that takes a pluggable FileRenamePolicy implementation.
035 * A particular policy can choose to rename or change the location of the file
036 * before it's written.
037 * <p>
038 * See the included upload.war for an example of how to use this class.
039 * <p>
040 * The full file upload specification is contained in experimental RFC 1867,
041 * available at <a href="http://www.ietf.org/rfc/rfc1867.txt">
042 * http://www.ietf.org/rfc/rfc1867.txt</a>.
043 *
044 * @see MultipartParser
045 * 
046 * @author Jason Hunter
047 * @author Geoff Soutter
048 * @version 1.11, 2002/11/01, combine query string params in param list<br>
049 * @version 1.10, 2002/05/27, added access to the original file names<br>
050 * @version 1.9, 2002/04/30, added support for file renaming, thanks to
051 *                           Changshin Lee<br>
052 * @version 1.8, 2002/04/30, added support for internationalization, thanks to
053 *                           Changshin Lee<br>
054 * @version 1.7, 2001/02/07, made fields protected to increase user flexibility<br>
055 * @version 1.6, 2000/07/21, redid internals to use MultipartParser,
056 *                           thanks to Geoff Soutter<br>
057 * @version 1.5, 2000/02/04, added auto MacBinary decoding for IE on Mac<br>
058 * @version 1.4, 2000/01/05, added getParameterValues(),
059 *                           WebSphere 2.x getContentType() workaround,
060 *                           stopped writing empty "unknown" file<br>
061 * @version 1.3, 1999/12/28, IE4 on Win98 lastIndexOf("boundary=")
062 * workaround<br>
063 * @version 1.2, 1999/12/20, IE4 on Mac readNextPart() workaround<br>
064 * @version 1.1, 1999/01/15, JSDK readLine() bug workaround<br>
065 * @version 1.0, 1998/09/18<br>
066 */
067public class MultipartRequest {
068
069  private static final int DEFAULT_MAX_POST_SIZE = 1024 * 1024;  // 1 Meg
070
071  protected Hashtable parameters = new Hashtable();  // name - Vector of values
072  protected Hashtable files = new Hashtable();       // name - UploadedFile
073
074  /**
075   * Constructs a new MultipartRequest to handle the specified request, 
076   * saving any uploaded files to the given directory, and limiting the 
077   * upload size to 1 Megabyte.  If the content is too large, an
078   * IOException is thrown.  This constructor actually parses the 
079   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
080   * problem reading or parsing the request.
081   *
082   * @param request the servlet request.
083   * @param saveDirectory the directory in which to save any uploaded files.
084   * @exception IOException if the uploaded content is larger than 1 Megabyte
085   * or there's a problem reading or parsing the request.
086   */
087  public MultipartRequest(HttpServletRequest request,
088                          String saveDirectory) throws IOException {
089    this(request, saveDirectory, DEFAULT_MAX_POST_SIZE);
090  }
091
092  /**
093   * Constructs a new MultipartRequest to handle the specified request, 
094   * saving any uploaded files to the given directory, and limiting the 
095   * upload size to the specified length.  If the content is too large, an 
096   * IOException is thrown.  This constructor actually parses the 
097   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
098   * problem reading or parsing the request.
099   *
100   * @param request the servlet request.
101   * @param saveDirectory the directory in which to save any uploaded files.
102   * @param maxPostSize the maximum size of the POST content.
103   * @exception IOException if the uploaded content is larger than 
104   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
105   */
106  public MultipartRequest(HttpServletRequest request,
107                          String saveDirectory,
108                          int maxPostSize) throws IOException {
109    this(request, saveDirectory, maxPostSize, null, null);
110  }
111
112  /**
113   * Constructs a new MultipartRequest to handle the specified request, 
114   * saving any uploaded files to the given directory, and limiting the 
115   * upload size to the specified length.  If the content is too large, an 
116   * IOException is thrown.  This constructor actually parses the 
117   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
118   * problem reading or parsing the request.
119   *
120   * @param request the servlet request.
121   * @param saveDirectory the directory in which to save any uploaded files.
122   * @param encoding the encoding of the response, such as ISO-8859-1
123   * @exception IOException if the uploaded content is larger than 
124   * 1 Megabyte or there's a problem reading or parsing the request.
125   */
126  public MultipartRequest(HttpServletRequest request,
127                          String saveDirectory,
128                          String encoding) throws IOException {
129    this(request, saveDirectory, DEFAULT_MAX_POST_SIZE, encoding, null);
130  }
131
132  /**
133   * Constructs a new MultipartRequest to handle the specified request, 
134   * saving any uploaded files to the given directory, and limiting the 
135   * upload size to the specified length.  If the content is too large, an 
136   * IOException is thrown.  This constructor actually parses the 
137   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
138   * problem reading or parsing the request.
139   *
140   * @param request the servlet request.
141   * @param saveDirectory the directory in which to save any uploaded files.
142   * @param maxPostSize the maximum size of the POST content.
143   * @param encoding the encoding of the response, such as ISO-8859-1
144   * @exception IOException if the uploaded content is larger than 
145   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
146   */
147  public MultipartRequest(HttpServletRequest request,
148                          String saveDirectory,
149                          int maxPostSize,
150                          FileRenamePolicy policy) throws IOException {
151    this(request, saveDirectory, maxPostSize, null, policy);
152  }
153
154  /**
155   * Constructs a new MultipartRequest to handle the specified request, 
156   * saving any uploaded files to the given directory, and limiting the 
157   * upload size to the specified length.  If the content is too large, an 
158   * IOException is thrown.  This constructor actually parses the 
159   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
160   * problem reading or parsing the request.
161   *
162   * @param request the servlet request.
163   * @param saveDirectory the directory in which to save any uploaded files.
164   * @param maxPostSize the maximum size of the POST content.
165   * @param encoding the encoding of the response, such as ISO-8859-1
166   * @exception IOException if the uploaded content is larger than 
167   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
168   */
169  public MultipartRequest(HttpServletRequest request,
170                          String saveDirectory,
171                          int maxPostSize,
172                          String encoding) throws IOException {
173    this(request, saveDirectory, maxPostSize, encoding, null);
174  }
175
176  /**
177   * Constructs a new MultipartRequest to handle the specified request, 
178   * saving any uploaded files to the given directory, and limiting the 
179   * upload size to the specified length.  If the content is too large, an 
180   * IOException is thrown.  This constructor actually parses the 
181   * <tt>multipart/form-data</tt> and throws an IOException if there's any 
182   * problem reading or parsing the request.
183   *
184   * To avoid file collisions, this constructor takes an implementation of the
185   * FileRenamePolicy interface to allow a pluggable rename policy.
186   *
187   * @param request the servlet request.
188   * @param saveDirectory the directory in which to save any uploaded files.
189   * @param maxPostSize the maximum size of the POST content.
190   * @param encoding the encoding of the response, such as ISO-8859-1
191   * @param policy a pluggable file rename policy
192   * @exception IOException if the uploaded content is larger than 
193   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
194   */
195  public MultipartRequest(HttpServletRequest request,
196                          String saveDirectory,
197                          int maxPostSize,
198                          String encoding,
199                          FileRenamePolicy policy) throws IOException {
200    // Sanity check values
201    if (request == null)
202      throw new IllegalArgumentException("request cannot be null");
203    if (saveDirectory == null)
204      throw new IllegalArgumentException("saveDirectory cannot be null");
205    if (maxPostSize <= 0) {
206      throw new IllegalArgumentException("maxPostSize must be positive");
207    }
208
209    // Save the dir
210    File dir = new File(saveDirectory);
211
212    // Check saveDirectory is truly a directory
213    if (!dir.isDirectory())
214      throw new IllegalArgumentException("Not a directory: " + saveDirectory);
215
216    // Check saveDirectory is writable
217    if (!dir.canWrite())
218      throw new IllegalArgumentException("Not writable: " + saveDirectory);
219
220    // Parse the incoming multipart, storing files in the dir provided, 
221    // and populate the meta objects which describe what we found
222    MultipartParser parser =
223      new MultipartParser(request, maxPostSize, true, true, encoding);
224
225    // Some people like to fetch query string parameters from
226    // MultipartRequest, so here we make that possible.  Thanks to 
227    // Ben Johnson, ben.johnson@merrillcorp.com, for the idea.
228    if (request.getQueryString() != null) {
229      // Let HttpUtils create a name->String[] structure
230      Hashtable queryParameters =
231        HttpUtils.parseQueryString(request.getQueryString());
232      // For our own use, name it a name->Vector structure
233      Enumeration queryParameterNames = queryParameters.keys();
234      while (queryParameterNames.hasMoreElements()) {
235        Object paramName = queryParameterNames.nextElement();
236        String[] values = (String[])queryParameters.get(paramName);
237        Vector newValues = new Vector();
238        for (int i = 0; i < values.length; i++) {
239          newValues.add(values[i]);
240        }
241        parameters.put(paramName, newValues);
242      }
243    }
244
245    Part part;
246    while ((part = parser.readNextPart()) != null) {
247      String name = part.getName();
248      if (part.isParam()) {
249        // It's a parameter part, add it to the vector of values
250        ParamPart paramPart = (ParamPart) part;
251        String value = paramPart.getStringValue();
252        Vector existingValues = (Vector)parameters.get(name);
253        if (existingValues == null) {
254          existingValues = new Vector();
255          parameters.put(name, existingValues);
256        }
257        existingValues.addElement(value);
258      }
259      else if (part.isFile()) {
260        // It's a file part
261        FilePart filePart = (FilePart) part;
262        String fileName = filePart.getFileName();
263        if (fileName != null) {
264          filePart.setRenamePolicy(policy);  // null policy is OK
265          // The part actually contained a file
266          filePart.writeTo(dir);
267          files.put(name, new UploadedFile(dir.toString(),
268                                           filePart.getFileName(),
269                                           fileName,
270                                           filePart.getContentType()));
271        }
272        else { 
273          // The field did not contain a file
274          files.put(name, new UploadedFile(null, null, null, null));
275        }
276      }
277    }
278  }
279
280  /**
281   * Constructor with an old signature, kept for backward compatibility.
282   * Without this constructor, a servlet compiled against a previous version 
283   * of this class (pre 1.4) would have to be recompiled to link with this 
284   * version.  This constructor supports the linking via the old signature.
285   * Callers must simply be careful to pass in an HttpServletRequest.
286   * 
287   */
288  public MultipartRequest(ServletRequest request,
289                          String saveDirectory) throws IOException {
290    this((HttpServletRequest)request, saveDirectory);
291  }
292
293  /**
294   * Constructor with an old signature, kept for backward compatibility.
295   * Without this constructor, a servlet compiled against a previous version 
296   * of this class (pre 1.4) would have to be recompiled to link with this 
297   * version.  This constructor supports the linking via the old signature.
298   * Callers must simply be careful to pass in an HttpServletRequest.
299   * 
300   */
301  public MultipartRequest(ServletRequest request,
302                          String saveDirectory,
303                          int maxPostSize) throws IOException {
304    this((HttpServletRequest)request, saveDirectory, maxPostSize);
305  }
306
307  /**
308   * Returns the names of all the parameters as an Enumeration of 
309   * Strings.  It returns an empty Enumeration if there are no parameters.
310   *
311   * @return the names of all the parameters as an Enumeration of Strings.
312   */
313  public Enumeration getParameterNames() {
314    return parameters.keys();
315  }
316
317  /**
318   * Returns the names of all the uploaded files as an Enumeration of 
319   * Strings.  It returns an empty Enumeration if there are no uploaded 
320   * files.  Each file name is the name specified by the form, not by 
321   * the user.
322   *
323   * @return the names of all the uploaded files as an Enumeration of Strings.
324   */
325  public Enumeration getFileNames() {
326    return files.keys();
327  }
328
329  /**
330   * Returns the value of the named parameter as a String, or null if 
331   * the parameter was not sent or was sent without a value.  The value 
332   * is guaranteed to be in its normal, decoded form.  If the parameter 
333   * has multiple values, only the last one is returned (for backward 
334   * compatibility).  For parameters with multiple values, it's possible
335   * the last "value" may be null.
336   *
337   * @param name the parameter name.
338   * @return the parameter value.
339   */
340  public String getParameter(String name) {
341    try {
342      Vector values = (Vector)parameters.get(name);
343      if (values == null || values.size() == 0) {
344        return null;
345      }
346      String value = (String)values.elementAt(values.size() - 1);
347      return value;
348    }
349    catch (Exception e) {
350      return null;
351    }
352  }
353
354  /**
355   * Returns the values of the named parameter as a String array, or null if 
356   * the parameter was not sent.  The array has one entry for each parameter 
357   * field sent.  If any field was sent without a value that entry is stored 
358   * in the array as a null.  The values are guaranteed to be in their 
359   * normal, decoded form.  A single value is returned as a one-element array.
360   *
361   * @param name the parameter name.
362   * @return the parameter values.
363   */
364  public String[] getParameterValues(String name) {
365    try {
366      Vector values = (Vector)parameters.get(name);
367      if (values == null || values.size() == 0) {
368        return null;
369      }
370      String[] valuesArray = new String[values.size()];
371      values.copyInto(valuesArray);
372      return valuesArray;
373    }
374    catch (Exception e) {
375      return null;
376    }
377  }
378
379  /**
380   * Returns the filesystem name of the specified file, or null if the 
381   * file was not included in the upload.  A filesystem name is the name 
382   * specified by the user.  It is also the name under which the file is 
383   * actually saved.
384   *
385   * @param name the file name.
386   * @return the filesystem name of the file.
387   */
388  public String getFilesystemName(String name) {
389    try {
390      UploadedFile file = (UploadedFile)files.get(name);
391      return file.getFilesystemName();  // may be null
392    }
393    catch (Exception e) {
394      return null;
395    }
396  }
397
398  /**
399   * Returns the original filesystem name of the specified file (before any
400   * renaming policy was applied), or null if the file was not included in 
401   * the upload.  A filesystem name is the name specified by the user.
402   *
403   * @param name the file name.
404   * @return the original file name of the file.
405   */
406  public String getOriginalFileName(String name) {
407    try {
408      UploadedFile file = (UploadedFile)files.get(name);
409      return file.getOriginalFileName();  // may be null
410    }
411    catch (Exception e) {
412      return null;
413    }
414  }
415
416  /**
417   * Returns the content type of the specified file (as supplied by the 
418   * client browser), or null if the file was not included in the upload.
419   *
420   * @param name the file name.
421   * @return the content type of the file.
422   */
423  public String getContentType(String name) {
424    try {
425      UploadedFile file = (UploadedFile)files.get(name);
426      return file.getContentType();  // may be null
427    }
428    catch (Exception e) {
429      return null;
430    }
431  }
432
433  /**
434   * Returns a File object for the specified file saved on the server's 
435   * filesystem, or null if the file was not included in the upload.
436   *
437   * @param name the file name.
438   * @return a File object for the named file.
439   */
440  public File getFile(String name) {
441    try {
442      UploadedFile file = (UploadedFile)files.get(name);
443      return file.getFile();  // may be null
444    }
445    catch (Exception e) {
446      return null;
447    }
448  }
449}
450
451
452// A class to hold information about an uploaded file.
453//
454class UploadedFile {
455
456  private String dir;
457  private String filename;
458  private String original;
459  private String type;
460
461  UploadedFile(String dir, String filename, String original, String type) {
462    this.dir = dir;
463    this.filename = filename;
464    this.original = original;
465    this.type = type;
466  }
467
468  public String getContentType() {
469    return type;
470  }
471
472  public String getFilesystemName() {
473    return filename;
474  }
475
476  public String getOriginalFileName() {
477    return original;
478  }
479
480  public File getFile() {
481    if (dir == null || filename == null) {
482      return null;
483    }
484    else {
485      return new File(dir + File.separator + filename);
486    }
487  }
488}
489