Skip to content

Commit

Permalink
Forward approved preflight headers to request during XHR
Browse files Browse the repository at this point in the history
Fixes #1799.
  • Loading branch information
mbroadst authored and domenic committed Apr 24, 2017
1 parent 3a1c2e0 commit 380e2fe
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 1 deletion.
23 changes: 22 additions & 1 deletion lib/jsdom/living/xhr-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,25 @@ function updateRequestHeader(requestHeaders, header, newValue) {
}
}

function mergeHeaders(lhs, rhs) {
const rhsParts = rhs.split(",");
const lhsParts = lhs.split(",");
return rhsParts.concat(lhsParts.filter(p => rhsParts.indexOf(p) < 0)).join(",");
}

const simpleMethods = new Set(["GET", "HEAD", "POST"]);
const simpleHeaders = new Set(["accept", "accept-language", "content-language", "content-type"]);
const preflightHeaders = new Set([
"access-control-expose-headers",
"access-control-allow-headers",
"access-control-allow-credentials",
"access-control-allow-origin"
]);

exports.getRequestHeader = getRequestHeader;
exports.updateRequestHeader = updateRequestHeader;
exports.simpleHeaders = simpleHeaders;
exports.preflightHeaders = preflightHeaders;

// return a "request" client object or an event emitter matching the same behaviour for unsupported protocols
// the callback should be called with a "request" response object or an event emitter matching the same behaviour too
Expand Down Expand Up @@ -278,7 +291,15 @@ exports.createClient = function createClient(xhr) {
preflightClient.on("response", resp => {
if (resp.statusCode >= 200 && resp.statusCode <= 299) {
const realClient = doRequest();
realClient.on("response", res => client.emit("response", res));
realClient.on("response", res => {
for (const header in resp.headers) {
if (preflightHeaders.has(header)) {
res.headers[header] = res.headers.hasOwnProperty(header) ?
mergeHeaders(res.headers[header], resp.headers[header]) : resp.headers[header];
}
}
client.emit("response", res);
});
realClient.on("data", chunk => client.emit("data", chunk));
realClient.on("end", () => client.emit("end"));
realClient.on("abort", () => client.emit("abort"));
Expand Down
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require("./old-api/named-properties-tracker.js");
require("./old-api/resource-loading.js");
require("./old-api/utils.js");

require("./to-port-to-wpts/cors.js");
require("./to-port-to-wpts/css.js");
require("./to-port-to-wpts/jsonp.js");
require("./to-port-to-wpts/misc.js");
Expand Down
82 changes: 82 additions & 0 deletions test/to-port-to-wpts/cors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"use strict";
const { assert } = require("chai");
const { describe, specify, before, after } = require("mocha-sugar-free");
const { createServer } = require("../util.js");

const { JSDOM } = require("../..");

const routes = {
"/html": `<!DOCTYPE html><html>
<head><script>window.test = true;</script><script src="/js"></script></head>
<body></body>
</html>`,
"/cors": "test"
};

function createCORSServer() {
return createServer((req, res) => {
setTimeout(() => {
if (req.method === "OPTIONS") {
res.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": ["GET", "PUT", "DELETE", "POST", "OPTIONS"].join(", "),
"Access-Control-Allow-Headers": ["content-range", "authorization", "accept"].join(", ")
});
res.end();
} else {
const length = routes.hasOwnProperty(req.url) ? routes[req.url].length : 0;
res.writeHead(200, { "Content-Length": length });
res.end(routes[req.url]);
}
}, 200);
});
}

function createBaseServer() {
return createServer((req, res) => {
setTimeout(() => {
const length = routes.hasOwnProperty(req.url) ? routes[req.url].length : 0;
res.writeHead(200, { "Content-Length": length });
res.end(routes[req.url]);
}, 200);
});
}

describe("jsdom/cors", { skipIfBrowser: true }, () => {
let server;
let corsServer;
let host;
let corsHost;

before(() => {
return Promise.all([createBaseServer(), createCORSServer()])
.then(servers => {
server = servers[0];
corsServer = servers[1];
host = `http://127.0.0.1:${server.address().port}`;
corsHost = `http://127.0.0.1:${corsServer.address().port}`;
});
});

after(() => {
return Promise.all([server.destroy(), corsServer.destroy()]);
});

specify("preflight response headers should be forwarded", () => {
return JSDOM.fromURL(host + "/html").then(({ window }) => {
return new Promise((resolve, reject) => {
const xhr = new window.XMLHttpRequest();
xhr.onload = () => {
assert.equal(xhr.response, "test");
resolve();
};
xhr.onerror = () => reject(new Error("Network request failed (error)"));
xhr.ontimeout = () => reject(new Error("Network request failed (timeout)"));

xhr.open("GET", `${corsHost}/cors`, true);
xhr.setRequestHeader("Authorization", "Basic dGVzdGluZzpwYXNzd29yZA==");
xhr.send();
});
});
});
});

0 comments on commit 380e2fe

Please sign in to comment.