text: /* ************************************************************************
  
     qooxdoo - the new era of web development
  
     http://qooxdoo.org
  
     Copyright:
       2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
       2006 Derrell Lipman
  
     License:
       MIT: https://opensource.org/licenses/MIT
       See the LICENSE file in the project's top-level directory for details.
  
     Authors:
       * Sebastian Werner (wpbasti)
       * Andreas Ecker (ecker)
       * Derrell Lipman (derrell)
  
  ************************************************************************ */
  
  /**
   * This class is used to send HTTP requests to the server.
   *
   * NOTE: Instances of this class must be disposed of after use
   *
   * Note: This class will be deprecated in a future release. Instead,
   * please use classes found in {@link qx.io.request}.
   */
  qx.Class.define("qx.io.remote.Request",
  {
    extend : qx.core.Object,
    implement : [ qx.core.IDisposable ],
  
  
  
  
    /*
    *****************************************************************************
       CONSTRUCTOR
    *****************************************************************************
    */
  
    /**
     * @param vUrl {String}
     *   Target url to issue the request to.
     *
     * @param vMethod {String}
     *   Determines http method (GET, POST, PUT, etc.) to use. See "method" property
     *   for valid values and default value.
     *
     * @param vResponseType {String}
     *   The mime type of the response. Default is text/plain.
     */
    construct : function(vUrl, vMethod, vResponseType)
    {
      this.base(arguments);
  
      this.__requestHeaders = {};
      this.__urlParameters = {};
      this.__dataParameters = {};
      this.__formFields = {};
  
      if (vUrl !== undefined) {
        this.setUrl(vUrl);
      }
  
      if (vMethod !== undefined) {
        this.setMethod(vMethod);
      }
  
      if (vResponseType !== undefined) {
        this.setResponseType(vResponseType);
      }
  
      this.setProhibitCaching(true);
  
      // Get the next sequence number for this request
      this.__seqNum = ++qx.io.remote.Request.__seqNum;
    },
  
  
  
  
    /*
    *****************************************************************************
       EVENTS
    *****************************************************************************
    */
  
    events : {
  
      /** Fired when the Request object changes its state to 'created' */
      "created" : "qx.event.type.Event",
  
      /** Fired when the Request object changes its state to 'configured' */
      "configured" : "qx.event.type.Event",
  
      /** Fired when the Request object changes its state to 'sending' */
      "sending" : "qx.event.type.Event",
  
      /** Fired when the Request object changes its state to 'receiving' */
      "receiving" : "qx.event.type.Event",
  
      /**
       * Fired once the request has finished successfully. The event object
       * can be used to read the transferred data.
       */
      "completed" : "qx.io.remote.Response",
  
      /** Fired when the pending request has been aborted. */
      "aborted" : "qx.event.type.Event",
  
      /** Fired when the pending request fails. */
      "failed" : "qx.io.remote.Response",
  
      /** Fired when the pending request times out. */
      "timeout" : "qx.io.remote.Response"
    },
  
  
  
    /*
    *****************************************************************************
       STATICS
    *****************************************************************************
    */
  
    statics :
    {
      /*
      ---------------------------------------------------------------------------
        SEQUENCE NUMBER
      ---------------------------------------------------------------------------
      */
  
      /**
       * Sequence (id) number of a request, used to associate a response or error
       * with its initiating request.
       */
      __seqNum : 0,
  
      /**
       * Returns true if the given HTTP method allows a request body being transferred to the server.
       * This is currently POST and PUT. Other methods require their data being encoded into
       * the URL
       *
       * @param httpMethod {String} one of the values of the method property
       * @return {Boolean}
       */
      methodAllowsRequestBody : function(httpMethod) {
        return (httpMethod == "POST") || (httpMethod == "PUT");
      }
  
    },
  
  
  
  
    /*
    *****************************************************************************
       PROPERTIES
    *****************************************************************************
    */
  
    properties :
    {
      /**
       * Target url to issue the request to.
       */
      url :
      {
        check : "String",
        init : ""
      },
  
  
      /**
       * Determines what type of request to issue (GET, POST, PUT, HEAD, DELETE).
       */
      method :
      {
        check : [ "GET", "POST", "PUT", "HEAD", "DELETE" ],
        apply : "_applyMethod",
        init : "GET"
      },
  
  
      /**
       * Set the request to asynchronous.
       */
      asynchronous :
      {
        check : "Boolean",
        init : true
      },
  
  
      /**
       * Set the data to be sent via this request
       */
      data :
      {
        check : "String",
        nullable : true
      },
  
  
      /**
       * Username to use for HTTP authentication.
       * Set to NULL if HTTP authentication is not used.
       */
      username :
      {
        check : "String",
        nullable : true
      },
  
  
      /**
       * Password to use for HTTP authentication.
       * Set to NULL if HTTP authentication is not used.
       */
      password :
      {
        check : "String",
        nullable : true
      },
  
  
      /**
       * The state that the request is in, while being processed.
       */
      state :
      {
        check : [ "configured", "queued", "sending", "receiving", "completed", "aborted", "timeout", "failed" ],
        init : "configured",
        apply : "_applyState",
        event : "changeState"
      },
  
  
      /**
       * Response type of request.
       *
       * The response type is a MIME type, default is text/plain. Other supported
       * MIME types are text/javascript, text/html, application/json,
       * application/xml.
       */
      responseType :
      {
        check : [ "text/plain", "text/javascript", "application/json", "application/xml", "text/html" ],
        init : "text/plain",
        apply : "_applyResponseType"
      },
  
  
      /**
       * Number of milliseconds before the request is being timed out.
       *
       * If this property is null, the timeout for the request comes is the
       * qx.io.remote.RequestQueue's property defaultTimeout.
       */
      timeout :
      {
        check : "Integer",
        nullable : true
      },
  
  
      /**
       * Prohibit request from being cached.
       *
       * Setting the value to <i>true</i> adds a parameter "nocache" to the
       * request URL with a value of the current time, as well as adding request
       * headers Pragma:no-cache and Cache-Control:no-cache.
       *
       * Setting the value to <i>false</i> removes the parameter and request
       * headers.
       *
       * As a special case, this property may be set to the string value
       * "no-url-params-on-post" which will prevent the nocache parameter from
       * being added to the URL if the POST method is used but will still add
       * the Pragma and Cache-Control headers.  This is useful if your backend
       * does nasty things like mixing parameters specified in the URL into
       * form fields in the POST request.  (One example of this nasty behavior
       * is known as "mixed mode" in Oracle, as described here:
       * http://docs.oracle.com/cd/B32110_01/web.1013/b28963/concept.htm#i1005684)
       */
      prohibitCaching :
      {
        check : function(v)
        {
          return typeof v == "boolean" || v === "no-url-params-on-post";
        },
        init : true,
        apply : "_applyProhibitCaching"
      },
  
  
      /**
       * Indicate that the request is cross domain.
       *
       * A request is cross domain if the request's URL points to a host other than
       * the local host. This switches the concrete implementation that is used for
       * sending the request from qx.io.remote.transport.XmlHttp to
       * qx.io.remote.transport.Script, because only the latter can handle cross
       * domain requests.
       */
      crossDomain :
      {
        check : "Boolean",
        init : false
      },
  
  
      /**
       * Indicate that the request will be used for a file upload.
       *
       * The request will be used for a file upload.  This switches the concrete
       * implementation that is used for sending the request from
       * qx.io.remote.transport.XmlHttp to qx.io.remote.IFrameTransport, because only
       * the latter can handle file uploads.
       */
      fileUpload :
      {
        check : "Boolean",
        init : false
      },
  
  
      /**
       * The transport instance used for the request.
       *
       * This is necessary to be able to abort an asynchronous request.
       */
      transport :
      {
        check : "qx.io.remote.Exchange",
        nullable : true
      },
  
  
      /**
       * Use Basic HTTP Authentication.
       */
      useBasicHttpAuth :
      {
        check : "Boolean",
        init : false
      },
  
      /**
       * If true and the responseType property is set to "application/json", getContent() will
       * return a Javascript map containing the JSON contents, i. e. the result qx.lang.Json.parse().
       * If false, the raw string data will be returned and the parsing must be done manually.
       * This is useful for special JSON dialects / extensions which are not supported by
       * qx.lang.Json.
       *
       * Note that this is currently only respected by qx.io.remote.transport.XmlHttp, i. e.
       * if the transport used is the one using XMLHttpRequests. The other transports
       * do not support JSON parsing, so this property has no effect.
       */
      parseJson :
      {
        check : "Boolean",
        init : true
      }
    },
  
  
  
  
    /*
    *****************************************************************************
       MEMBERS
    *****************************************************************************
    */
  
    members :
    {
  
      __requestHeaders : null,
      __urlParameters : null,
      __dataParameters : null,
      __formFields : null,
      __seqNum : null,
  
      /*
      ---------------------------------------------------------------------------
        CORE METHODS
      ---------------------------------------------------------------------------
      */
  
      /**
       * Schedule this request for transport to server.
       *
       * The request is added to the singleton class qx.io.remote.RequestQueue's
       * list of pending requests.
       *
       */
      send : function() {
        qx.io.remote.RequestQueue.getInstance().add(this);
      },
  
  
      /**
       * Abort sending this request.
       *
       * The request is removed from the singleton class qx.io.remote.RequestQueue's
       * list of pending events. If the request haven't been scheduled this
       * method is a noop.
       *
       */
      abort : function() {
        qx.io.remote.RequestQueue.getInstance().abort(this);
      },
  
  
      /**
       * Abort sending this request if it has not already been aborted.
       *
       */
      reset : function()
      {
        switch(this.getState())
        {
          case "sending":
          case "receiving":
            this.error("Aborting already sent request!");
  
            // no break
  
          case "queued":
            this.abort();
            break;
        }
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        STATE ALIASES
      ---------------------------------------------------------------------------
      */
  
      /**
       * Determine if this request is in the configured state.
       *
       * @return {Boolean} <true> if the request is in the configured state; <false> otherwise.
       */
      isConfigured : function() {
        return this.getState() === "configured";
      },
  
  
      /**
       * Determine if this request is in the queued state.
       *
       * @return {Boolean} <true> if the request is in the queued state; <false> otherwise.
       */
      isQueued : function() {
        return this.getState() === "queued";
      },
  
  
      /**
       * Determine if this request is in the sending state.
       *
       * @return {Boolean} <true> if the request is in the sending state; <false> otherwise.
       */
      isSending : function() {
        return this.getState() === "sending";
      },
  
  
      /**
       * Determine if this request is in the receiving state.
       *
       * @return {Boolean} <true> if the request is in the receiving state; <false> otherwise.
       */
      isReceiving : function() {
        return this.getState() === "receiving";
      },
  
  
      /**
       * Determine if this request is in the completed state.
       *
       * @return {Boolean} <true> if the request is in the completed state; <false> otherwise.
       */
      isCompleted : function() {
        return this.getState() === "completed";
      },
  
  
      /**
       * Determine if this request is in the aborted state.
       *
       * @return {Boolean} <true> if the request is in the aborted state; <false> otherwise.
       */
      isAborted : function() {
        return this.getState() === "aborted";
      },
  
  
      /**
       * Determine if this request is in the timeout state.
       *
       * @return {Boolean} <true> if the request is in the timeout state; <false> otherwise.
       */
      isTimeout : function() {
        return this.getState() === "timeout";
      },
  
  
      /**
       * Determine if this request is in the failed state.
       *
       * @return {Boolean} <true> if the request is in the failed state; <false> otherwise.
       */
      isFailed : function() {
        return this.getState() === "failed";
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        EVENT HANDLER
      ---------------------------------------------------------------------------
      */
  
      /**
       * Dispatches a clone of the given event on this instance
       *
       * @param e {qx.event.type.Event} The original event
       */
      __forwardEvent : qx.event.GlobalError.observeMethod(function(e)
      {
        var clonedEvent = e.clone();
        clonedEvent.setTarget(this);
        this.dispatchEvent(clonedEvent);
      }),
  
  
  
      /**
       * Event handler called when the request enters the queued state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _onqueued : function(e)
      {
        // Modify internal state
        this.setState("queued");
  
        // Bubbling up
        this.__forwardEvent(e);
      },
  
  
      /**
       * Event handler called when the request enters the sending state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _onsending : function(e)
      {
        // Modify internal state
        this.setState("sending");
  
        // Bubbling up
        this.__forwardEvent(e);
      },
  
  
      /**
       * Event handler called when the request enters the receiving state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _onreceiving : function(e)
      {
        // Modify internal state
        this.setState("receiving");
  
        // Bubbling up
        this.__forwardEvent(e);
      },
  
  
      /**
       * Event handler called when the request enters the completed state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _oncompleted : function(e)
      {
        // Modify internal state
        this.setState("completed");
  
        // Bubbling up
        this.__forwardEvent(e);
  
        // Automatically dispose after event completion
        this.dispose();
      },
  
  
      /**
       * Event handler called when the request enters the aborted state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _onaborted : function(e)
      {
        // Modify internal state
        this.setState("aborted");
  
        // Bubbling up
        this.__forwardEvent(e);
  
        // Automatically dispose after event completion
        this.dispose();
      },
  
  
      /**
       * Event handler called when the request enters the timeout state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _ontimeout : function(e)
      {
        /*
          // User's handler can block until timeout.
          switch(this.getState())
          {
            // If we're no longer running...
            case "completed":
            case "timeout":
            case "aborted":
            case "failed":
              // then don't bubble up the timeout event
              return;
          }
  
  
      */  // Modify internal state
        this.setState("timeout");
  
        // Bubbling up
        this.__forwardEvent(e);
  
        // Automatically dispose after event completion
        this.dispose();
      },
  
  
      /**
       * Event handler called when the request enters the failed state.
       *
       * @param e {qx.event.type.Event} Event indicating state change
       */
      _onfailed : function(e)
      {
        // Modify internal state
        this.setState("failed");
  
        // Bubbling up
        this.__forwardEvent(e);
  
        // Automatically dispose after event completion
        this.dispose();
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        APPLY ROUTINES
      ---------------------------------------------------------------------------
      */
  
      // property apply
      _applyState : function(value, old)
      {
        if (qx.core.Environment.get("qx.debug"))
        {
          if (qx.core.Environment.get("qx.debug.io.remote")) {
            this.debug("State: " + value);
          }
        }
      },
  
  
      // property apply
      _applyProhibitCaching : function(value, old)
      {
        if (! value)
        {
          this.removeParameter("nocache");
          this.removeRequestHeader("Pragma");
          this.removeRequestHeader("Cache-Control");
          return;
        }
  
        // If value isn't "no-url-params-on-post" or this isn't a POST request
        if (value !== "no-url-params-on-post" ||
            this.getMethod() != "POST")
        {
          // ... then add a parameter to the URL to make it unique on each
          // request.  The actual id, "nocache" is irrelevant; it's the fact
          // that a (usually) different date is added to the URL on each request
          // that prevents caching.
          this.setParameter("nocache", new Date().valueOf());
        }
        else
        {
          // Otherwise, we don't want the nocache parameter in the URL.
          this.removeParameter("nocache");
        }
  
        // Add the HTTP 1.0 request to avoid use of a cache
        this.setRequestHeader("Pragma", "no-cache");
  
        // Add the HTTP 1.1 request to avoid use of a cache
        this.setRequestHeader("Cache-Control", "no-cache");
      },
  
  
      // property apply
      _applyMethod : function(value, old)
      {
        if (qx.io.remote.Request.methodAllowsRequestBody(value)) {
          this.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        } else {
          this.removeRequestHeader("Content-Type");
        }
  
        // Re-test the prohibit caching property.  We may need to add or remove
        // the "nocache" parameter.  We explicitly call the _apply method since
        // it wouldn't be called normally when setting the value to its already
        // existant value.
        var prohibitCaching = this.getProhibitCaching();
        this._applyProhibitCaching(prohibitCaching, prohibitCaching);
      },
  
  
      // property apply
      _applyResponseType : function(value, old) {
        this.setRequestHeader("X-Qooxdoo-Response-Type", value);
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        REQUEST HEADER
      ---------------------------------------------------------------------------
      */
  
      /**
       * Add a request header to the request.
       *
       * Example: request.setRequestHeader("Content-Type", "text/html")
       *
       * Please note: Some browsers, such as Safari 3 and 4, will capitalize
       * header field names. This is in accordance with RFC 2616[1], which states
       * that HTTP 1.1 header names are case-insensitive, so your server backend
       * should be case-agnostic when dealing with request headers.
       *
       * [1]<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">RFC 2616: HTTP Message Headers</a>
       *
       * @param vId {String} The identifier to use for this added header
       * @param vValue {String} The value to use for this added header
       */
      setRequestHeader : function(vId, vValue) {
        this.__requestHeaders[vId] = vValue;
      },
  
  
      /**
       * Remove a previously-added request header
       *
       * @param vId {String} The id of the header to be removed
       */
      removeRequestHeader : function(vId) {
        delete this.__requestHeaders[vId];
      },
  
  
      /**
       * Retrieve the value of a header which was previously set
       *
       * @param vId {String} The id of the header value being requested
       * @return {String} The value of the header with the specified id
       */
      getRequestHeader : function(vId) {
        return this.__requestHeaders[vId] || null;
      },
  
  
      /**
       * Return the object containing all of the headers which have been added.
       *
       * @return {Object} The returned object has as its property names each of the ids of headers
       *     which have been added, and as each property value, the value of the
       *     property corresponding to that id.
       */
      getRequestHeaders : function() {
        return this.__requestHeaders;
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        PARAMETERS
      ---------------------------------------------------------------------------
      */
  
      /**
       * Add a parameter to the request.
       *
       * @param vId {String}
       *   String identifier of the parameter to add.
       *
       * @param vValue {var}
       *   Value of parameter. May be a string (for one parameter) or an array
       *   of strings (for setting multiple parameter values with the same
       *   parameter name).
       *
       * @param bAsData {Boolean}
       *   If <i>false</i>, add the parameter to the URL.  If <i>true</i> then
       *   instead the parameters added by calls to this method will be combined
       *   into a string added as the request data, as if the entire set of
       *   parameters had been pre-build and passed to setData().
       *
       * Note: Parameters requested to be sent as data will be silently dropped
       *       if data is manually added via a call to setData().
       *
       * Note: Some transports, e.g. Script, do not support passing parameters
       *       as data.
       *
       */
      setParameter : function(vId, vValue, bAsData)
      {
        if (bAsData)
        {
          this.__dataParameters[vId] = vValue;
        }
        else
        {
          this.__urlParameters[vId] = vValue;
        }
      },
  
  
      /**
       * Remove a parameter from the request.
       *
       * @param vId {String}
       *   Identifier of the parameter to remove.
       *
       * @param bFromData {Boolean}
       *   If <i>false</i> then remove the parameter of the URL parameter list.
       *   If <i>true</i> then remove it from the list of parameters to be sent
       *   as request data.
       *
       */
      removeParameter : function(vId, bFromData)
      {
        if (bFromData)
        {
          delete this.__dataParameters[vId];
        }
        else
        {
          delete this.__urlParameters[vId];
        }
      },
  
  
      /**
       * Get a parameter in the request.
       *
       * @param vId {String}
       *   Identifier of the parameter to get.
       *
       * @param bFromData {Boolean}
       *   If <i>false</i> then retrieve the parameter from the URL parameter
       *   list. If <i>true</i> then retrieve it from the list of parameters to
       *   be sent as request data.
       *
       * @return {var}
       *   The requested parameter value
       *
       */
      getParameter : function(vId, bFromData)
      {
        if (bFromData)
        {
          return this.__dataParameters[vId] || null;
        }
        else
        {
          return this.__urlParameters[vId] || null;
        }
      },
  
  
      /**
       * Returns the object containing all parameters for the request.
       *
       * @param bFromData {Boolean}
       *   If <i>false</i> then retrieve the URL parameter list.
       *   If <i>true</i> then retrieve the data parameter list.
       *
       * @return {Object}
       *   The returned object has as its property names each of the ids of
       *   parameters which have been added, and as each property value, the
       *   value of the property corresponding to that id.
       */
      getParameters : function(bFromData)
      {
        return (bFromData ? this.__dataParameters : this.__urlParameters);
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        FORM FIELDS
      ---------------------------------------------------------------------------
      */
  
      /**
       * Add a form field to the POST request.
       *
       * NOTE: Adding any programmatic form fields using this method will switch the
       *       Transport implementation to IframeTransport.
       *
       * NOTE: Use of these programmatic form fields disallow use of synchronous
       *       requests and cross-domain requests.  Be sure that you do not need
       *       those features when setting these programmatic form fields.
       *
       * @param vId {String} String identifier of the form field to add.
       * @param vValue {String} Value of form field
       */
      setFormField : function(vId, vValue) {
        this.__formFields[vId] = vValue;
      },
  
  
      /**
       * Remove a form field from the POST request.
       *
       * @param vId {String} Identifier of the form field to remove.
       */
      removeFormField : function(vId) {
        delete this.__formFields[vId];
      },
  
  
      /**
       * Get a form field in the POST request.
       *
       * @param vId {String} Identifier of the form field to get.
       * @return {String|null} Value of form field or <code>null</code> if no value
       *    exists for the passed identifier.
       */
      getFormField : function(vId) {
        return this.__formFields[vId] || null;
      },
  
  
      /**
       * Returns the object containing all form fields for the POST request.
       *
       * @return {Object} The returned object has as its property names each of the ids of
       *     form fields which have been added, and as each property value, the value
       *     of the property corresponding to that id.
       */
      getFormFields : function() {
        return this.__formFields;
      },
  
  
      /**
       * Obtain the sequence (id) number used for this request
       *
       * @return {Integer} The sequence number of this request
       */
      getSequenceNumber : function() {
        return this.__seqNum;
      }
    },
  
  
  
  
    /*
    *****************************************************************************
       DESTRUCTOR
    *****************************************************************************
    */
  
    destruct : function()
    {
      this.setTransport(null);
      this.__requestHeaders = this.__urlParameters = this.__dataParameters =
        this.__formFields = null;
    }
  });
  