Skip to content
This repository was archived by the owner on Sep 3, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
54 changes: 36 additions & 18 deletions lib/XMLHttpRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/

var Url = require("url");
var spawn = require("child_process").spawn;
var fs = require("fs");

exports.XMLHttpRequest = function() {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand All @@ -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;
};

/**
Expand Down Expand Up @@ -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 "";
Expand All @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -363,7 +376,8 @@ exports.XMLHttpRequest = function() {
path: uri,
method: settings.method,
headers: headers,
agent: false
agent: false,
withCredentials: self.withCredentials
};

var done = false;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -458,19 +473,20 @@ exports.XMLHttpRequest = function() {
self.dispatchEvent("loadstart");

while(!done) {
require('deasync').sleep(100);
require('deasync').sleep(10);
}

}
/**
* 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');
};

/**
Expand All @@ -483,6 +499,7 @@ exports.XMLHttpRequest = function() {
}

headers = defaultHeaders;
this.status = 0;
this.responseText = "";
this.responseXML = "";

Expand All @@ -495,6 +512,7 @@ exports.XMLHttpRequest = function() {
setState(this.DONE);
}
this.readyState = this.UNSENT;
this.dispatchEvent('abort');
};

/**
Expand Down
7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 4 additions & 0 deletions tests/test-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"));

Expand Down