737 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			737 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /**
 | |
|  * XmlHttpRequest implementation that uses TLS and flash SocketPool.
 | |
|  *
 | |
|  * @author Dave Longley
 | |
|  *
 | |
|  * Copyright (c) 2010-2013 Digital Bazaar, Inc.
 | |
|  */
 | |
| var forge = require('./forge');
 | |
| require('./socket');
 | |
| require('./http');
 | |
| 
 | |
| /* XHR API */
 | |
| var xhrApi = module.exports = forge.xhr = forge.xhr || {};
 | |
| 
 | |
| (function($) {
 | |
| 
 | |
| // logging category
 | |
| var cat = 'forge.xhr';
 | |
| 
 | |
| /*
 | |
| XMLHttpRequest interface definition from:
 | |
| http://www.w3.org/TR/XMLHttpRequest
 | |
| 
 | |
| interface XMLHttpRequest {
 | |
|   // event handler
 | |
|   attribute EventListener onreadystatechange;
 | |
| 
 | |
|   // state
 | |
|   const unsigned short UNSENT = 0;
 | |
|   const unsigned short OPENED = 1;
 | |
|   const unsigned short HEADERS_RECEIVED = 2;
 | |
|   const unsigned short LOADING = 3;
 | |
|   const unsigned short DONE = 4;
 | |
|   readonly attribute unsigned short readyState;
 | |
| 
 | |
|   // request
 | |
|   void open(in DOMString method, in DOMString url);
 | |
|   void open(in DOMString method, in DOMString url, in boolean async);
 | |
|   void open(in DOMString method, in DOMString url,
 | |
|             in boolean async, in DOMString user);
 | |
|   void open(in DOMString method, in DOMString url,
 | |
|             in boolean async, in DOMString user, in DOMString password);
 | |
|   void setRequestHeader(in DOMString header, in DOMString value);
 | |
|   void send();
 | |
|   void send(in DOMString data);
 | |
|   void send(in Document data);
 | |
|   void abort();
 | |
| 
 | |
|   // response
 | |
|   DOMString getAllResponseHeaders();
 | |
|   DOMString getResponseHeader(in DOMString header);
 | |
|   readonly attribute DOMString responseText;
 | |
|   readonly attribute Document responseXML;
 | |
|   readonly attribute unsigned short status;
 | |
|   readonly attribute DOMString statusText;
 | |
| };
 | |
| */
 | |
| 
 | |
| // readyStates
 | |
| var UNSENT = 0;
 | |
| var OPENED = 1;
 | |
| var HEADERS_RECEIVED = 2;
 | |
| var LOADING = 3;
 | |
| var DONE = 4;
 | |
| 
 | |
| // exceptions
 | |
| var INVALID_STATE_ERR = 11;
 | |
| var SYNTAX_ERR = 12;
 | |
| var SECURITY_ERR = 18;
 | |
| var NETWORK_ERR = 19;
 | |
| var ABORT_ERR = 20;
 | |
| 
 | |
| // private flash socket pool vars
 | |
| var _sp = null;
 | |
| var _policyPort = 0;
 | |
| var _policyUrl = null;
 | |
| 
 | |
| // default client (used if no special URL provided when creating an XHR)
 | |
| var _client = null;
 | |
| 
 | |
| // all clients including the default, key'd by full base url
 | |
| // (multiple cross-domain http clients are permitted so there may be more
 | |
| // than one client in this map)
 | |
| // TODO: provide optional clean up API for non-default clients
 | |
| var _clients = {};
 | |
| 
 | |
| // the default maximum number of concurrents connections per client
 | |
| var _maxConnections = 10;
 | |
| 
 | |
| var net = forge.net;
 | |
| var http = forge.http;
 | |
| 
 | |
| /**
 | |
|  * Initializes flash XHR support.
 | |
|  *
 | |
|  * @param options:
 | |
|  *   url: the default base URL to connect to if xhr URLs are relative,
 | |
|  *     ie: https://myserver.com.
 | |
|  *   flashId: the dom ID of the flash SocketPool.
 | |
|  *   policyPort: the port that provides the server's flash policy, 0 to use
 | |
|  *     the flash default.
 | |
|  *   policyUrl: the policy file URL to use instead of a policy port.
 | |
|  *   msie: true if browser is internet explorer, false if not.
 | |
|  *   connections: the maximum number of concurrent connections.
 | |
|  *   caCerts: a list of PEM-formatted certificates to trust.
 | |
|  *   cipherSuites: an optional array of cipher suites to use,
 | |
|  *     see forge.tls.CipherSuites.
 | |
|  *   verify: optional TLS certificate verify callback to use (see forge.tls
 | |
|  *     for details).
 | |
|  *   getCertificate: an optional callback used to get a client-side
 | |
|  *     certificate (see forge.tls for details).
 | |
|  *   getPrivateKey: an optional callback used to get a client-side private
 | |
|  *     key (see forge.tls for details).
 | |
|  *   getSignature: an optional callback used to get a client-side signature
 | |
|  *     (see forge.tls for details).
 | |
|  *   persistCookies: true to use persistent cookies via flash local storage,
 | |
|  *     false to only keep cookies in javascript.
 | |
|  *   primeTlsSockets: true to immediately connect TLS sockets on their
 | |
|  *     creation so that they will cache TLS sessions for reuse.
 | |
|  */
 | |
| xhrApi.init = function(options) {
 | |
|   forge.log.debug(cat, 'initializing', options);
 | |
| 
 | |
|   // update default policy port and max connections
 | |
|   _policyPort = options.policyPort || _policyPort;
 | |
|   _policyUrl = options.policyUrl || _policyUrl;
 | |
|   _maxConnections = options.connections || _maxConnections;
 | |
| 
 | |
|   // create the flash socket pool
 | |
|   _sp = net.createSocketPool({
 | |
|     flashId: options.flashId,
 | |
|     policyPort: _policyPort,
 | |
|     policyUrl: _policyUrl,
 | |
|     msie: options.msie || false
 | |
|   });
 | |
| 
 | |
|   // create default http client
 | |
|   _client = http.createClient({
 | |
|     url: options.url || (
 | |
|       window.location.protocol + '//' + window.location.host),
 | |
|     socketPool: _sp,
 | |
|     policyPort: _policyPort,
 | |
|     policyUrl: _policyUrl,
 | |
|     connections: options.connections || _maxConnections,
 | |
|     caCerts: options.caCerts,
 | |
|     cipherSuites: options.cipherSuites,
 | |
|     persistCookies: options.persistCookies || true,
 | |
|     primeTlsSockets: options.primeTlsSockets || false,
 | |
|     verify: options.verify,
 | |
|     getCertificate: options.getCertificate,
 | |
|     getPrivateKey: options.getPrivateKey,
 | |
|     getSignature: options.getSignature
 | |
|   });
 | |
|   _clients[_client.url.full] = _client;
 | |
| 
 | |
|   forge.log.debug(cat, 'ready');
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Called to clean up the clients and socket pool.
 | |
|  */
 | |
| xhrApi.cleanup = function() {
 | |
|   // destroy all clients
 | |
|   for(var key in _clients) {
 | |
|     _clients[key].destroy();
 | |
|   }
 | |
|   _clients = {};
 | |
|   _client = null;
 | |
| 
 | |
|   // destroy socket pool
 | |
|   _sp.destroy();
 | |
|   _sp = null;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Sets a cookie.
 | |
|  *
 | |
|  * @param cookie the cookie with parameters:
 | |
|  *   name: the name of the cookie.
 | |
|  *   value: the value of the cookie.
 | |
|  *   comment: an optional comment string.
 | |
|  *   maxAge: the age of the cookie in seconds relative to created time.
 | |
|  *   secure: true if the cookie must be sent over a secure protocol.
 | |
|  *   httpOnly: true to restrict access to the cookie from javascript
 | |
|  *     (inaffective since the cookies are stored in javascript).
 | |
|  *   path: the path for the cookie.
 | |
|  *   domain: optional domain the cookie belongs to (must start with dot).
 | |
|  *   version: optional version of the cookie.
 | |
|  *   created: creation time, in UTC seconds, of the cookie.
 | |
|  */
 | |
| xhrApi.setCookie = function(cookie) {
 | |
|   // default cookie expiration to never
 | |
|   cookie.maxAge = cookie.maxAge || -1;
 | |
| 
 | |
|   // if the cookie's domain is set, use the appropriate client
 | |
|   if(cookie.domain) {
 | |
|     // add the cookies to the applicable domains
 | |
|     for(var key in _clients) {
 | |
|       var client = _clients[key];
 | |
|       if(http.withinCookieDomain(client.url, cookie) &&
 | |
|         client.secure === cookie.secure) {
 | |
|         client.setCookie(cookie);
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     // use the default domain
 | |
|     // FIXME: should a null domain cookie be added to all clients? should
 | |
|     // this be an option?
 | |
|     _client.setCookie(cookie);
 | |
|   }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Gets a cookie.
 | |
|  *
 | |
|  * @param name the name of the cookie.
 | |
|  * @param path an optional path for the cookie (if there are multiple cookies
 | |
|  *          with the same name but different paths).
 | |
|  * @param domain an optional domain for the cookie (if not using the default
 | |
|  *          domain).
 | |
|  *
 | |
|  * @return the cookie, cookies (if multiple matches), or null if not found.
 | |
|  */
 | |
| xhrApi.getCookie = function(name, path, domain) {
 | |
|   var rval = null;
 | |
| 
 | |
|   if(domain) {
 | |
|     // get the cookies from the applicable domains
 | |
|     for(var key in _clients) {
 | |
|       var client = _clients[key];
 | |
|       if(http.withinCookieDomain(client.url, domain)) {
 | |
|         var cookie = client.getCookie(name, path);
 | |
|         if(cookie !== null) {
 | |
|           if(rval === null) {
 | |
|             rval = cookie;
 | |
|           } else if(!forge.util.isArray(rval)) {
 | |
|             rval = [rval, cookie];
 | |
|           } else {
 | |
|             rval.push(cookie);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     // get cookie from default domain
 | |
|     rval = _client.getCookie(name, path);
 | |
|   }
 | |
| 
 | |
|   return rval;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Removes a cookie.
 | |
|  *
 | |
|  * @param name the name of the cookie.
 | |
|  * @param path an optional path for the cookie (if there are multiple cookies
 | |
|  *          with the same name but different paths).
 | |
|  * @param domain an optional domain for the cookie (if not using the default
 | |
|  *          domain).
 | |
|  *
 | |
|  * @return true if a cookie was removed, false if not.
 | |
|  */
 | |
| xhrApi.removeCookie = function(name, path, domain) {
 | |
|   var rval = false;
 | |
| 
 | |
|   if(domain) {
 | |
|     // remove the cookies from the applicable domains
 | |
|     for(var key in _clients) {
 | |
|       var client = _clients[key];
 | |
|       if(http.withinCookieDomain(client.url, domain)) {
 | |
|         if(client.removeCookie(name, path)) {
 | |
|            rval = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     // remove cookie from default domain
 | |
|     rval = _client.removeCookie(name, path);
 | |
|   }
 | |
| 
 | |
|   return rval;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Creates a new XmlHttpRequest. By default the base URL, flash policy port,
 | |
|  * etc, will be used. However, an XHR can be created to point at another
 | |
|  * cross-domain URL.
 | |
|  *
 | |
|  * @param options:
 | |
|  *   logWarningOnError: If true and an HTTP error status code is received then
 | |
|  *     log a warning, otherwise log a verbose message.
 | |
|  *   verbose: If true be very verbose in the output including the response
 | |
|  *     event and response body, otherwise only include status, timing, and
 | |
|  *     data size.
 | |
|  *   logError: a multi-var log function for warnings that takes the log
 | |
|  *     category as the first var.
 | |
|  *   logWarning: a multi-var log function for warnings that takes the log
 | |
|  *     category as the first var.
 | |
|  *   logDebug: a multi-var log function for warnings that takes the log
 | |
|  *     category as the first var.
 | |
|  *   logVerbose: a multi-var log function for warnings that takes the log
 | |
|  *     category as the first var.
 | |
|  *   url: the default base URL to connect to if xhr URLs are relative,
 | |
|  *     eg: https://myserver.com, and note that the following options will be
 | |
|  *     ignored if the URL is absent or the same as the default base URL.
 | |
|  *   policyPort: the port that provides the server's flash policy, 0 to use
 | |
|  *     the flash default.
 | |
|  *   policyUrl: the policy file URL to use instead of a policy port.
 | |
|  *   connections: the maximum number of concurrent connections.
 | |
|  *   caCerts: a list of PEM-formatted certificates to trust.
 | |
|  *   cipherSuites: an optional array of cipher suites to use, see
 | |
|  *     forge.tls.CipherSuites.
 | |
|  *   verify: optional TLS certificate verify callback to use (see forge.tls
 | |
|  *     for details).
 | |
|  *   getCertificate: an optional callback used to get a client-side
 | |
|  *     certificate.
 | |
|  *   getPrivateKey: an optional callback used to get a client-side private key.
 | |
|  *   getSignature: an optional callback used to get a client-side signature.
 | |
|  *   persistCookies: true to use persistent cookies via flash local storage,
 | |
|  *     false to only keep cookies in javascript.
 | |
|  *   primeTlsSockets: true to immediately connect TLS sockets on their
 | |
|  *     creation so that they will cache TLS sessions for reuse.
 | |
|  *
 | |
|  * @return the XmlHttpRequest.
 | |
|  */
 | |
| xhrApi.create = function(options) {
 | |
|   // set option defaults
 | |
|   options = $.extend({
 | |
|     logWarningOnError: true,
 | |
|     verbose: false,
 | |
|     logError: function() {},
 | |
|     logWarning: function() {},
 | |
|     logDebug: function() {},
 | |
|     logVerbose: function() {},
 | |
|     url: null
 | |
|   }, options || {});
 | |
| 
 | |
|   // private xhr state
 | |
|   var _state = {
 | |
|     // the http client to use
 | |
|     client: null,
 | |
|     // request storage
 | |
|     request: null,
 | |
|     // response storage
 | |
|     response: null,
 | |
|     // asynchronous, true if doing asynchronous communication
 | |
|     asynchronous: true,
 | |
|     // sendFlag, true if send has been called
 | |
|     sendFlag: false,
 | |
|     // errorFlag, true if a network error occurred
 | |
|     errorFlag: false
 | |
|   };
 | |
| 
 | |
|   // private log functions
 | |
|   var _log = {
 | |
|     error: options.logError || forge.log.error,
 | |
|     warning: options.logWarning || forge.log.warning,
 | |
|     debug: options.logDebug || forge.log.debug,
 | |
|     verbose: options.logVerbose || forge.log.verbose
 | |
|   };
 | |
| 
 | |
|   // create public xhr interface
 | |
|   var xhr = {
 | |
|     // an EventListener
 | |
|     onreadystatechange: null,
 | |
|     // readonly, the current readyState
 | |
|     readyState: UNSENT,
 | |
|     // a string with the response entity-body
 | |
|     responseText: '',
 | |
|     // a Document for response entity-bodies that are XML
 | |
|     responseXML: null,
 | |
|     // readonly, returns the HTTP status code (i.e. 404)
 | |
|     status: 0,
 | |
|     // readonly, returns the HTTP status message (i.e. 'Not Found')
 | |
|     statusText: ''
 | |
|   };
 | |
| 
 | |
|   // determine which http client to use
 | |
|   if(options.url === null) {
 | |
|     // use default
 | |
|     _state.client = _client;
 | |
|   } else {
 | |
|     var url = http.parseUrl(options.url);
 | |
|     if(!url) {
 | |
|       var error = new Error('Invalid url.');
 | |
|       error.details = {
 | |
|         url: options.url
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     // find client
 | |
|     if(url.full in _clients) {
 | |
|       // client found
 | |
|       _state.client = _clients[url.full];
 | |
|     } else {
 | |
|       // create client
 | |
|       _state.client = http.createClient({
 | |
|         url: options.url,
 | |
|         socketPool: _sp,
 | |
|         policyPort: options.policyPort || _policyPort,
 | |
|         policyUrl: options.policyUrl || _policyUrl,
 | |
|         connections: options.connections || _maxConnections,
 | |
|         caCerts: options.caCerts,
 | |
|         cipherSuites: options.cipherSuites,
 | |
|         persistCookies: options.persistCookies || true,
 | |
|         primeTlsSockets: options.primeTlsSockets || false,
 | |
|         verify: options.verify,
 | |
|         getCertificate: options.getCertificate,
 | |
|         getPrivateKey: options.getPrivateKey,
 | |
|         getSignature: options.getSignature
 | |
|       });
 | |
|       _clients[url.full] = _state.client;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Opens the request. This method will create the HTTP request to send.
 | |
|    *
 | |
|    * @param method the HTTP method (i.e. 'GET').
 | |
|    * @param url the relative url (the HTTP request path).
 | |
|    * @param async always true, ignored.
 | |
|    * @param user always null, ignored.
 | |
|    * @param password always null, ignored.
 | |
|    */
 | |
|   xhr.open = function(method, url, async, user, password) {
 | |
|     // 1. validate Document if one is associated
 | |
|     // TODO: not implemented (not used yet)
 | |
| 
 | |
|     // 2. validate method token
 | |
|     // 3. change method to uppercase if it matches a known
 | |
|     // method (here we just require it to be uppercase, and
 | |
|     // we do not allow the standard methods)
 | |
|     // 4. disallow CONNECT, TRACE, or TRACK with a security error
 | |
|     switch(method) {
 | |
|     case 'DELETE':
 | |
|     case 'GET':
 | |
|     case 'HEAD':
 | |
|     case 'OPTIONS':
 | |
|     case 'PATCH':
 | |
|     case 'POST':
 | |
|     case 'PUT':
 | |
|       // valid method
 | |
|       break;
 | |
|     case 'CONNECT':
 | |
|     case 'TRACE':
 | |
|     case 'TRACK':
 | |
|       throw new Error('CONNECT, TRACE and TRACK methods are disallowed');
 | |
|     default:
 | |
|       throw new Error('Invalid method: ' + method);
 | |
|     }
 | |
| 
 | |
|     // TODO: other validation steps in algorithm are not implemented
 | |
| 
 | |
|     // 19. set send flag to false
 | |
|     // set response body to null
 | |
|     // empty list of request headers
 | |
|     // set request method to given method
 | |
|     // set request URL
 | |
|     // set username, password
 | |
|     // set asychronous flag
 | |
|     _state.sendFlag = false;
 | |
|     xhr.responseText = '';
 | |
|     xhr.responseXML = null;
 | |
| 
 | |
|     // custom: reset status and statusText
 | |
|     xhr.status = 0;
 | |
|     xhr.statusText = '';
 | |
| 
 | |
|     // create the HTTP request
 | |
|     _state.request = http.createRequest({
 | |
|       method: method,
 | |
|       path: url
 | |
|     });
 | |
| 
 | |
|     // 20. set state to OPENED
 | |
|     xhr.readyState = OPENED;
 | |
| 
 | |
|     // 21. dispatch onreadystatechange
 | |
|     if(xhr.onreadystatechange) {
 | |
|        xhr.onreadystatechange();
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Adds an HTTP header field to the request.
 | |
|    *
 | |
|    * @param header the name of the header field.
 | |
|    * @param value the value of the header field.
 | |
|    */
 | |
|   xhr.setRequestHeader = function(header, value) {
 | |
|     // 1. if state is not OPENED or send flag is true, raise exception
 | |
|     if(xhr.readyState != OPENED || _state.sendFlag) {
 | |
|       throw new Error('XHR not open or sending');
 | |
|     }
 | |
| 
 | |
|     // TODO: other validation steps in spec aren't implemented
 | |
| 
 | |
|     // set header
 | |
|     _state.request.setField(header, value);
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Sends the request and any associated data.
 | |
|    *
 | |
|    * @param data a string or Document object to send, null to send no data.
 | |
|    */
 | |
|   xhr.send = function(data) {
 | |
|     // 1. if state is not OPENED or 2. send flag is true, raise
 | |
|     // an invalid state exception
 | |
|     if(xhr.readyState != OPENED || _state.sendFlag) {
 | |
|       throw new Error('XHR not open or sending');
 | |
|     }
 | |
| 
 | |
|     // 3. ignore data if method is GET or HEAD
 | |
|     if(data &&
 | |
|       _state.request.method !== 'GET' &&
 | |
|       _state.request.method !== 'HEAD') {
 | |
|       // handle non-IE case
 | |
|       if(typeof(XMLSerializer) !== 'undefined') {
 | |
|         if(data instanceof Document) {
 | |
|           var xs = new XMLSerializer();
 | |
|           _state.request.body = xs.serializeToString(data);
 | |
|         } else {
 | |
|           _state.request.body = data;
 | |
|         }
 | |
|       } else {
 | |
|         // poorly implemented IE case
 | |
|         if(typeof(data.xml) !== 'undefined') {
 | |
|           _state.request.body = data.xml;
 | |
|         } else {
 | |
|           _state.request.body = data;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // 4. release storage mutex (not used)
 | |
| 
 | |
|     // 5. set error flag to false
 | |
|     _state.errorFlag = false;
 | |
| 
 | |
|     // 6. if asynchronous is true (must be in this implementation)
 | |
| 
 | |
|     // 6.1 set send flag to true
 | |
|     _state.sendFlag = true;
 | |
| 
 | |
|     // 6.2 dispatch onreadystatechange
 | |
|     if(xhr.onreadystatechange) {
 | |
|       xhr.onreadystatechange();
 | |
|     }
 | |
| 
 | |
|     // create send options
 | |
|     var options = {};
 | |
|     options.request = _state.request;
 | |
|     options.headerReady = function(e) {
 | |
|       // make cookies available for ease of use/iteration
 | |
|       xhr.cookies = _state.client.cookies;
 | |
| 
 | |
|       // TODO: update document.cookie with any cookies where the
 | |
|       // script's domain matches
 | |
| 
 | |
|       // headers received
 | |
|       xhr.readyState = HEADERS_RECEIVED;
 | |
|       xhr.status = e.response.code;
 | |
|       xhr.statusText = e.response.message;
 | |
|       _state.response = e.response;
 | |
|       if(xhr.onreadystatechange) {
 | |
|         xhr.onreadystatechange();
 | |
|       }
 | |
|       if(!_state.response.aborted) {
 | |
|         // now loading body
 | |
|         xhr.readyState = LOADING;
 | |
|         if(xhr.onreadystatechange) {
 | |
|            xhr.onreadystatechange();
 | |
|         }
 | |
|       }
 | |
|     };
 | |
|     options.bodyReady = function(e) {
 | |
|       xhr.readyState = DONE;
 | |
|       var ct = e.response.getField('Content-Type');
 | |
|       // Note: this null/undefined check is done outside because IE
 | |
|       // dies otherwise on a "'null' is null" error
 | |
|       if(ct) {
 | |
|         if(ct.indexOf('text/xml') === 0 ||
 | |
|           ct.indexOf('application/xml') === 0 ||
 | |
|           ct.indexOf('+xml') !== -1) {
 | |
|           try {
 | |
|             var doc = new ActiveXObject('MicrosoftXMLDOM');
 | |
|             doc.async = false;
 | |
|             doc.loadXML(e.response.body);
 | |
|             xhr.responseXML = doc;
 | |
|           } catch(ex) {
 | |
|             var parser = new DOMParser();
 | |
|             xhr.responseXML = parser.parseFromString(ex.body, 'text/xml');
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       var length = 0;
 | |
|       if(e.response.body !== null) {
 | |
|         xhr.responseText = e.response.body;
 | |
|         length = e.response.body.length;
 | |
|       }
 | |
|       // build logging output
 | |
|       var req = _state.request;
 | |
|       var output =
 | |
|         req.method + ' ' + req.path + ' ' +
 | |
|         xhr.status + ' ' + xhr.statusText + ' ' +
 | |
|         length + 'B ' +
 | |
|         (e.request.connectTime + e.request.time + e.response.time) +
 | |
|         'ms';
 | |
|       var lFunc;
 | |
|       if(options.verbose) {
 | |
|         lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
 | |
|           _log.warning : _log.verbose;
 | |
|         lFunc(cat, output,
 | |
|           e, e.response.body ? '\n' + e.response.body : '\nNo content');
 | |
|       } else {
 | |
|         lFunc = (xhr.status >= 400 && options.logWarningOnError) ?
 | |
|           _log.warning : _log.debug;
 | |
|         lFunc(cat, output);
 | |
|       }
 | |
|       if(xhr.onreadystatechange) {
 | |
|         xhr.onreadystatechange();
 | |
|       }
 | |
|     };
 | |
|     options.error = function(e) {
 | |
|       var req = _state.request;
 | |
|       _log.error(cat, req.method + ' ' + req.path, e);
 | |
| 
 | |
|       // 1. set response body to null
 | |
|       xhr.responseText = '';
 | |
|       xhr.responseXML = null;
 | |
| 
 | |
|       // 2. set error flag to true (and reset status)
 | |
|       _state.errorFlag = true;
 | |
|       xhr.status = 0;
 | |
|       xhr.statusText = '';
 | |
| 
 | |
|       // 3. set state to done
 | |
|       xhr.readyState = DONE;
 | |
| 
 | |
|       // 4. asyc flag is always true, so dispatch onreadystatechange
 | |
|       if(xhr.onreadystatechange) {
 | |
|         xhr.onreadystatechange();
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     // 7. send request
 | |
|     _state.client.send(options);
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Aborts the request.
 | |
|    */
 | |
|   xhr.abort = function() {
 | |
|     // 1. abort send
 | |
|     // 2. stop network activity
 | |
|     _state.request.abort();
 | |
| 
 | |
|     // 3. set response to null
 | |
|     xhr.responseText = '';
 | |
|     xhr.responseXML = null;
 | |
| 
 | |
|     // 4. set error flag to true (and reset status)
 | |
|     _state.errorFlag = true;
 | |
|     xhr.status = 0;
 | |
|     xhr.statusText = '';
 | |
| 
 | |
|     // 5. clear user headers
 | |
|     _state.request = null;
 | |
|     _state.response = null;
 | |
| 
 | |
|     // 6. if state is DONE or UNSENT, or if OPENED and send flag is false
 | |
|     if(xhr.readyState === DONE || xhr.readyState === UNSENT ||
 | |
|      (xhr.readyState === OPENED && !_state.sendFlag)) {
 | |
|       // 7. set ready state to unsent
 | |
|       xhr.readyState = UNSENT;
 | |
|     } else {
 | |
|       // 6.1 set state to DONE
 | |
|       xhr.readyState = DONE;
 | |
| 
 | |
|       // 6.2 set send flag to false
 | |
|       _state.sendFlag = false;
 | |
| 
 | |
|       // 6.3 dispatch onreadystatechange
 | |
|       if(xhr.onreadystatechange) {
 | |
|         xhr.onreadystatechange();
 | |
|       }
 | |
| 
 | |
|       // 7. set state to UNSENT
 | |
|       xhr.readyState = UNSENT;
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Gets all response headers as a string.
 | |
|    *
 | |
|    * @return the HTTP-encoded response header fields.
 | |
|    */
 | |
|   xhr.getAllResponseHeaders = function() {
 | |
|     var rval = '';
 | |
|     if(_state.response !== null) {
 | |
|       var fields = _state.response.fields;
 | |
|       $.each(fields, function(name, array) {
 | |
|         $.each(array, function(i, value) {
 | |
|           rval += name + ': ' + value + '\r\n';
 | |
|         });
 | |
|       });
 | |
|     }
 | |
|     return rval;
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Gets a single header field value or, if there are multiple
 | |
|    * fields with the same name, a comma-separated list of header
 | |
|    * values.
 | |
|    *
 | |
|    * @return the header field value(s) or null.
 | |
|    */
 | |
|   xhr.getResponseHeader = function(header) {
 | |
|     var rval = null;
 | |
|     if(_state.response !== null) {
 | |
|       if(header in _state.response.fields) {
 | |
|         rval = _state.response.fields[header];
 | |
|         if(forge.util.isArray(rval)) {
 | |
|           rval = rval.join();
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return rval;
 | |
|   };
 | |
| 
 | |
|   return xhr;
 | |
| };
 | |
| 
 | |
| })(jQuery);
 |