/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/server/http/HttpServletRequestImpl.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.server.http;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.MultipartConfigElement;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import com.caucho.config.scope.ScopeRemoveListener;
import com.caucho.i18n.CharacterEncoding;
import com.caucho.network.listen.SocketLink;
import com.caucho.network.listen.SocketLinkDuplexController;
import com.caucho.remote.websocket.MaskedFrameInputStream;
import com.caucho.remote.websocket.UnmaskedFrameInputStream;
import com.caucho.remote.websocket.WebSocketConstants;
import com.caucho.security.AbstractLogin;
import com.caucho.security.Login;
import com.caucho.server.cluster.Server;
import com.caucho.server.dispatch.Invocation;
import com.caucho.server.session.SessionManager;
import com.caucho.server.webapp.WebApp;
import com.caucho.util.Base64;
import com.caucho.util.CharBuffer;
import com.caucho.util.CharSegment;
import com.caucho.util.HashMapImpl;
import com.caucho.util.L10N;
import com.caucho.util.NullEnumeration;
import com.caucho.vfs.Encoding;
import com.caucho.vfs.FilePath;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;
import com.caucho.websocket.WebSocketContext;
import com.caucho.websocket.WebSocketListener;
import com.caucho.websocket.WebSocketServletRequest;

/**
 * User facade for http requests.
 */
public final class HttpServletRequestImpl extends AbstractCauchoRequest
  implements CauchoRequest, WebSocketServletRequest
{
  private static final Logger log
    = Logger.getLogger(HttpServletRequestImpl.class.getName());

  private static final L10N L = new L10N(HttpServletRequestImpl.class);

  private static final String CHAR_ENCODING = "resin.form.character.encoding";
  private static final String FORM_LOCALE = "resin.form.local";
  private static final String CAUCHO_CHAR_ENCODING = "caucho.form.character.encoding";

  private static final Charset UTF8 = Charset.forName("UTF-8");

  private AbstractHttpRequest _request;

  private final HttpServletResponseImpl _response;

  private Boolean _isSecure;

  private Invocation _invocation;

  // form
  private HashMapImpl<String,String[]> _filledForm;
  private List<Part> _parts;

  // session/cookies
  private Cookie []_cookiesIn;

  private boolean _varyCookies;   // True if the page depends on cookies
  private boolean _hasCookie;

  private boolean _isSessionIdFromCookie;

  // security
  private String _runAs;
  private boolean _isLoginRequested;

  // input stream management
  private boolean _hasReader;
  private boolean _hasInputStream;

  // servlet attributes
  private HashMapImpl<String,Object> _attributes;

  // proxy caching
  private boolean _isSyntheticCacheHeader;

  // comet
  private long _asyncTimeout;
  private AsyncContextImpl _asyncContext;

  private ArrayList<Path> _closeOnExit;

  /**
   * Create a new Request.  Because the actual initialization occurs with
   * the start() method, this just allocates statics.
   *
   * @param request
   */
  public HttpServletRequestImpl(AbstractHttpRequest request)
  {
    _request = request;

    _response = new HttpServletResponseImpl(this,
                                            request.getAbstractHttpResponse());
  }

  @Override
  public HttpServletResponseImpl getResponse()
  {
    return _response;
  }

  //
  // ServletRequest methods
  //

  /**
   * Returns the prococol, e.g. "HTTP/1.1"
   */
  @Override
  public String getProtocol()
  {
    return _request.getProtocol();
  }

  /**
   * Returns the request scheme, e.g. "http"
   */
  @Override
  public String getScheme()
  {
    String scheme = _request.getScheme();

    // server/12j2, server/1kkg
    if ("http".equals(scheme) || "https".equals(scheme))
      return isSecure() ? "https" : "http";
    else
      return scheme;
  }

  /**
   * Returns the server name handling the request.  When using virtual hosts,
   * this returns the virtual host name, e.g. "vhost1.caucho.com".
   *
   * This call returns the host name as the client sees it, which means that
   * if ipchains, load balancing, or proxying is involved this call returns the
   * correct call for forming urls, but may not contain the host that Resin is
   * actually listening on.
   */
  @Override
  public String getServerName()
  {
    AbstractHttpRequest request = _request;

    return request != null ? request.getServerName() : null;
  }

  /**
   * Returns the server port used by the client, e.g. 80.
   *
   * This call returns the port number as the client sees it, which means that
   * if ipchains, load balancing, or proxying is involved this call returns the
   * correct call for forming urls, but may not return the actual port that
   * Resin is listening on.
   *
   * This call should not be used to test for an ssl connection
   * (getServerPort() == 443), {@link #isSecure()} is provided for
   * that purpose.
   */
  @Override
  public int getServerPort()
  {
    AbstractHttpRequest request = _request;

    return request != null ? request.getServerPort() : 0;
  }

  /**
   * Returns the IP address of the remote host, i.e. the client browser.
   */
  @Override
  public String getRemoteAddr()
  {
    return _request.getRemoteAddr();
  }

  /**
   * Returns the DNS hostname of the remote host, i.e. the client browser.
   */
  @Override
  public String getRemoteHost()
  {
    return _request.getRemoteHost();
  }

  /**
   * Returns the port of the remote host, i.e. the client browser.
   *
   * @since 2.4
   */
  @Override
  public int getRemotePort()
  {
    return _request.getRemotePort();
  }

  /**
   * This call returns the ip of the host actually used to connect to the Resin
   * server,  which means that if ipchains, load balancing, or proxying is
   * involved this call <i>does not</i> return the correct host for
   * forming urls.
   *
   * @since 2.4
   */
  @Override
  public String getLocalAddr()
  {
    return _request.getLocalHost();
  }

  /**
   * Returns the IP address of the local host, i.e. the server.
   *
   * This call returns the name of the host actually used to connect to the
   * Resin server,  which means that if ipchains, load balancing, or proxying
   * is involved this call <i>does not</i> return the correct host for
   * forming urls.
   *
   * @since 2.4
   */
  @Override
  public String getLocalName()
  {
    return _request.getLocalHost();
  }

  /**
   * Returns the port of the local host.
   *
   * This call returns the port number actually used to connect to the Resin
   * server,  which means that if ipchains, load balancing, or proxying is
   * involved this call <i>does not</i> return the correct port for
   * forming urls.
   *
   * This call should not be used to test for an ssl connection
   * (getServerPort() == 443), {@link #isSecure()} is provided for that purpose.
   *
   * @since 2.4
   */
  @Override
  public int getLocalPort()
  {
    return _request.getLocalPort();
  }

  /**
   * Overrides the character encoding specified in the request.
   * <code>setCharacterEncoding</code> must be called before calling
   * <code>getReader</code> or reading any parameters.
   */
  @Override
  public void setCharacterEncoding(String encoding)
    throws java.io.UnsupportedEncodingException
  {
    _request.setCharacterEncoding(encoding);
  }

  /**
   * Returns an InputStream to retrieve POST data from the request.
   * The stream will automatically end when the end of the POST data
   * is complete.
   */
  @Override
  public ServletInputStream getInputStream()
    throws IOException
  {
    if (_hasReader)
      throw new IllegalStateException(L.l("getInputStream() can't be called after getReader()"));

    _hasInputStream = true;

    return _request.getInputStream();
  }

  /**
   * Returns a reader to read POSTed data.  Character encoding is
   * based on the request data and is the same as
   * <code>getCharacterEncoding()</code>
   */
  @Override
  public BufferedReader getReader()
    throws IOException, IllegalStateException
  {
    if (_hasInputStream)
      throw new IllegalStateException(L.l("getReader() can't be called after getInputStream()"));

    _hasReader = true;

    return _request.getReader();
  }

  /**
   * Returns the character encoding of the POSTed data.
   */
  @Override
  public String getCharacterEncoding()
  {
    return _request.getCharacterEncoding();
  }

  /**
   * Returns the content length of the data.  This value may differ from
   * the actual length of the data.  Newer browsers
   * supporting HTTP/1.1 may use "chunked" encoding which does
   * not make the content length available.
   *
   * <p>The upshot is, rely on the input stream to end when the data
   * completes.
   */
  @Override
  public int getContentLength()
  {
    return _request.getContentLength();
  }

  /**
   * Returns the request's mime-type.
   */
  @Override
  public String getContentType()
  {
    return _request.getContentType();
  }

  /**
   * Returns the request's preferred locale, based on the Accept-Language
   * header.  If unspecified, returns the server's default locale.
   */
  @Override
  public Locale getLocale()
  {
    return _request.getLocale();
  }

  /**
   * Returns an enumeration of all locales acceptable by the client.
   */
  @Override
  public Enumeration<Locale> getLocales()
  {
    return _request.getLocales();
  }

  /**
   * Returns true if the connection is secure, e.g. it uses SSL.
   */
  @Override
  public boolean isSecure()
  {
    if (_isSecure != null)
      return _isSecure;

    AbstractHttpRequest request = _request;

    if (request == null)
      return false;
    
    WebApp webApp = request.getWebApp();
      
    if (webApp != null) {
      Boolean isSecure = webApp.isRequestSecure();
        
      if (isSecure != null)
        return isSecure;
    }
      
    return request.isSecure();
  }

  //
  // request attributes
  //

  /**
   * Returns the value of the named request attribute.
   *
   * @param name the attribute name.
   *
   * @return the attribute value.
   */
  @Override
  public Object getAttribute(String name)
  {
    HashMapImpl<String,Object> attributes = _attributes;
    
    if (attributes != null)
      return attributes.get(name);
    else if (isSecure()) {
      _attributes = new HashMapImpl<String,Object>();
      attributes = _attributes;
      _request.initAttributes(this);

      return attributes.get(name);
    }
    else
      return null;
  }

  /**
   * Returns an enumeration of the request attribute names.
   */
  @Override
  public Enumeration<String> getAttributeNames()
  {
    HashMapImpl<String,Object> attributes = _attributes;

    if (attributes != null) {
      return Collections.enumeration(attributes.keySet());
    }
    else if (isSecure()) {
      _attributes = new HashMapImpl<String,Object>();
      attributes = _attributes;
      _request.initAttributes(this);

      return Collections.enumeration(attributes.keySet());
    }
    else
      return NullEnumeration.create();
  }
  
  /**
   * Sets the value of the named request attribute.
   *
   * @param name the attribute name.
   * @param value the new attribute value.
   */
  @Override
  public void setAttribute(String name, Object value)
  {
    HashMapImpl<String,Object> attributes = _attributes;

    if (value != null) {
      if (attributes == null) {
        attributes = new HashMapImpl<String,Object>();
        _attributes = attributes;
        _request.initAttributes(this);
      }

      Object oldValue = attributes.put(name, value);

      WebApp webApp = getWebApp();

      if (webApp != null) {
        for (ServletRequestAttributeListener listener
               : webApp.getRequestAttributeListeners()) {
          ServletRequestAttributeEvent event;

          if (oldValue != null) {
            event = new ServletRequestAttributeEvent(webApp, this,
                                                     name, oldValue);

            listener.attributeReplaced(event);
          }
          else {
            event = new ServletRequestAttributeEvent(webApp, this,
                                                     name, value);

            listener.attributeAdded(event);
          }
        }
      }
    }
    else
      removeAttribute(name);
  }

  /**
   * Removes the value of the named request attribute.
   *
   * @param name the attribute name.
   */
  @Override
  public void removeAttribute(String name)
  {
    HashMapImpl<String,Object> attributes = _attributes;

    if (attributes == null)
      return;

    Object oldValue = attributes.remove(name);

    WebApp webApp = getWebApp();
    
    if (webApp == null)
      return;

    for (ServletRequestAttributeListener listener
           : webApp.getRequestAttributeListeners()) {
      ServletRequestAttributeEvent event;

      event = new ServletRequestAttributeEvent(webApp, this,
                                               name, oldValue);

      listener.attributeRemoved(event);
    }

    if (oldValue instanceof ScopeRemoveListener) {
      ((ScopeRemoveListener) oldValue).removeEvent(this, name);
    }
  }

  //
  // request dispatching
  //

  /**
   * Returns a request dispatcher for later inclusion or forwarding.  This
   * is the servlet API equivalent to SSI includes.  <code>uri</code>
   * is relative to the request URI.  Absolute URIs are relative to
   * the application prefix (<code>getContextPath()</code>).
   *
   * <p>If <code>getRequestURI()</code> is /myapp/dir/test.jsp and the
   * <code>uri</code> is "inc.jsp", the resulting page is
   * /myapp/dir/inc.jsp.

   * <code><pre>
   *   RequestDispatcher disp;
   *   disp = getRequestDispatcher("inc.jsp?a=b");
   *   disp.include(request, response);
   * </pre></code>
   *
   * @param path path relative to <code>getRequestURI()</code>
   * (including query string) for the included file.
   * @return RequestDispatcher for later inclusion or forwarding.
   */
  @Override
  public RequestDispatcher getRequestDispatcher(String path)
  {
    if (path == null || path.length() == 0)
      return null;
    else if (path.charAt(0) == '/')
      return getWebApp().getRequestDispatcher(path);
    else {
      CharBuffer cb = new CharBuffer();

      WebApp webApp = getWebApp();

      String servletPath = getPageServletPath();
      if (servletPath != null)
        cb.append(servletPath);
      String pathInfo = getPagePathInfo();
      if (pathInfo != null)
        cb.append(pathInfo);

      int p = cb.lastIndexOf('/');
      if (p >= 0)
        cb.setLength(p);
      cb.append('/');
      cb.append(path);

      if (webApp != null)
        return webApp.getRequestDispatcher(cb.toString());

      return null;
    }
  }

  /**
   * Returns the servlet context for the request
   *
   * @since Servlet 3.0
   */
  @Override
  public ServletContext getServletContext()
  {
    Invocation invocation = _invocation;

    if (invocation != null)
      return invocation.getWebApp();
    else
      return null;
  }

  /**
   * Returns the servlet response for the request
   *
   * @since Servlet 3.0
   */
  @Override
  public ServletResponse getServletResponse()
  {
    return _response;
  }

  //
  // HttpServletRequest APIs
  //

  /**
   * Returns the HTTP method, e.g. "GET" or "POST"
   *
   * <p/>Equivalent to CGI's <code>REQUEST_METHOD</code>
   */
  @Override
  public String getMethod()
  {
    return _request.getMethod();
  }

  /**
   * Returns the URI for the request
   */
  @Override
  public String getRequestURI()
  {
    if (_invocation != null)
      return _invocation.getRawURI();
    else
      return "";
  }

  /**
   * Returns the URI for the page.  getPageURI and getRequestURI differ
   * for included files.  getPageURI gets the URI for the included page.
   * getRequestURI returns the original URI.
   */
  public String getPageURI()
  {
    return _invocation.getRawURI();
  }

  /**
   * Returns the context part of the uri.  The context part is the part
   * that maps to an webApp.
   */
  public String getContextPath()
  {
    if (_invocation != null)
      return _invocation.getContextPath();
    else
      return "";
  }

  /**
   * Returns the context part of the uri.  For included files, this will
   * return the included context-path.
   */
  public String getPageContextPath()
  {
    return getContextPath();
  }

  /**
   * Returns the portion of the uri mapped to the servlet for the original
   * request.
   */
  public String getServletPath()
  {
    if (_invocation != null)
      return _invocation.getServletPath();
    else
      return "";
  }

  /**
   * Returns the portion of the uri mapped to the servlet for the current
   * page.
   */
  public String getPageServletPath()
  {
    if (_invocation != null)
      return _invocation.getServletPath();
    else
      return "";
  }

  /**
   * Returns the portion of the uri after the servlet path for the original
   * request.
   */
  public String getPathInfo()
  {
    if (_invocation != null)
      return _invocation.getPathInfo();
    else
      return null;
  }

  /**
   * Returns the portion of the uri after the servlet path for the current
   * page.
   */
  public String getPagePathInfo()
  {
    if (_invocation != null)
      return _invocation.getPathInfo();
    else
      return null;
  }

  /**
   * Returns the URL for the request
   */
  @Override
  public StringBuffer getRequestURL()
  {
    StringBuffer sb = new StringBuffer();

    sb.append(getScheme());
    sb.append("://");

    sb.append(getServerName());
    int port = getServerPort();

    if (port > 0
        && port != 80
        && port != 443) {
      sb.append(":");
      sb.append(port);
    }

    sb.append(getRequestURI());

    return sb;
  }

  /**
   * @deprecated As of JSDK 2.1
   */
  @Override
  public String getRealPath(String path)
  {
    if (path == null)
      return null;
    if (path.length() > 0 && path.charAt(0) == '/')
      return _invocation.getWebApp().getRealPath(path);

    String uri = getPageURI();
    String context = getPageContextPath();
    if (context != null)
      uri = uri.substring(context.length());

    int p = uri.lastIndexOf('/');
    if (p >= 0)
      path = uri.substring(0, p + 1) + path;

    return _invocation.getWebApp().getRealPath(path);
  }

  /**
   * Returns the real path of pathInfo.
   */
  @Override
  public String getPathTranslated()
  {
    String pathInfo = getPathInfo();

    if (pathInfo == null)
      return null;
    else
      return getRealPath(pathInfo);
  }

  /**
   * Returns the current page's query string.
   */
  @Override
  public String getQueryString()
  {
    if (_invocation != null)
      return _invocation.getQueryString();
    else
      return null;
  }

  /**
   * Returns the current page's query string.
   */
  public String getPageQueryString()
  {
    return getQueryString();
  }

  //
  // header management
  //

  /**
   * Returns the first value for a request header.
   *
   * <p/>Corresponds to CGI's <code>HTTP_*</code>
   *
   * <code><pre>
   * String userAgent = request.getHeader("User-Agent");
   * </pre></code>
   *
   * @param name the header name
   * @return the header value
   */
  @Override
  public String getHeader(String name)
  {
    return _request.getHeader(name);
  }

  /**
   * Returns all the values for a request header.  In some rare cases,
   * like cookies, browsers may return multiple headers.
   *
   * @param name the header name
   * @return an enumeration of the header values.
   */
  @Override
  public Enumeration<String> getHeaders(String name)
  {
    return _request.getHeaders(name);
  }

  /**
   * Returns an enumeration of all headers sent by the client.
   */
  @Override
  public Enumeration<String> getHeaderNames()
  {
    return _request.getHeaderNames();
  }

  /**
   * Converts a header value to an integer.
   *
   * @param name the header name
   * @return the header value converted to an integer
   */
  @Override
  public int getIntHeader(String name)
  {
    return _request.getIntHeader(name);
  }

  /**
   * Converts a date header to milliseconds since the epoch.
   *
   * <pre><code>
   * long mod = request.getDateHeader("If-Modified-Since");
   * </code></pre>
   *
   * @param name the header name
   * @return the header value converted to an date
   */
  @Override
  public long getDateHeader(String name)
  {
    return _request.getDateHeader(name);
  }

  //
  // parameter/form
  //

  /**
   * Returns an enumeration of the form names.
   */
  @Override
  public Enumeration<String> getParameterNames()
  {
    if (_filledForm == null)
      _filledForm = parseQuery();

    return Collections.enumeration(_filledForm.keySet());
  }

  /**
   * Returns a map of the form.
   */
  @Override
  public Map<String,String[]> getParameterMap()
  {
    if (_filledForm == null)
      _filledForm = parseQuery();

    return Collections.unmodifiableMap(_filledForm);
  }

  /**
   * Returns the form's values for the given name.
   *
   * @param name key in the form
   * @return value matching the key
   */
  @Override
  public String []getParameterValues(String name)
  {
    if (_filledForm == null)
      _filledForm = parseQuery();

    return (String []) _filledForm.get(name);
  }

  /**
   * Returns the form primary value for the given name.
   */
  @Override
  public String getParameter(String name)
  {
    String []values = getParameterValues(name);

    if (values != null && values.length > 0)
      return values[0];
    else
      return null;
  }

  /**
   * @since Servlet 3.0
   */
  @Override
  public Collection<Part> getParts()
    throws IOException, ServletException
  {
    MultipartConfigElement multipartConfig
      = _invocation.getMultipartConfig();
    
    if (multipartConfig == null)
      throw new ServletException(L.l("multipart-form is disabled; check @MultipartConfig annotation on '{0}'.", _invocation.getServletName()));
    
    /*
    if (! getWebApp().doMultipartForm())
      throw new ServletException("multipart-form is disabled; check <multipart-form> configuration tag.");
      */

    if (! getContentType().startsWith("multipart/form-data"))
      throw new ServletException("Content-Type must be of 'multipart/form-data'.");

    if (_filledForm == null)
      _filledForm = parseQuery();

    return _parts;
  }

  Part createPart(String name, Map<String, List<String>> headers)
  {
    return new PartImpl(name, headers);
  }

  /**
   * @since Servlet 3.0
   */
  @Override
  public Part getPart(String name)
    throws IOException, ServletException
  {
    for (Part part : getParts()) {
      if (name.equals(part.getName()))
        return part;
    }

    return null;
  }

  /**
   * Parses the query, either from the GET or the post.
   *
   * <p/>The character encoding is somewhat tricky.  If it's a post, then
   * assume the encoded form uses the same encoding as
   * getCharacterEncoding().
   *
   * <p/>If the request doesn't provide the encoding, use the
   * character-encoding parameter from the webApp.
   *
   * <p/>Otherwise use the default system encoding.
   */
  private HashMapImpl<String,String[]> parseQuery()
  {
    HashMapImpl<String,String[]> form = _request.getForm();

    try {
      String query = getQueryString();
      CharSegment contentType = _request.getContentTypeBuffer();

      if (query == null && contentType == null)
        return form;

      Form formParser = _request.getFormParser();
      long contentLength = _request.getLongContentLength();

      String charEncoding = getCharacterEncoding();
      if (charEncoding == null) {
        charEncoding = (String) getAttribute(CAUCHO_CHAR_ENCODING);
        if (charEncoding == null)
          charEncoding = (String) getAttribute(CHAR_ENCODING);
        if (charEncoding == null) {
          Locale locale = (Locale) getAttribute(FORM_LOCALE);
          if (locale != null)
            charEncoding = Encoding.getMimeName(locale);
        }
      }

      if (query != null) {
        String queryEncoding = charEncoding;

        if (queryEncoding == null && getServer() != null)
          queryEncoding = getServer().getURLCharacterEncoding();

        if (queryEncoding == null)
          queryEncoding = CharacterEncoding.getLocalEncoding();

        String javaEncoding = Encoding.getJavaName(queryEncoding);

        formParser.parseQueryString(form, query, javaEncoding, true);
      }

      if (charEncoding == null)
        charEncoding = CharacterEncoding.getLocalEncoding();

      String javaEncoding = Encoding.getJavaName(charEncoding);

      MultipartConfigElement multipartConfig
        = _invocation.getMultipartConfig();

      if (contentType == null || ! "POST".equalsIgnoreCase(getMethod())) {
      }

      else if (contentType.startsWith("application/x-www-form-urlencoded")) {
        formParser.parsePostData(form, getInputStream(), javaEncoding);
      }

      else if ((getWebApp().doMultipartForm() || multipartConfig != null)
               && contentType.startsWith("multipart/form-data")) {
        int length = contentType.length();
        int i = contentType.indexOf("boundary=");

        if (i < 0)
          return form;

        long formUploadMax = getWebApp().getFormUploadMax();
        long parameterLengthMax = getWebApp().getFormParameterLengthMax();
        
        if (parameterLengthMax < 0)
          parameterLengthMax = Long.MAX_VALUE / 2;

        Object uploadMax = getAttribute("caucho.multipart.form.upload-max");
        if (uploadMax instanceof Number)
          formUploadMax = ((Number) uploadMax).longValue();

        Object paramMax = getAttribute("caucho.multipart.form.parameter-length-max");
        if (paramMax instanceof Number)
          parameterLengthMax = ((Number) paramMax).longValue();

        // XXX: should this be an error?
        if (formUploadMax >= 0 && formUploadMax < contentLength) {
          setAttribute("caucho.multipart.form.error",
                       L.l("Multipart form upload of '{0}' bytes was too large.",
                           String.valueOf(contentLength)));
          setAttribute("caucho.multipart.form.error.size",
                       new Long(contentLength));

          return form;
        }

        long fileUploadMax = -1;

        if (multipartConfig != null) {
          formUploadMax = multipartConfig.getMaxRequestSize();
          fileUploadMax = multipartConfig.getMaxFileSize();
        }

        if (multipartConfig != null
            && formUploadMax > 0
            && formUploadMax < contentLength)
          throw new IllegalStateException(L.l(
            "multipart form data request's Content-Length '{0}' is greater then configured in @MultipartConfig.maxRequestSize value: '{1}'",
            contentLength,
            formUploadMax));

        i += "boundary=".length();
        char ch = contentType.charAt(i);
        CharBuffer boundary = new CharBuffer();
        if (ch == '\'') {
          for (i++; i < length && contentType.charAt(i) != '\''; i++)
            boundary.append(contentType.charAt(i));
        }
        else if (ch == '\"') {
          for (i++; i < length && contentType.charAt(i) != '\"'; i++)
            boundary.append(contentType.charAt(i));
        }
        else {
          for (;
               i < length && (ch = contentType.charAt(i)) != ' ' &&
                 ch != ';';
               i++) {
            boundary.append(ch);
          }
        }

        _parts = new ArrayList<Part>();

        try {
          MultipartFormParser.parsePostData(form,
                                      _parts,
                                      getStream(false), boundary.toString(),
                                      this,
                                      javaEncoding,
                                      formUploadMax,
                                      fileUploadMax,
                                      parameterLengthMax);
        } catch (IOException e) {
          log.log(Level.FINE, e.toString(), e);
          setAttribute("caucho.multipart.form.error", e.getMessage());
        }
      }
    } catch (IOException e) {
      log.log(Level.FINE, e.toString(), e);
    }

    return form;
  }

  //
  // session/cookie management
  //

  /**
   * Returns an array of all cookies sent by the client.
   */
  @Override
  public Cookie []getCookies()
  {
    if (_cookiesIn == null) {
      _cookiesIn = _request.getCookies();

      SessionManager sessionManager = getSessionManager();
      String sessionCookieName = getSessionCookie(sessionManager);

      for (int i = 0; i < _cookiesIn.length; i++) {
        Cookie cookie = _cookiesIn[i];

        if (cookie.getName().equals(sessionCookieName)
          && sessionManager.isSecure()) {
          cookie.setSecure(true);
          break;
        }
      }

      /*
      // The page varies depending on the presense of any cookies
      setVaryCookie(null);

      // If any cookies actually exist, the page is not anonymous
      if (_cookiesIn != null && _cookiesIn.length > 0)
        setHasCookie();
      */
    }

    if (_cookiesIn == null || _cookiesIn.length == 0)
      return null;
    else
      return _cookiesIn;
  }

  /**
   * Returns the named cookie from the browser
   */
  @Override
  public Cookie getCookie(String name)
  {
    /*
    // The page varies depending on the presense of any cookies
    setVaryCookie(name);
    */

    return findCookie(name);
  }

  private Cookie findCookie(String name)
  {
    Cookie []cookies = getCookies();

    if (cookies == null)
      return null;

    int length = cookies.length;
    for (int i = 0; i < length; i++) {
      Cookie cookie = cookies[i];

      if (cookie.getName().equals(name)) {
        setHasCookie();
        return cookie;
      }
    }

    return null;
  }

  /**
   * Returns the session id in the HTTP request.  The cookie has
   * priority over the URL.  Because the webApp might be using
   * the cookie to change the page contents, the caching sets
   * vary: JSESSIONID.
   */
  @Override
  public String getRequestedSessionId()
  {
    SessionManager manager = getSessionManager();

    if (manager != null && manager.enableSessionCookies()) {
      setVaryCookie(getSessionCookie(manager));

      String id = findSessionIdFromCookie();

      if (id != null) {
        _isSessionIdFromCookie = true;
        setHasCookie();
        return id;
      }
    }

    String id = findSessionIdFromUrl();
    if (id != null) {
      return id;
    }

    if (manager != null && manager.enableSessionCookies())
      return null;
    else
      return _request.findSessionIdFromConnection();
  }

  /**
   * Returns the session id in the HTTP request cookies.
   * Because the webApp might use the cookie to change
   * the page contents, the caching sets vary: JSESSIONID.
   */
  protected String findSessionIdFromCookie()
  {
    SessionManager manager = getSessionManager();

    if (manager == null || ! manager.enableSessionCookies())
      return null;

    Cookie cookie = getCookie(getSessionCookie(manager));

    if (cookie != null) {
      _isSessionIdFromCookie = true;
      return cookie.getValue();
    }
    else
      return null;
  }

  @Override
  public boolean isSessionIdFromCookie()
  {
    return _isSessionIdFromCookie;
  }

  @Override
  public String getSessionId()
  {
    String sessionId = getResponse().getSessionId();

    if (sessionId != null)
      return sessionId;
    else
      return getRequestedSessionId();
  }

  @Override
  public void setSessionId(String sessionId)
  {
    getResponse().setSessionId(sessionId);
  }

  /**
   * Returns the session id in the HTTP request from the url.
   */
  private String findSessionIdFromUrl()
  {
    // server/1319
    // setVaryCookie(getSessionCookie(manager));

    String id = _invocation != null ? _invocation.getSessionId() : null;
    if (id != null)
      setHasCookie();

    return id;
  }

  /**
   * Returns true if the current sessionId came from a cookie.
   */
  @Override
  public boolean isRequestedSessionIdFromCookie()
  {
    return findSessionIdFromCookie() != null;
  }

  /**
   * Returns true if the current sessionId came from the url.
   */
  @Override
  public boolean isRequestedSessionIdFromURL()
  {
    return findSessionIdFromUrl() != null;
  }

  /**
   * @deprecated
   */
  @Override
  public boolean isRequestedSessionIdFromUrl()
  {
    return isRequestedSessionIdFromURL();
  }

  /**
   * Returns the session id in the HTTP request.  The cookie has
   * priority over the URL.  Because the webApp might be using
   * the cookie to change the page contents, the caching sets
   * vary: JSESSIONID.
   */
  public String getRequestedSessionIdNoVary()
  {
    boolean varyCookies = _varyCookies;
    boolean hasCookie = _hasCookie;
    boolean privateCache = _response.getPrivateCache();

    String id = getRequestedSessionId();

    _varyCookies = varyCookies;
    _hasCookie = hasCookie;
    _response.setPrivateOrResinCache(privateCache);

    return id;
  }

  //
  // security
  //

  @Override
  protected String getRunAs()
  {
    return _runAs;
  }

  /**
   * Gets the authorization type
   */
  public String getAuthType()
  {
    Object login = getAttribute(AbstractLogin.LOGIN_USER_NAME);

    if (login instanceof X509Certificate)
      return HttpServletRequest.CLIENT_CERT_AUTH;

    WebApp app = getWebApp();

    if (app != null && app.getLogin() != null && getUserPrincipal() != null)
      return app.getLogin().getAuthType();
    else
      return null;
  }

  /**
   * Returns the login for the request.
   */
  protected Login getLogin()
  {
    WebApp webApp = getWebApp();

    if (webApp != null)
      return webApp.getLogin();
    else
      return null;
  }
  /**
   * Returns true if any authentication is requested
   */
  @Override
  public boolean isLoginRequested()
  {
    return _isLoginRequested;
  }

  @Override
  public void requestLogin()
  {
    _isLoginRequested = true;
  }

  /**
   * Gets the remote user from the authorization type
   */
  public String getRemoteUser()
  {
    Principal principal = getUserPrincipal();

    if (principal != null)
      return principal.getName();
    else
      return null;
  }

  /**
   * Internal logging return to get the remote user.  If the request already
   * knows the user, get it, otherwise just return null.
   */
  public String getRemoteUser(boolean create)
  {
    /*
    if (getSession(false) == null)
      return null;
    */

    Principal user = (Principal) getAttribute(AbstractLogin.LOGIN_USER_NAME);

    if (user == null && create)
      user = getUserPrincipal();

    if (user != null)
      return user.getName();
    else
      return null;
  }

  /**
   * Logs out the principal.
   */
  @Override
  public void logout()
  {
    Login login = getLogin();

    if (login != null) {
      login.logout(getUserPrincipal(), this, getResponse());
    }
  }

  /**
   * Clear the principal from the request object.
   */
  public void logoutUserPrincipal()
  {
    // XXX:
    /*
    if (_session != null)
      _session.logout();
    */
  }

  /**
   * Sets the overriding role.
   */
  public String runAs(String role)
  {
    String oldRunAs = _runAs;

    _runAs = role;

    return oldRunAs;
  }

  public void setSecure(Boolean isSecure)
  {
    // server/12ds
    _isSecure = isSecure;
  }

  //
  // deprecated
  //

  public ReadStream getStream()
    throws IOException
  {
    return _request.getStream();
  }

  public ReadStream getStream(boolean isFlush)
    throws IOException
  {
    return _request.getStream(isFlush);
  }

  public int getRequestDepth(int depth)
  {
    return depth;
  }

  public void setHeader(String key, String value)
  {
    _request.setHeader(key, value);
  }

  @Override
  public void setSyntheticCacheHeader(boolean isSynthetic)
  {
    _isSyntheticCacheHeader = isSynthetic;
  }

  @Override
  public boolean isSyntheticCacheHeader()
  {
    return _isSyntheticCacheHeader;
  }

  /**
   * Called if the page depends on a cookie.  If the cookie is null, then
   * the page depends on all cookies.
   *
   * @param cookie the cookie the page depends on.
   */
  public void setVaryCookie(String cookie)
  {
    _varyCookies = true;

    // XXX: server/1315 vs 2671
    // _response.setPrivateOrResinCache(true);
  }

  /**
   * Returns true if the page depends on cookies.
   */
  public boolean getVaryCookies()
  {
    return _varyCookies;
  }

  /**
   * Set when the page actually has a cookie.
   */
  public void setHasCookie()
  {
    _hasCookie = true;

    // XXX: 1171 vs 1240
    // _response.setPrivateOrResinCache(true);
  }

  /**
   * True if this page uses cookies.
   */
  public boolean getHasCookie()
  {
    if (_hasCookie)
      return true;
    else if (_invocation != null)
      return _invocation.getSessionId() != null;
    else
      return false;
  }

  public boolean isTop()
  {
    return true;
  }

  public boolean isComet()
  {
    return _request.isCometActive();
  }

  /**
   * Adds a file to be removed at the end.
   */
  public void addCloseOnExit(Path path)
  {
    if (_closeOnExit == null)
      _closeOnExit = new ArrayList<Path>();

    _closeOnExit.add(path);
  }

  public boolean isDuplex()
  {
    return _request.isDuplex();
  }

  @Override
  public void killKeepalive(String reason)
  {
    _request.killKeepalive(reason);
  }

  public boolean isConnectionClosed()
  {
    return _request.isConnectionClosed();
  }

  public SocketLink getConnection()
  {
    return _request.getConnection();
  }

  //
  // HttpServletRequestImpl methods
  //

  public AbstractHttpRequest getAbstractHttpRequest()
  {
    return _request;
  }

  public boolean isSuspend()
  {
    return _request.isSuspend();
  }

  public boolean hasRequest()
  {
    return _request.hasRequest();
  }

  public void setInvocation(Invocation invocation)
  {
    _invocation = invocation;
  }

  public Invocation getInvocation()
  {
    return _invocation;
  }

  public long getStartTime()
  {
    return _request.getStartTime();
  }

  public void finishInvocation()
  {
    // server/11d4
    /*
    AsyncContextImpl asyncContext = _asyncContext;

    if (asyncContext != null)
      asyncContext.onComplete();
      */

    _request.finishInvocation();
  }

  //
  // servlet 3.0 async support
  //

  /**
   * Returns true if the request is in async.
   *
   * @since Servlet 3.0
   */
  @Override
  public boolean isAsyncStarted()
  {
    AsyncContextImpl asyncContext = _asyncContext;

    if (asyncContext == null)
      return false;
    
    return asyncContext.isAsyncStarted();
  }

  /**
   * Returns true if the request supports async
   *
   * @since Servlet 3.0
   */
  @Override
  public boolean isAsyncSupported()
  {
    Invocation invocation = _invocation;

    if (invocation != null)
      return invocation.isAsyncSupported();
    else
      return false;
  }

  /**
   * Starts an async mode
   *
   * @since Servlet 3.0
   */
  @Override
  public AsyncContext startAsync()
  {
    return startAsync(this, _response);
  }

  /**
   * Starts an async mode
   *
   * @since Servlet 3.0
   */
  @Override
  public AsyncContext startAsync(ServletRequest request,
                                 ServletResponse response)
  {

    if (! isAsyncSupported())
      throw new IllegalStateException(L.l("The servlet '{0}' at '{1}' does not support async because the servlet or one of the filters does not support asynchronous mode.  The servlet should be annotated with a @WebServlet(asyncSupported=true) annotation or have a <async-supported> tag in the web.xml.",
                                          getServletName(), getServletPath()));

    if (_request.isCometActive()) {
      throw new IllegalStateException(L.l("startAsync may not be called twice on the same dispatch."));
    }

    boolean isOriginal = (request == this && response == _response);

    if (_asyncContext == null) {
      _asyncContext = new AsyncContextImpl(_request);
      
      if (_asyncTimeout > 0)
        _asyncContext.setTimeout(_asyncTimeout);
    }
    else
      _asyncContext.restart();
    
    _asyncContext.init(request, response, isOriginal);

    return _asyncContext;
  }

  /**
   * Returns the async context for the request
   *
   * @since Servlet 3.0
   */
  @Override
  public AsyncContextImpl getAsyncContext()
  {
    if (_asyncContext != null)
      return _asyncContext;
    else
      throw new IllegalStateException(L.l("getAsyncContext() must be called after asyncStarted() has started a new AsyncContext."));
  }

  //
  // WebSocket
  //

  @Override
  public WebSocketContext startWebSocket(WebSocketListener listener)
    throws IOException
  {
    if (log.isLoggable(Level.FINE))
      log.fine(this + " upgrade HTTP to WebSocket " + listener);
    
    String method = getMethod();
    
    if (! "GET".equals(method)) {
      getResponse().sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
      
      throw new IllegalStateException(L.l("HTTP Method must be 'GET', because the WebSocket protocol requires 'GET'.\n  remote-IP: {0}",
                                          getRemoteAddr()));
    }

    String connection = getHeader("Connection");
    String upgrade = getHeader("Upgrade");

    if (! "WebSocket".equalsIgnoreCase(upgrade)) {
      getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
      
      throw new IllegalStateException(L.l("HTTP Upgrade header '{0}' must be 'WebSocket', because the WebSocket protocol requires an Upgrade: WebSocket header.\n  remote-IP: {1}",
                                          upgrade,
                                          getRemoteAddr()));
    }

    if (connection == null 
        || connection.toLowerCase().indexOf("upgrade") < 0) {
      getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
      
      throw new IllegalStateException(L.l("HTTP Connection header '{0}' must be 'Upgrade', because the WebSocket protocol requires a Connection: Upgrade header.\n  remote-IP: {1}",
                                          connection,
                                          getRemoteAddr()));
    }
    
    String key = getHeader("Sec-WebSocket-Key");

    if (key == null) {
      getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
      
      throw new IllegalStateException(L.l("HTTP Sec-WebSocket-Key header is required, because the WebSocket protocol requires an Origin header.\n  remote-IP: {0}",
                                          getRemoteAddr()));
    }
    else if (key.length() != 24) {
      getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
      
      throw new IllegalStateException(L.l("HTTP Sec-WebSocket-Key header is invalid '{0}' because it's not a 16-byte value.\n  remote-IP: {1}",
                                          key,
                                          getRemoteAddr()));
    }

    String version = getHeader("Sec-WebSocket-Version");

    String requiredVersion = WebSocketConstants.VERSION;
    if (! requiredVersion.equals(version)) {
      getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
      
      throw new IllegalStateException(L.l("HTTP Sec-WebSocket-Version header with value '{0}' is required, because the WebSocket protocol requires an Sec-WebSocket-Version header.\n  remote-IP: {1}",
                                          requiredVersion,
                                          getRemoteAddr()));
    }
    
    String extensions = getHeader("Sec-WebSocket-Extensions");
    
    boolean isMasked = true;
    
    if (extensions != null
        && extensions.indexOf("x-unmasked") >= 0) {
      isMasked = false;
    }
    
    String serverExtensions = null;
    
    if (! isMasked)
      serverExtensions = "x-unmasked";
    
    _response.setStatus(101);//, "Switching Protocols");
    _response.setHeader("Upgrade", "websocket");
    
    String accept = calculateWebSocketAccept(key);
    
    _response.setHeader("Sec-WebSocket-Accept", accept);
    
    if (serverExtensions != null)
      _response.setHeader("Sec-WebSocket-Extensions", serverExtensions);

    _response.setContentLength(0);

    WebSocketContextImpl webSocket;
    
    if (isMasked)
      webSocket = new WebSocketContextImpl(this, _response, listener,
                                           new MaskedFrameInputStream());
    else
      webSocket = new WebSocketContextImpl(this, _response, listener,
                                           new UnmaskedFrameInputStream());
    
    SocketLinkDuplexController controller = _request.startDuplex(webSocket);
    webSocket.setController(controller);
    
    try {
      _response.getOutputStream().flush();
      
      webSocket.flush();

      webSocket.onStart();
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    return webSocket;
  }
  
  private String calculateWebSocketAccept(String key)
  {
    try {
      MessageDigest md = MessageDigest.getInstance("SHA1");
      
      int length = key.length();
      for (int i = 0; i < length; i++) {
        md.update((byte) key.charAt(i));
      }
      
      String guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
      length = guid.length();
      for (int i = 0; i < length; i++) {
        md.update((byte) guid.charAt(i));
      }
      
      byte []digest = md.digest();
      
      return Base64.encode(digest);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  int getAvailable()
    throws IOException
  {
    if (_request != null)
      return _request.getAvailable();
    else
      return -1;
  }

  public DispatcherType getDispatcherType()
  {
    return DispatcherType.REQUEST;
  }

  @Override
  protected void finishRequest()
    throws IOException
  {
    AsyncContextImpl async = _asyncContext;
    _asyncContext = null;

    /* server/1ld5
    if (comet != null) {
      comet.onComplete();
    }
    */
    
    if (async != null) {
      async.onComplete();
    }

    super.finishRequest();

    // ioc/0a10
    cleanup();

    if (_closeOnExit != null) {
      for (int i = _closeOnExit.size() - 1; i >= 0; i--) {
        Path path = _closeOnExit.get(i);

        try {
          path.remove();
        } catch (Throwable e) {
          log.log(Level.FINE, e.toString(), e);
        }
      }
    }
    
    _request = null;
  }

  public void cleanup()
  {
    HashMapImpl<String,Object> attributes = _attributes;

    if (attributes != null) {
      for (Map.Entry<String,Object> entry : attributes.entrySet()) {
        Object value = entry.getValue();

        if (value instanceof ScopeRemoveListener) {
          ((ScopeRemoveListener) value).removeEvent(this, entry.getKey());
        }
      }
    }
  }

  //
  // XXX: unsorted
  //

  /**
   * Returns the servlet name.
   */
  public String getServletName()
  {
    if (_invocation != null) {
      return _invocation.getServletName();
    }
    else
      return null;
  }

  public final Server getServer()
  {
    return _request.getServer();
  }

  /**
   * Returns the invocation's webApp.
   */
  public final WebApp getWebApp()
  {
    if (_invocation != null)
      return _invocation.getWebApp();
    else
      return null;
  }

  public boolean isClosed()
  {
    return _request == null;
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _request + "]";
  }

  public class PartImpl implements Part {
    private String _name;
    private Map<String, List<String>> _headers;
    private Object _value;
    private Path _newPath;

    private PartImpl(String name, Map<String, List<String>> headers)
    {
      _name = name;
      _headers = headers;
    }

    public void delete()
      throws IOException
    {
      if (_newPath != null)
        _newPath.remove();

      Object value = getValue();

      if (! (value instanceof FilePath))
        throw new IOException(L.l("Part.delete() is not applicable to part '{0}':'{1}'", _name, value));

      ((FilePath)value).remove();
    }

    public String getContentType()
    {
      String[] value = _filledForm.get(_name + ".content-type");

      if (value != null && value.length > 0)
        return value[0];

      return null;
    }

    public String getHeader(String name)
    {
      List<String> values = _headers.get(name);

      if (values != null && values.size() > 0)
        return values.get(0);

      return null;
    }

    public Collection<String> getHeaderNames()
    {
      return _headers.keySet();
    }

    public Collection<String> getHeaders(String name)
    {
      return _headers.get(name);
    }

    public InputStream getInputStream()
      throws IOException
    {
      Object value = getValue();

      if (value instanceof FilePath)
        return ((FilePath) value).openRead();

      ByteArrayInputStream is
        = new ByteArrayInputStream(value.toString().getBytes(UTF8));

      return is;
    }


    public String getName()
    {
      return _name;
    }

    public long getSize()
    {
      Object value = getValue();

      if (value instanceof FilePath) {
        return ((Path) value).getLength();
      }
      else if (value instanceof String) {
        return -1;
      }
      else if (value == null) {
        return -1;
      }
      else {
        log.finest(L.l("Part.getSize() is not applicable to part'{0}':'{1}'",
                       _name, value));

        return -1;
      }
    }

    @Override
    public void write(String fileName)
      throws IOException
    {
      if (_newPath != null)
        throw new IOException(L.l(
          "Contents of part '{0}' has already been written to '{1}'",
          _name,
          _newPath));

      Path path;

      Object value = getValue();

      if (! (value instanceof FilePath))
        throw new IOException(L.l(
          "Part.write() is not applicable to part '{0}':'{1}'",
          _name,
          value));
      else
        path = (Path) value;

      MultipartConfigElement mc = _invocation.getMultipartConfig();
      String location = mc.getLocation().replace('\\', '/');
      fileName = fileName.replace('\\', '/');

      String file;

      if (location.charAt(location.length() -1) != '/' && fileName.charAt(fileName.length() -1) != '/')
        file = location + '/' + fileName;
      else
        file = location + fileName;

      _newPath = Vfs.lookup(file);

      if (_newPath.exists())
        throw new IOException(L.l("File '{0}' already exists.", _newPath));

      Path parent = _newPath.getParent();

      if (! parent.exists())
        if (! parent.mkdirs())
          throw new IOException(L.l("Unable to create path '{0}'. Check permissions.", parent));

      if (! path.renameTo(_newPath)) {
        WriteStream out = null;

        try {
          out = _newPath.openWrite();

          path.writeToStream(out);

          out.flush();

          out.close();
        } catch (IOException e) {
          log.log(Level.SEVERE, L.l("Cannot write contents of '{0}' to '{1}'", path, _newPath), e);

          throw e;
        } finally {
          if (out != null)
            out.close();
        }
      }
    }

    public Object getValue()
    {
      if (_value != null)
        return _value;

      String []values = _filledForm.get(_name + ".file");

      if (values != null && values.length > 0) {
        _value = Vfs.lookup(values[0]);
      } else {
        values = _filledForm.get(_name);

        if (values != null && values.length > 0)
          _value = values[0];
      }

      return _value;
    }
  }
}
