| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 | 
							- /**
 
-  * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
 
-  *
 
-  * This can be used with JS designed for browsers to improve reuse of code and
 
-  * allow the use of existing libraries.
 
-  *
 
-  * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
 
-  *
 
-  * @author Dan DeFelippi <dan@driverdan.com>
 
-  * @contributor David Ellis <d.f.ellis@ieee.org>
 
-  * @license MIT
 
-  */
 
- var Url = require("url");
 
- var spawn = require("child_process").spawn;
 
- var fs = require("fs");
 
- exports.XMLHttpRequest = function() {
 
-   "use strict";
 
-   /**
 
-    * Private variables
 
-    */
 
-   var self = this;
 
-   var http = require("http");
 
-   var https = require("https");
 
-   // Holds http.js objects
 
-   var request;
 
-   var response;
 
-   // Request settings
 
-   var settings = {};
 
-   // Disable header blacklist.
 
-   // Not part of XHR specs.
 
-   var disableHeaderCheck = false;
 
-   // Set some default headers
 
-   var defaultHeaders = {
 
-     "User-Agent": "node-XMLHttpRequest",
 
-     "Accept": "*/*",
 
-   };
 
-   var headers = {};
 
-   var headersCase = {};
 
-   // These headers are not user setable.
 
-   // The following are allowed but banned in the spec:
 
-   // * user-agent
 
-   var forbiddenRequestHeaders = [
 
-     "accept-charset",
 
-     "accept-encoding",
 
-     "access-control-request-headers",
 
-     "access-control-request-method",
 
-     "connection",
 
-     "content-length",
 
-     "content-transfer-encoding",
 
-     "cookie",
 
-     "cookie2",
 
-     "date",
 
-     "expect",
 
-     "host",
 
-     "keep-alive",
 
-     "origin",
 
-     "referer",
 
-     "te",
 
-     "trailer",
 
-     "transfer-encoding",
 
-     "upgrade",
 
-     "via"
 
-   ];
 
-   // These request methods are not allowed
 
-   var forbiddenRequestMethods = [
 
-     "TRACE",
 
-     "TRACK",
 
-     "CONNECT"
 
-   ];
 
-   // Send flag
 
-   var sendFlag = false;
 
-   // Error flag, used when errors occur or abort is called
 
-   var errorFlag = false;
 
-   // Event listeners
 
-   var listeners = {};
 
-   /**
 
-    * Constants
 
-    */
 
-   this.UNSENT = 0;
 
-   this.OPENED = 1;
 
-   this.HEADERS_RECEIVED = 2;
 
-   this.LOADING = 3;
 
-   this.DONE = 4;
 
-   /**
 
-    * Public vars
 
-    */
 
-   // Current state
 
-   this.readyState = this.UNSENT;
 
-   // default ready state change handler in case one is not set or is set late
 
-   this.onreadystatechange = null;
 
-   // Result & response
 
-   this.responseText = "";
 
-   this.responseXML = "";
 
-   this.status = null;
 
-   this.statusText = null;
 
-   
 
-   // Whether cross-site Access-Control requests should be made using
 
-   // credentials such as cookies or authorization headers
 
-   this.withCredentials = false;
 
-   /**
 
-    * Private methods
 
-    */
 
-   /**
 
-    * Check if the specified header is allowed.
 
-    *
 
-    * @param string header Header to validate
 
-    * @return boolean False if not allowed, otherwise true
 
-    */
 
-   var isAllowedHttpHeader = function(header) {
 
-     return disableHeaderCheck || (header && forbiddenRequestHeaders.indexOf(header.toLowerCase()) === -1);
 
-   };
 
-   /**
 
-    * Check if the specified method is allowed.
 
-    *
 
-    * @param string method Request method to validate
 
-    * @return boolean False if not allowed, otherwise true
 
-    */
 
-   var isAllowedHttpMethod = function(method) {
 
-     return (method && forbiddenRequestMethods.indexOf(method) === -1);
 
-   };
 
-   /**
 
-    * Public methods
 
-    */
 
-   /**
 
-    * Open the connection. Currently supports local server requests.
 
-    *
 
-    * @param string method Connection method (eg GET, POST)
 
-    * @param string url URL for the connection.
 
-    * @param boolean async Asynchronous connection. Default is true.
 
-    * @param string user Username for basic authentication (optional)
 
-    * @param string password Password for basic authentication (optional)
 
-    */
 
-   this.open = function(method, url, async, user, password) {
 
-     this.abort();
 
-     errorFlag = false;
 
-     // Check for valid request method
 
-     if (!isAllowedHttpMethod(method)) {
 
-       throw new Error("SecurityError: Request method not allowed");
 
-     }
 
-     settings = {
 
-       "method": method,
 
-       "url": url.toString(),
 
-       "async": (typeof async !== "boolean" ? true : async),
 
-       "user": user || null,
 
-       "password": password || null
 
-     };
 
-     setState(this.OPENED);
 
-   };
 
-   /**
 
-    * Disables or enables isAllowedHttpHeader() check the request. Enabled by default.
 
-    * This does not conform to the W3C spec.
 
-    *
 
-    * @param boolean state Enable or disable header checking.
 
-    */
 
-   this.setDisableHeaderCheck = function(state) {
 
-     disableHeaderCheck = state;
 
-   };
 
-   /**
 
-    * Sets a header for the request or appends the value if one is already set.
 
-    *
 
-    * @param string header Header name
 
-    * @param string value Header value
 
-    */
 
-   this.setRequestHeader = function(header, value) {
 
-     if (this.readyState !== this.OPENED) {
 
-       throw new Error("INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN");
 
-     }
 
-     if (!isAllowedHttpHeader(header)) {
 
-       console.warn("Refused to set unsafe header \"" + header + "\"");
 
-       return;
 
-     }
 
-     if (sendFlag) {
 
-       throw new Error("INVALID_STATE_ERR: send flag is true");
 
-     }
 
-     header = headersCase[header.toLowerCase()] || header;
 
-     headersCase[header.toLowerCase()] = header;
 
-     headers[header] = headers[header] ? headers[header] + ', ' + value : value;
 
-   };
 
-   /**
 
-    * Gets a header from the server response.
 
-    *
 
-    * @param string header Name of header to get.
 
-    * @return string Text of the header or null if it doesn't exist.
 
-    */
 
-   this.getResponseHeader = function(header) {
 
-     if (typeof header === "string"
 
-       && this.readyState > this.OPENED
 
-       && response
 
-       && response.headers
 
-       && response.headers[header.toLowerCase()]
 
-       && !errorFlag
 
-     ) {
 
-       return response.headers[header.toLowerCase()];
 
-     }
 
-     return null;
 
-   };
 
-   /**
 
-    * Gets all the response headers.
 
-    *
 
-    * @return string A string with all response headers separated by CR+LF
 
-    */
 
-   this.getAllResponseHeaders = function() {
 
-     if (this.readyState < this.HEADERS_RECEIVED || errorFlag) {
 
-       return "";
 
-     }
 
-     var result = "";
 
-     for (var i in response.headers) {
 
-       // Cookie headers are excluded
 
-       if (i !== "set-cookie" && i !== "set-cookie2") {
 
-         result += i + ": " + response.headers[i] + "\r\n";
 
-       }
 
-     }
 
-     return result.substr(0, result.length - 2);
 
-   };
 
-   /**
 
-    * Gets a request header
 
-    *
 
-    * @param string name Name of header to get
 
-    * @return string Returns the request header or empty string if not set
 
-    */
 
-   this.getRequestHeader = function(name) {
 
-     if (typeof name === "string" && headersCase[name.toLowerCase()]) {
 
-       return headers[headersCase[name.toLowerCase()]];
 
-     }
 
-     return "";
 
-   };
 
-   /**
 
-    * Sends the request to the server.
 
-    *
 
-    * @param string data Optional data to send as request body.
 
-    */
 
-   this.send = function(data) {
 
-     if (this.readyState !== this.OPENED) {
 
-       throw new Error("INVALID_STATE_ERR: connection must be opened before send() is called");
 
-     }
 
-     if (sendFlag) {
 
-       throw new Error("INVALID_STATE_ERR: send has already been called");
 
-     }
 
-     var ssl = false, local = false;
 
-     var url = Url.parse(settings.url);
 
-     var host;
 
-     // Determine the server
 
-     switch (url.protocol) {
 
-       case "https:":
 
-         ssl = true;
 
-         // SSL & non-SSL both need host, no break here.
 
-       case "http:":
 
-         host = url.hostname;
 
-         break;
 
-       case "file:":
 
-         local = true;
 
-         break;
 
-       case undefined:
 
-       case null:
 
-       case "":
 
-         host = "localhost";
 
-         break;
 
-       default:
 
-         throw new Error("Protocol not supported.");
 
-     }
 
-     // Load files off the local filesystem (file://)
 
-     if (local) {
 
-       if (settings.method !== "GET") {
 
-         throw new Error("XMLHttpRequest: Only GET method is supported");
 
-       }
 
-       if (settings.async) {
 
-         fs.readFile(url.pathname, "utf8", function(error, data) {
 
-           if (error) {
 
-             self.handleError(error);
 
-           } else {
 
-             self.status = 200;
 
-             self.responseText = data;
 
-             setState(self.DONE);
 
-           }
 
-         });
 
-       } else {
 
-         try {
 
-           this.responseText = fs.readFileSync(url.pathname, "utf8");
 
-           this.status = 200;
 
-           setState(self.DONE);
 
-         } catch(e) {
 
-           this.handleError(e);
 
-         }
 
-       }
 
-       return;
 
-     }
 
-     // Default to port 80. If accessing localhost on another port be sure
 
-     // to use http://localhost:port/path
 
-     var port = url.port || (ssl ? 443 : 80);
 
-     // Add query string if one is used
 
-     var uri = url.pathname + (url.search ? url.search : "");
 
-     // Set the defaults if they haven't been set
 
-     for (var name in defaultHeaders) {
 
-       if (!headersCase[name.toLowerCase()]) {
 
-         headers[name] = defaultHeaders[name];
 
-       }
 
-     }
 
-     // Set the Host header or the server may reject the request
 
-     headers.Host = host;
 
-     if (!((ssl && port === 443) || port === 80)) {
 
-       headers.Host += ":" + url.port;
 
-     }
 
-     // Set Basic Auth if necessary
 
-     if (settings.user) {
 
-       if (typeof settings.password === "undefined") {
 
-         settings.password = "";
 
-       }
 
-       var authBuf = new Buffer(settings.user + ":" + settings.password);
 
-       headers.Authorization = "Basic " + authBuf.toString("base64");
 
-     }
 
-     // Set content length header
 
-     if (settings.method === "GET" || settings.method === "HEAD") {
 
-       data = null;
 
-     } else if (data) {
 
-       headers["Content-Length"] = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
 
-       if (!headers["Content-Type"]) {
 
-         headers["Content-Type"] = "text/plain;charset=UTF-8";
 
-       }
 
-     } else if (settings.method === "POST") {
 
-       // For a post with no data set Content-Length: 0.
 
-       // This is required by buggy servers that don't meet the specs.
 
-       headers["Content-Length"] = 0;
 
-     }
 
-     var options = {
 
-       host: host,
 
-       port: port,
 
-       path: uri,
 
-       method: settings.method,
 
-       headers: headers,
 
-       agent: false,
 
-       withCredentials: self.withCredentials
 
-     };
 
-     // Reset error flag
 
-     errorFlag = false;
 
-     // Handle async requests
 
-     if (settings.async) {
 
-       // Use the proper protocol
 
-       var doRequest = ssl ? https.request : http.request;
 
-       // Request is being sent, set send flag
 
-       sendFlag = true;
 
-       // As per spec, this is called here for historical reasons.
 
-       self.dispatchEvent("readystatechange");
 
-       // Handler for the response
 
-       var responseHandler = function responseHandler(resp) {
 
-         // Set response var to the response we got back
 
-         // This is so it remains accessable outside this scope
 
-         response = resp;
 
-         // Check for redirect
 
-         // @TODO Prevent looped redirects
 
-         if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 303 || response.statusCode === 307) {
 
-           // Change URL to the redirect location
 
-           settings.url = response.headers.location;
 
-           var url = Url.parse(settings.url);
 
-           // Set host var in case it's used later
 
-           host = url.hostname;
 
-           // Options for the new request
 
-           var newOptions = {
 
-             hostname: url.hostname,
 
-             port: url.port,
 
-             path: url.path,
 
-             method: response.statusCode === 303 ? "GET" : settings.method,
 
-             headers: headers,
 
-             withCredentials: self.withCredentials
 
-           };
 
-           // Issue the new request
 
-           request = doRequest(newOptions, responseHandler).on("error", errorHandler);
 
-           request.end();
 
-           // @TODO Check if an XHR event needs to be fired here
 
-           return;
 
-         }
 
-         response.setEncoding("utf8");
 
-         setState(self.HEADERS_RECEIVED);
 
-         self.status = response.statusCode;
 
-         response.on("data", function(chunk) {
 
-           // Make sure there's some data
 
-           if (chunk) {
 
-             self.responseText += chunk;
 
-           }
 
-           // Don't emit state changes if the connection has been aborted.
 
-           if (sendFlag) {
 
-             setState(self.LOADING);
 
-           }
 
-         });
 
-         response.on("end", function() {
 
-           if (sendFlag) {
 
-             // Discard the end event if the connection has been aborted
 
-             setState(self.DONE);
 
-             sendFlag = false;
 
-           }
 
-         });
 
-         response.on("error", function(error) {
 
-           self.handleError(error);
 
-         });
 
-       };
 
-       // Error handler for the request
 
-       var errorHandler = function errorHandler(error) {
 
-         self.handleError(error);
 
-       };
 
-       // Create the request
 
-       request = doRequest(options, responseHandler).on("error", errorHandler);
 
-       // Node 0.4 and later won't accept empty data. Make sure it's needed.
 
-       if (data) {
 
-         request.write(data);
 
-       }
 
-       request.end();
 
-       self.dispatchEvent("loadstart");
 
-     } else { // Synchronous
 
-       // Create a temporary file for communication with the other Node process
 
-       var contentFile = ".node-xmlhttprequest-content-" + process.pid;
 
-       var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
 
-       fs.writeFileSync(syncFile, "", "utf8");
 
-       // The async request the other Node process executes
 
-       var execString = "var http = require('http'), https = require('https'), fs = require('fs');"
 
-         + "var doRequest = http" + (ssl ? "s" : "") + ".request;"
 
-         + "var options = " + JSON.stringify(options) + ";"
 
-         + "var responseText = '';"
 
-         + "var req = doRequest(options, function(response) {"
 
-         + "response.setEncoding('utf8');"
 
-         + "response.on('data', function(chunk) {"
 
-         + "  responseText += chunk;"
 
-         + "});"
 
-         + "response.on('end', function() {"
 
-         + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText}}), 'utf8');"
 
-         + "fs.unlinkSync('" + syncFile + "');"
 
-         + "});"
 
-         + "response.on('error', function(error) {"
 
-         + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: error}), 'utf8');"
 
-         + "fs.unlinkSync('" + syncFile + "');"
 
-         + "});"
 
-         + "}).on('error', function(error) {"
 
-         + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: error}), 'utf8');"
 
-         + "fs.unlinkSync('" + syncFile + "');"
 
-         + "});"
 
-         + (data ? "req.write('" + JSON.stringify(data).slice(1,-1).replace(/'/g, "\\'") + "');":"")
 
-         + "req.end();";
 
-       // Start the other Node Process, executing this string
 
-       var syncProc = spawn(process.argv[0], ["-e", execString]);
 
-       while(fs.existsSync(syncFile)) {
 
-         // Wait while the sync file is empty
 
-       }
 
-       var resp = JSON.parse(fs.readFileSync(contentFile, 'utf8'));
 
-       // Kill the child process once the file has data
 
-       syncProc.stdin.end();
 
-       // Remove the temporary file
 
-       fs.unlinkSync(contentFile);
 
-       if (resp.err) {
 
-         self.handleError(resp.err);
 
-       } else {
 
-         response = resp.data;
 
-         self.status = resp.data.statusCode;
 
-         self.responseText = resp.data.text;
 
-         setState(self.DONE);
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Called when an error is encountered to deal with it.
 
-    */
 
-   this.handleError = function(error) {
 
-     this.status = 0;
 
-     this.statusText = error;
 
-     this.responseText = error.stack;
 
-     errorFlag = true;
 
-     setState(this.DONE);
 
-     this.dispatchEvent('error');
 
-   };
 
-   /**
 
-    * Aborts a request.
 
-    */
 
-   this.abort = function() {
 
-     if (request) {
 
-       request.abort();
 
-       request = null;
 
-     }
 
-     headers = defaultHeaders;
 
-     this.status = 0;
 
-     this.responseText = "";
 
-     this.responseXML = "";
 
-     errorFlag = true;
 
-     if (this.readyState !== this.UNSENT
 
-         && (this.readyState !== this.OPENED || sendFlag)
 
-         && this.readyState !== this.DONE) {
 
-       sendFlag = false;
 
-       setState(this.DONE);
 
-     }
 
-     this.readyState = this.UNSENT;
 
-     this.dispatchEvent('abort');
 
-   };
 
-   /**
 
-    * Adds an event listener. Preferred method of binding to events.
 
-    */
 
-   this.addEventListener = function(event, callback) {
 
-     if (!(event in listeners)) {
 
-       listeners[event] = [];
 
-     }
 
-     // Currently allows duplicate callbacks. Should it?
 
-     listeners[event].push(callback);
 
-   };
 
-   /**
 
-    * Remove an event callback that has already been bound.
 
-    * Only works on the matching funciton, cannot be a copy.
 
-    */
 
-   this.removeEventListener = function(event, callback) {
 
-     if (event in listeners) {
 
-       // Filter will return a new array with the callback removed
 
-       listeners[event] = listeners[event].filter(function(ev) {
 
-         return ev !== callback;
 
-       });
 
-     }
 
-   };
 
-   /**
 
-    * Dispatch any events, including both "on" methods and events attached using addEventListener.
 
-    */
 
-   this.dispatchEvent = function(event) {
 
-     if (typeof self["on" + event] === "function") {
 
-       self["on" + event]();
 
-     }
 
-     if (event in listeners) {
 
-       for (var i = 0, len = listeners[event].length; i < len; i++) {
 
-         listeners[event][i].call(self);
 
-       }
 
-     }
 
-   };
 
-   /**
 
-    * Changes readyState and calls onreadystatechange.
 
-    *
 
-    * @param int state New state
 
-    */
 
-   var setState = function(state) {
 
-     if (state == self.LOADING || self.readyState !== state) {
 
-       self.readyState = state;
 
-       if (settings.async || self.readyState < self.OPENED || self.readyState === self.DONE) {
 
-         self.dispatchEvent("readystatechange");
 
-       }
 
-       if (self.readyState === self.DONE && !errorFlag) {
 
-         self.dispatchEvent("load");
 
-         // @TODO figure out InspectorInstrumentation::didLoadXHR(cookie)
 
-         self.dispatchEvent("loadend");
 
-       }
 
-     }
 
-   };
 
- };
 
 
  |