Package com.oreilly.servlet

Source Code of com.oreilly.servlet.UploadedFile

// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
// All rights reserved.  Use of this class is limited.
// Please see the LICENSE for more information.

package com.oreilly.servlet;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import java.util.Enumeration;
import java.util.Map;

import java.security.Principal;

import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.Part;
import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.ParamPart;
import com.oreilly.servlet.multipart.FileRenamePolicy;

/**
* A utility class to handle <code>multipart/form-data</code> requests,
* the kind of requests that support file uploads.  This class emulates the
* interface of <code>HttpServletRequest</code>, making it familiar to use.
* It uses a "push" model where any incoming files are read and saved directly
* to disk in the constructor. If you wish to have more flexibility, e.g.
* write the files to a database, use the "pull" model
* <code>MultipartParser</code> instead.
* <p>
* This class can receive arbitrarily large files (up to an artificial limit
* you can set), and fairly efficiently too.
* It cannot handle nested data (multipart content within multipart content).
* It <b>can</b> now with the latest release handle internationalized content
* (such as non Latin-1 filenames).
* <p>
* To avoid collisions and have fine control over file placement, there's a
* constructor variety that takes a pluggable FileRenamePolicy implementation.
* A particular policy can choose to rename or change the location of the file
* before it's written.
* <p>
* See the included upload.war for an example of how to use this class.
* <p>
* The full file upload specification is contained in experimental RFC 1867,
* available at <a href="http://www.ietf.org/rfc/rfc1867.txt">
* http://www.ietf.org/rfc/rfc1867.txt</a>.
*
* @see MultipartParser
*
* @author Jason Hunter
* @author Geoff Soutter
* @version 1.11, 2002/11/01, combine query string params in param list<br>
* @version 1.10, 2002/05/27, added access to the original file names<br>
* @version 1.9, 2002/04/30, added support for file renaming, thanks to
*                           Changshin Lee<br>
* @version 1.8, 2002/04/30, added support for internationalization, thanks to
*                           Changshin Lee<br>
* @version 1.7, 2001/02/07, made fields protected to increase user flexibility<br>
* @version 1.6, 2000/07/21, redid internals to use MultipartParser,
*                           thanks to Geoff Soutter<br>
* @version 1.5, 2000/02/04, added auto MacBinary decoding for IE on Mac<br>
* @version 1.4, 2000/01/05, added getParameterValues(),
*                           WebSphere 2.x getContentType() workaround,
*                           stopped writing empty "unknown" file<br>
* @version 1.3, 1999/12/28, IE4 on Win98 lastIndexOf("boundary=")
* workaround<br>
* @version 1.2, 1999/12/20, IE4 on Mac readNextPart() workaround<br>
* @version 1.1, 1999/01/15, JSDK readLine() bug workaround<br>
* @version 1.0, 1998/09/18<br>
*/
public class MultipartRequest implements HttpServletRequest {

  private static final int DEFAULT_MAX_POST_SIZE = 1024 * 1024// 1 Meg

  protected Hashtable parameters = new Hashtable()// name - Vector of values
  protected Hashtable files = new Hashtable();       // name - UploadedFile
  protected Cookie[] cookies;
  protected Locale requestlocale;
  protected String sessionid;
  protected String encoding;
  protected String servletpath;
  protected String requesturi;
  protected String remoteuser;
  protected String querystr;
  protected String ctxpath;
  protected String trnpath;
  protected String pathinfo;
  protected String formmethod;
  protected String contenttype;
  protected int contentlength;
  protected StringBuffer requesturl;
  protected String savedir;
  protected boolean bRequestedSessionIdFromURL;
  protected boolean bRequestedSessionIdFromCookie;
  protected boolean bRequestedSessionIdValid;

  /**
   * Constructs a new MultipartRequest to handle the specified request,
   * saving any uploaded files to the given directory, and limiting the
   * upload size to 1 Megabyte.  If the content is too large, an
   * IOException is thrown.  This constructor actually parses the
   * <tt>multipart/form-data</tt> and throws an IOException if there's any
   * problem reading or parsing the request.
   *
   * @param request the servlet request.
   * @param saveDirectory the directory in which to save any uploaded files.
   * @exception IOException if the uploaded content is larger than 1 Megabyte
   * or there's a problem reading or parsing the request.
   */
  public MultipartRequest(HttpServletRequest request,
                          String saveDirectory) throws IOException {
    this(request, saveDirectory, DEFAULT_MAX_POST_SIZE);
  }

  /**
   * Constructs a new MultipartRequest to handle the specified request,
   * saving any uploaded files to the given directory, and limiting the
   * upload size to the specified length.  If the content is too large, an
   * IOException is thrown.  This constructor actually parses the
   * <tt>multipart/form-data</tt> and throws an IOException if there's any
   * problem reading or parsing the request.
   *
   * @param request the servlet request.
   * @param saveDirectory the directory in which to save any uploaded files.
   * @param maxPostSize the maximum size of the POST content.
   * @exception IOException if the uploaded content is larger than
   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
   */
  public MultipartRequest(HttpServletRequest request,
                          String saveDirectory,
                          int maxPostSize) throws IOException {
    this(request, saveDirectory, maxPostSize, null, null);
  }

  /**
   * Constructs a new MultipartRequest to handle the specified request,
   * saving any uploaded files to the given directory, and limiting the
   * upload size to the specified length.  If the content is too large, an
   * IOException is thrown.  This constructor actually parses the
   * <tt>multipart/form-data</tt> and throws an IOException if there's any
   * problem reading or parsing the request.
   *
   * @param request the servlet request.
   * @param saveDirectory the directory in which to save any uploaded files.
   * @param encoding the encoding of the response, such as ISO-8859-1
   * @exception IOException if the uploaded content is larger than
   * 1 Megabyte or there's a problem reading or parsing the request.
   */
  public MultipartRequest(HttpServletRequest request,
                          String saveDirectory,
                          String encoding) throws IOException {
    this(request, saveDirectory, DEFAULT_MAX_POST_SIZE, encoding, null);
  }

  /**
   * Constructs a new MultipartRequest to handle the specified request,
   * saving any uploaded files to the given directory, and limiting the
   * upload size to the specified length.  If the content is too large, an
   * IOException is thrown.  This constructor actually parses the
   * <tt>multipart/form-data</tt> and throws an IOException if there's any
   * problem reading or parsing the request.
   *
   * @param request the servlet request.
   * @param saveDirectory the directory in which to save any uploaded files.
   * @param maxPostSize the maximum size of the POST content.
   * @param encoding the encoding of the response, such as ISO-8859-1
   * @exception IOException if the uploaded content is larger than
   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
   */
  public MultipartRequest(HttpServletRequest request,
                          String saveDirectory,
                          int maxPostSize,
                          FileRenamePolicy policy) throws IOException {
    this(request, saveDirectory, maxPostSize, null, policy);
  }

  /**
   * Constructs a new MultipartRequest to handle the specified request,
   * saving any uploaded files to the given directory, and limiting the
   * upload size to the specified length.  If the content is too large, an
   * IOException is thrown.  This constructor actually parses the
   * <tt>multipart/form-data</tt> and throws an IOException if there's any
   * problem reading or parsing the request.
   *
   * @param request the servlet request.
   * @param saveDirectory the directory in which to save any uploaded files.
   * @param maxPostSize the maximum size of the POST content.
   * @param encoding the encoding of the response, such as ISO-8859-1
   * @exception IOException if the uploaded content is larger than
   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
   */
  public MultipartRequest(HttpServletRequest request,
                          String saveDirectory,
                          int maxPostSize,
                          String encoding) throws IOException {
    this(request, saveDirectory, maxPostSize, encoding, null);
  }

  /**
   * Constructs a new MultipartRequest to handle the specified request,
   * saving any uploaded files to the given directory, and limiting the
   * upload size to the specified length.  If the content is too large, an
   * IOException is thrown.  This constructor actually parses the
   * <tt>multipart/form-data</tt> and throws an IOException if there's any
   * problem reading or parsing the request.
   *
   * To avoid file collisions, this constructor takes an implementation of the
   * FileRenamePolicy interface to allow a pluggable rename policy.
   *
   * @param request the servlet request.
   * @param saveDirectory the directory in which to save any uploaded files.
   * @param maxPostSize the maximum size of the POST content.
   * @param encoding the encoding of the response, such as ISO-8859-1
   * @param policy a pluggable file rename policy
   * @exception IOException if the uploaded content is larger than
   * <tt>maxPostSize</tt> or there's a problem reading or parsing the request.
   */
  public MultipartRequest(HttpServletRequest request,
                          String saveDirectory,
                          int maxPostSize,
                          String encoding,
                          FileRenamePolicy policy) throws IOException {
    // Sanity check values
    if (request == null)
      throw new IllegalArgumentException("request cannot be null");
    if (saveDirectory == null)
      throw new IllegalArgumentException("saveDirectory cannot be null");
    if (maxPostSize <= 0) {
      throw new IllegalArgumentException("maxPostSize must be positive");
    }

    requestlocale = request.getLocale();
    encoding = request.getCharacterEncoding();
    sessionid = request.getRequestedSessionId();
    requesturl = request.getRequestURL();
    requesturi = request.getRequestURI();
    remoteuser = request.getRemoteUser();
    querystr = request.getQueryString();
    ctxpath = request.getContextPath();
    trnpath = request.getPathTranslated();
    pathinfo = request.getPathInfo();
    servletpath = request.getServletPath();
    formmethod = request.getMethod();
    cookies = request.getCookies();

    // Save the dir
    File dir = new File(saveDirectory);

    // Check saveDirectory is truly a directory
    if (!dir.isDirectory())
      throw new IllegalArgumentException("Not a directory: " + saveDirectory);

    // Check saveDirectory is writable
    if (!dir.canWrite())
      throw new IllegalArgumentException("Not writable: " + saveDirectory);

    savedir = saveDirectory;

    // Parse the incoming multipart, storing files in the dir provided,
    // and populate the meta objects which describe what we found
    MultipartParser parser = new MultipartParser(request, maxPostSize, true, true, encoding);

    // Some people like to fetch query string parameters from
    // MultipartRequest, so here we make that possible.  Thanks to
    // Ben Johnson, ben.johnson@merrillcorp.com, for the idea.
    if (request.getQueryString() != null) {
      // Let HttpUtils create a name->String[] structure
      Hashtable queryParameters =
        HttpUtils.parseQueryString(request.getQueryString());
      // For our own use, name it a name->Vector structure
      Enumeration queryParameterNames = queryParameters.keys();
      while (queryParameterNames.hasMoreElements()) {
        Object paramName = queryParameterNames.nextElement();
        String[] values = (String[])queryParameters.get(paramName);
        Vector newValues = new Vector();
        for (int i = 0; i < values.length; i++) {
          newValues.add(values[i]);
        }
        parameters.put(paramName, newValues);
      }
    }

    Part part;
    while ((part = parser.readNextPart()) != null) {
      String name = part.getName();
      if (part.isParam()) {
        // It's a parameter part, add it to the vector of values
        ParamPart paramPart = (ParamPart) part;
        String value = paramPart.getStringValue();
        Vector existingValues = (Vector)parameters.get(name);
        if (existingValues == null) {
          existingValues = new Vector();
          parameters.put(name, existingValues);
        }
        existingValues.addElement(value);
      }
      else if (part.isFile()) {
        // It's a file part
        FilePart filePart = (FilePart) part;
        String fileName = filePart.getFileName();
        if (fileName != null) {
          filePart.setRenamePolicy(policy)// null policy is OK
          // The part actually contained a file
          filePart.writeTo(dir);
          files.put(name, new UploadedFile(dir.toString(),
                                           filePart.getFileName(),
                                           fileName,
                                           filePart.getContentType()));
        }
        else {
          // The field did not contain a file
          files.put(name, new UploadedFile(null, null, null, null));
        }
      }
    }
    bRequestedSessionIdFromURL = request.isRequestedSessionIdFromURL();
    bRequestedSessionIdFromCookie = request.isRequestedSessionIdFromCookie();
    bRequestedSessionIdValid = request.isRequestedSessionIdValid();
  }

  /**
   * Constructor with an old signature, kept for backward compatibility.
   * Without this constructor, a servlet compiled against a previous version
   * of this class (pre 1.4) would have to be recompiled to link with this
   * version.  This constructor supports the linking via the old signature.
   * Callers must simply be careful to pass in an HttpServletRequest.
   *
   */
  public MultipartRequest(ServletRequest request,
                          String saveDirectory) throws IOException {
    this((HttpServletRequest)request, saveDirectory);
  }

  /**
   * Constructor with an old signature, kept for backward compatibility.
   * Without this constructor, a servlet compiled against a previous version
   * of this class (pre 1.4) would have to be recompiled to link with this
   * version.  This constructor supports the linking via the old signature.
   * Callers must simply be careful to pass in an HttpServletRequest.
   *
   */
  public MultipartRequest(ServletRequest request,
                          String saveDirectory,
                          int maxPostSize) throws IOException {
    this((HttpServletRequest)request, saveDirectory, maxPostSize);
  }

  public Cookie[] getCookies() {
    return cookies;
  }

  public Locale getLocale() {
    return requestlocale;
  }

  /**
   * Returns the names of all the parameters as an Enumeration of
   * Strings.  It returns an empty Enumeration if there are no parameters.
   *
   * @return the names of all the parameters as an Enumeration of Strings.
   */
  public Enumeration getParameterNames() {
    return parameters.keys();
  }

  /**
   * @return number of uploaded files
   */

  public int getFileCount() {
    int iCount = 0;
    Enumeration oFiles = files.keys();
    Object oFileName;

    while (oFiles.hasMoreElements()) {
      oFileName = oFiles.nextElement();
      if (null!=oFileName)
        if (!oFileName.equals(""))
          iCount++;
    } // wend
    return iCount;
  } // getFileCount

  /**
   * Returns the names of all the uploaded files as an Enumeration of
   * Strings.  It returns an empty Enumeration if there are no uploaded
   * files.  Each file name is the name specified by the form, not by
   * the user.
   *
   * @return the names of all the uploaded files as an Enumeration of Strings.
   */
  public Enumeration getFileNames() {
    return files.keys();
  }

  /**
   * Returns the value of the named parameter as a String, or null if
   * the parameter was not sent or was sent without a value.  The value
   * is guaranteed to be in its normal, decoded form.  If the parameter
   * has multiple values, only the last one is returned (for backward
   * compatibility).  For parameters with multiple values, it's possible
   * the last "value" may be null.
   *
   * @param name the parameter name.
   * @return the parameter value.
   */
  public String getParameter(String name) {
    try {
      Vector values = (Vector)parameters.get(name);
      if (values == null || values.size() == 0) {
        return null;
      }
      String value = (String)values.elementAt(values.size() - 1);
      return value;
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Returns the values of the named parameter as a String array, or null if
   * the parameter was not sent.  The array has one entry for each parameter
   * field sent.  If any field was sent without a value that entry is stored
   * in the array as a null.  The values are guaranteed to be in their
   * normal, decoded form.  A single value is returned as a one-element array.
   *
   * @param name the parameter name.
   * @return the parameter values.
   */
  public String[] getParameterValues(String name) {
    try {
      Vector values = (Vector)parameters.get(name);
      if (values == null || values.size() == 0) {
        return null;
      }
      String[] valuesArray = new String[values.size()];
      values.copyInto(valuesArray);
      return valuesArray;
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Returns the filesystem name of the specified file, or null if the
   * file was not included in the upload.  A filesystem name is the name
   * specified by the user.  It is also the name under which the file is
   * actually saved.
   *
   * @param name the file name.
   * @return the filesystem name of the file.
   */
  public String getFilesystemName(String name) {
    try {
      UploadedFile file = (UploadedFile)files.get(name);
      return file.getFilesystemName()// may be null
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Returns the original filesystem name of the specified file (before any
   * renaming policy was applied), or null if the file was not included in
   * the upload.  A filesystem name is the name specified by the user.
   *
   * @param name the file name.
   * @return the original file name of the file.
   */
  public String getOriginalFileName(String name) {
    try {
      UploadedFile file = (UploadedFile)files.get(name);
      return file.getOriginalFileName()// may be null
    }
    catch (Exception e) {
      return null;
    }
  }

  public String getCharacterEncoding() {
    return encoding;
  }

  /**
   * Returns the content type of the specified file (as supplied by the
   * client browser), or null if the file was not included in the upload.
   *
   * @param name the file name.
   * @return the content type of the file.
   */
  public String getContentType(String name) {
    try {
      UploadedFile file = (UploadedFile)files.get(name);
      return file.getContentType()// may be null
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Returns a File object for the specified file saved on the server's
   * filesystem, or null if the file was not included in the upload.
   * @param name the file name.
   * @return a File object for the named file.
   */
  public File getFile(String name) {
    try {
      UploadedFile file = (UploadedFile)files.get(name);
      return file.getFile()// may be null
    }
    catch (Exception e) {
      return null;
    }
  }

  /**
   * Returns a File object for the specified uploaded file
   * @param number int [0..getFileCount()-1]
   * @return File
   */
  public File getFile(int number) {
    UploadedFile file = null;
    Enumeration fileenum = files.elements();
    try {
      for (int f=0; f<getFileCount(); f++)
        file = (UploadedFile) fileenum.nextElement();
      return file.getFile()// may be null
    }
    catch (Exception e) {
      return null;
    }
  } // getFile

  public String getContentType() {
    return contenttype;
  }

  public int getContentLength() {
    return contentlength;
  }

  /**
   * <p>Returns the part of this request's URL that calls the servlet.</p>
   * Returns the part of this request's URL that calls the servlet.
   * This includes either the servlet name or a path to the servlet,
   * but does not include any extra path information or a query string.
   * Same as the value of the CGI variable SCRIPT_NAME.
   * @return  String containing the name or path of the servlet being called,
   * as specified in the request URL, decoded.
   */
  public String getServletPath() {
    return servletpath;
  }

  public String getContextPath() {
    return ctxpath;
  }

  public String getPathInfo() {
    return pathinfo;
  }

  public String getPathTranslated() {
    return trnpath;
  }

  public String getMethod() {
    return formmethod;
  }

  public String getRemoteUser() {
    return remoteuser;
  }

  public String getRequestURI() {
    return requesturi;
  }

  public StringBuffer getRequestURL() {
    return requesturl;
  }

  public String getRequestedSessionId() {
    return sessionid;
  }

  public String getQueryString() {
    return querystr;
  }

  public boolean isRequestedSessionIdFromURL() {
    return bRequestedSessionIdFromURL;
  }

  public boolean isRequestedSessionIdFromUrl() {
    return bRequestedSessionIdFromURL;
  }

  public boolean isRequestedSessionIdFromCookie() {
    return bRequestedSessionIdFromCookie;
  }

  public boolean isRequestedSessionIdValid() {
    return bRequestedSessionIdValid;
  }

  public Enumeration getAttributeNames() {
    throw new RuntimeException("HttpServletRequest.getAttributeNames() method not implemented for MultipartRequest");
  }

  public Object getAttribute(String sAttrName) {
    throw new RuntimeException("HttpServletRequest.getAttribute() method not implemented for MultipartRequest");
  }

  public void setAttribute(String sAttrName, Object sAttrVal) {
    throw new RuntimeException("HttpServletRequest.setAttribute() method not implemented for MultipartRequest");
  }

  public void removeAttribute(String sAttrName) {
    throw new RuntimeException("HttpServletRequest.removeAttribute() method not implemented for MultipartRequest");
  }

  public Enumeration getLocales() {
    throw new RuntimeException("HttpServletRequest.getLocales() method not implemented for MultipartRequest");
  }

  public boolean isSecure() {
    throw new RuntimeException("HttpServletRequest.isSecure() method not implemented for MultipartRequest");
  }

  public String getAuthType() {
    throw new RuntimeException("HttpServletRequest.getAuthType() method not implemented for MultipartRequest");
  }

  public int getLocalPort() {
    throw new RuntimeException("HttpServletRequest.getLocalPort() method not implemented for MultipartRequest");
  }

  public String getProtocol() {
    throw new RuntimeException("HttpServletRequest.getProtocol() method not implemented for MultipartRequest");
  }

  public Map getParameterMap() {
    throw new RuntimeException("HttpServletRequest.getParameterMap() method not implemented for MultipartRequest");
  }

  public String getScheme() {
    throw new RuntimeException("HttpServletRequest.getScheme() method not implemented for MultipartRequest");
  }

  public String getServerName() {
    throw new RuntimeException("HttpServletRequest.getServerName() method not implemented for MultipartRequest");
  }

  public int getServerPort() {
    throw new RuntimeException("HttpServletRequest.getServerPort() method not implemented for MultipartRequest");
  }

  public int getRemotePort() {
    throw new RuntimeException("HttpServletRequest.getRemotePort() method not implemented for MultipartRequest");
  }

  public String getLocalAddr() {
    throw new RuntimeException("HttpServletRequest.getLocalAddr() method not implemented for MultipartRequest");
  }

  public String getLocalName() {
    throw new RuntimeException("HttpServletRequest.getLocalName() method not implemented for MultipartRequest");
  }

  public String getRemoteAddr() {
    throw new RuntimeException("HttpServletRequest.getRemoteAddr() method not implemented for MultipartRequest");
  }

  public String getRemoteHost() {
    throw new RuntimeException("HttpServletRequest.getRemoteHost() method not implemented for MultipartRequest");
  }

  public HttpSession getSession() {
    throw new RuntimeException("HttpServletRequest.getSession() method not implemented for MultipartRequest");
  }

  public HttpSession getSession(boolean b) {
    throw new RuntimeException("HttpServletRequest.getSession() method not implemented for MultipartRequest");
  }

  public Principal getUserPrincipal()  {
    throw new RuntimeException("HttpServletRequest.getUserPrincipal() method not implemented for MultipartRequest");
  }

  public String getRealPath(String s) {
    throw new RuntimeException("HttpServletRequest.getRealPath() method not implemented for MultipartRequest");
  }

  public RequestDispatcher getRequestDispatcher(String s) {
    throw new RuntimeException("HttpServletRequest.getRequestDispatcher() method not implemented for MultipartRequest");
  }

  public boolean isUserInRole(String role)  {
    throw new RuntimeException("HttpServletRequest.isUserInRole() method not implemented for MultipartRequest");
  }

  public String getHeader(String hname)  {
    throw new RuntimeException("HttpServletRequest.getHeader() method not implemented for MultipartRequest");
  }

  public Enumeration getHeaders(String hname)  {
    throw new RuntimeException("HttpServletRequest.getHeaders() method not implemented for MultipartRequest");
  }

  public int getIntHeader(String hname)  {
    throw new RuntimeException("HttpServletRequest.getIntHeader() method not implemented for MultipartRequest");
  }

  public long getDateHeader(String hname)  {
    throw new RuntimeException("HttpServletRequest.getDateHeader() method not implemented for MultipartRequest");
  }

  public Enumeration getHeaderNames()  {
    throw new RuntimeException("HttpServletRequest.getHeaderNames() method not implemented for MultipartRequest");
  }

  public BufferedReader getReader() {
    throw new RuntimeException("HttpServletRequest.getReader() method not implemented for MultipartRequest");
  }

  public ServletInputStream getInputStream() {
    throw new RuntimeException("HttpServletRequest.getInputStream() method not implemented for MultipartRequest");
  }

  public void setCharacterEncoding(String sEncoding) {
    throw new RuntimeException("HttpServletRequest.setCharacterEncoding() method not implemented for MultipartRequest");
  }

}


// A class to hold information about an uploaded file.
//
class UploadedFile {

  private String dir;
  private String filename;
  private String original;
  private String type;

  UploadedFile(String dir, String filename, String original, String type) {
    this.dir = dir;
    this.filename = filename;
    this.original = original;
    this.type = type;
  }

  public String getContentType() {
    return type;
  }

  public String getFilesystemName() {
    return filename;
  }

  public String getOriginalFileName() {
    return original;
  }

  public File getFile() {
    if (dir == null || filename == null) {
      return null;
    }
    else {
      return new File(dir + File.separator + filename);
    }
  }

}

TOP

Related Classes of com.oreilly.servlet.UploadedFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.