diff --git a/README.md b/README.md index b989434..50039f9 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,5 @@ page](https://github.com/driverdan/node-XMLHttpRequest/issues). * Synchronous requests don't set headers properly * Synchronous requests freeze node while waiting for response (But that's what you want, right? Stick with async!). * Some events are missing, such as abort -* getRequestHeader is case-sensitive * Cookies aren't persisted between requests * Missing XML support -* Missing basic auth diff --git a/lib/XMLHttpRequest.js b/lib/XMLHttpRequest.js index 187c485..80ce0ea 100644 --- a/lib/XMLHttpRequest.js +++ b/lib/XMLHttpRequest.js @@ -12,7 +12,6 @@ */ var Url = require("url"); -var spawn = require("child_process").spawn; var fs = require("fs"); exports.XMLHttpRequest = function() { @@ -42,7 +41,8 @@ exports.XMLHttpRequest = function() { "Accept": "*/*", }; - var headers = defaultHeaders; + var headers = {}; + var headersCase = {}; // These headers are not user setable. // The following are allowed but banned in the spec: @@ -110,6 +110,10 @@ exports.XMLHttpRequest = function() { 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 @@ -154,7 +158,7 @@ exports.XMLHttpRequest = function() { // Check for valid request method if (!isAllowedHttpMethod(method)) { - throw "SecurityError: Request method not allowed"; + throw new Error("SecurityError: Request method not allowed"); } settings = { @@ -179,23 +183,25 @@ exports.XMLHttpRequest = function() { }; /** - * Sets a header for the request. + * 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 "INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN"; + 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 "INVALID_STATE_ERR: send flag is true"; + throw new Error("INVALID_STATE_ERR: send flag is true"); } - headers[header] = value; + header = headersCase[header.toLowerCase()] || header; + headersCase[header.toLowerCase()] = header; + headers[header] = headers[header] ? headers[header] + ', ' + value : value; }; /** @@ -245,9 +251,8 @@ exports.XMLHttpRequest = function() { * @return string Returns the request header or empty string if not set */ this.getRequestHeader = function(name) { - // @TODO Make this case insensitive - if (typeof name === "string" && headers[name]) { - return headers[name]; + if (typeof name === "string" && headersCase[name.toLowerCase()]) { + return headers[headersCase[name.toLowerCase()]]; } return ""; @@ -260,11 +265,11 @@ exports.XMLHttpRequest = function() { */ this.send = function(data) { if (this.readyState !== this.OPENED) { - throw "INVALID_STATE_ERR: connection must be opened before send() is called"; + throw new Error("INVALID_STATE_ERR: connection must be opened before send() is called"); } if (sendFlag) { - throw "INVALID_STATE_ERR: send has already been called"; + throw new Error("INVALID_STATE_ERR: send has already been called"); } var ssl = false, local = false; @@ -284,18 +289,19 @@ exports.XMLHttpRequest = function() { break; case undefined: + case null: case "": host = "localhost"; break; default: - throw "Protocol not supported."; + throw new Error("Protocol not supported."); } // Load files off the local filesystem (file://) if (local) { if (settings.method !== "GET") { - throw "XMLHttpRequest: Only GET method is supported"; + throw new Error("XMLHttpRequest: Only GET method is supported"); } if (settings.async) { @@ -327,6 +333,13 @@ exports.XMLHttpRequest = function() { // 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)) { @@ -363,7 +376,8 @@ exports.XMLHttpRequest = function() { path: uri, method: settings.method, headers: headers, - agent: false + agent: false, + withCredentials: self.withCredentials }; var done = false; @@ -399,7 +413,8 @@ exports.XMLHttpRequest = function() { port: url.port, path: url.path, method: response.statusCode === 303 ? "GET" : settings.method, - headers: headers + headers: headers, + withCredentials: self.withCredentials }; // Issue the new request @@ -458,7 +473,7 @@ exports.XMLHttpRequest = function() { self.dispatchEvent("loadstart"); while(!done) { - require('deasync').sleep(100); + require('deasync').sleep(10); } } @@ -466,11 +481,12 @@ exports.XMLHttpRequest = function() { * Called when an error is encountered to deal with it. */ this.handleError = function(error) { - this.status = 503; + this.status = 0; this.statusText = error; this.responseText = error.stack; errorFlag = true; setState(this.DONE); + this.dispatchEvent('error'); }; /** @@ -483,6 +499,7 @@ exports.XMLHttpRequest = function() { } headers = defaultHeaders; + this.status = 0; this.responseText = ""; this.responseXML = ""; @@ -495,6 +512,7 @@ exports.XMLHttpRequest = function() { setState(this.DONE); } this.readyState = this.UNSENT; + this.dispatchEvent('abort'); }; /** diff --git a/package.json b/package.json index e73be7d..f9c41d1 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,13 @@ { "name": "xmlhttprequest", "description": "XMLHttpRequest for Node", - "version": "1.7.0", + "version": "1.8.0", "author": { "name": "Dan DeFelippi", "url": "http://driverdan.com" }, "keywords": ["xhr", "ajax"], - "licenses": [{ - "type": "MIT", - "url": "http://creativecommons.org/licenses/MIT/" - }], + "license": "MIT", "repository": { "type": "git", "url": "git://github.com/driverdan/node-XMLHttpRequest.git" diff --git a/tests/test-headers.js b/tests/test-headers.js index 76454f1..23a419e 100644 --- a/tests/test-headers.js +++ b/tests/test-headers.js @@ -56,12 +56,16 @@ try { xhr.open("GET", "http://localhost:8000/"); // Valid header xhr.setRequestHeader("X-Test", "Foobar"); + xhr.setRequestHeader("X-Test2", "Foobar1"); + xhr.setRequestHeader("X-Test2", "Foobar2"); // Invalid header xhr.setRequestHeader("Content-Length", 0); // Allowed header outside of specs xhr.setRequestHeader("user-agent", "node-XMLHttpRequest-test"); // Test getRequestHeader assert.equal("Foobar", xhr.getRequestHeader("X-Test")); + assert.equal("Foobar", xhr.getRequestHeader("x-tEST")); + assert.equal("Foobar1, Foobar2", xhr.getRequestHeader("x-test2")); // Test invalid header assert.equal("", xhr.getRequestHeader("Content-Length"));