Skip to content

Commit

Permalink
Merge pull request #7017 from rtsao/crossorigin-attr
Browse files Browse the repository at this point in the history
Add script src origin check to ensure crossorigin attribute is only set when needed
  • Loading branch information
sokra committed Jun 28, 2018
2 parents b848ec5 + 5c4ffd5 commit e08399a
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 12 deletions.
26 changes: 19 additions & 7 deletions lib/web/JsonpMainTemplatePlugin.js
Expand Up @@ -154,15 +154,21 @@ class JsonpMainTemplatePlugin {
: "",
"script.charset = 'utf-8';",
`script.timeout = ${chunkLoadTimeout / 1000};`,
crossOriginLoading
? `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
: "",
`if (${mainTemplate.requireFn}.nc) {`,
Template.indent(
`script.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`
),
"}",
"script.src = jsonpScriptSrc(chunkId);",
crossOriginLoading
? Template.asString([
"if (script.src.indexOf(window.location.origin + '/') !== 0) {",
Template.indent(
`script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
),
"}"
])
: "",
"onScriptComplete = function (event) {",
Template.indent([
"// avoid mem leaks in IE.",
Expand Down Expand Up @@ -208,17 +214,23 @@ class JsonpMainTemplatePlugin {
? `link.type = ${JSON.stringify(jsonpScriptType)};`
: "",
"link.charset = 'utf-8';",
crossOriginLoading
? `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
: "",
`if (${mainTemplate.requireFn}.nc) {`,
Template.indent(
`link.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`
),
"}",
'link.rel = "preload";',
'link.as = "script";',
"link.href = jsonpScriptSrc(chunkId);"
"link.href = jsonpScriptSrc(chunkId);",
crossOriginLoading
? Template.asString([
"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
Template.indent(
`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
),
"}"
])
: ""
]);
}
);
Expand Down
9 changes: 8 additions & 1 deletion test/ConfigTestCases.test.js
Expand Up @@ -178,7 +178,14 @@ describe("ConfigTestCases", () => {
expect: expect,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
document: new FakeDocument()
document: new FakeDocument(),
location: {
href: "https://test.cases/path/index.html",
origin: "https://test.cases",
toString() {
return "https://test.cases/path/index.html";
}
}
};

function _require(currentDirectory, module) {
Expand Down
2 changes: 1 addition & 1 deletion test/__snapshots__/StatsTestCases.test.js.snap
Expand Up @@ -1767,7 +1767,7 @@ exports[`StatsTestCases should print correct stats for preload 1`] = `
normal.js 130 bytes 1 [emitted] normal
preloaded2.js 127 bytes 2 [emitted] preloaded2
preloaded3.js 130 bytes 3 [emitted] preloaded3
main.js 9.87 KiB 4 [emitted] main
main.js 9.86 KiB 4 [emitted] main
inner.js 136 bytes 5 [emitted] inner
inner2.js 201 bytes 6 [emitted] inner2
Entrypoint main = main.js (preload: preloaded2.js preloaded.js preloaded3.js)
Expand Down
Empty file.
67 changes: 67 additions & 0 deletions test/configCases/crossorigin/set-crossorigin/index.js
@@ -0,0 +1,67 @@
it("should load script without crossorigin attribute (default)", function() {
const promise = import("./empty?a" /* webpackChunkName: "crossorigin-default" */);

var script = document.head._children.pop();
__non_webpack_require__("./crossorigin-default.web.js");
expect(script.src).toBe("https://test.cases/path/crossorigin-default.web.js");
expect(script.crossOrigin).toBe(undefined);

return promise;
});

it("should load script without crossorigin attribute (relative)", function() {
var originalValue = __webpack_public_path__;
__webpack_public_path__ = "../";
const promise = import("./empty?b" /* webpackChunkName: "crossorigin-relative" */);
__webpack_public_path__ = originalValue;

var script = document.head._children.pop();
__non_webpack_require__("./crossorigin-relative.web.js");
expect(script.src).toBe("https://test.cases/crossorigin-relative.web.js");
expect(script.crossOrigin).toBe(undefined);

return promise;
});

it("should load script without crossorigin attribute (server relative)", function() {
var originalValue = __webpack_public_path__;
__webpack_public_path__ = "/server/";
const promise = import("./empty?c" /* webpackChunkName: "crossorigin-server-relative" */);
__webpack_public_path__ = originalValue;

var script = document.head._children.pop();
__non_webpack_require__("./crossorigin-server-relative.web.js");
expect(script.src).toBe("https://test.cases/server/crossorigin-server-relative.web.js");
expect(script.crossOrigin).toBe(undefined);

return promise;
});

it("should load script without crossorigin attribute (same origin)", function() {
var originalValue = __webpack_public_path__;
__webpack_public_path__ = "https://test.cases/";
const promise = import("./empty?d" /* webpackChunkName: "crossorigin-same-origin" */);
__webpack_public_path__ = originalValue;

var script = document.head._children.pop();
__non_webpack_require__("./crossorigin-same-origin.web.js");
expect(script.src).toBe("https://test.cases/crossorigin-same-origin.web.js");
expect(script.crossOrigin).toBe(undefined);

return promise;
});

it("should load script with crossorigin attribute anonymous (different origin)", function() {
var originalValue = __webpack_public_path__;
__webpack_public_path__ = "https://example.com/";
const promise = import("./empty?e" /* webpackChunkName: "crossorigin-different-origin" */);
__webpack_public_path__ = originalValue;


var script = document.head._children.pop();
__non_webpack_require__("./crossorigin-different-origin.web.js");
expect(script.src).toBe("https://example.com/crossorigin-different-origin.web.js");
expect(script.crossOrigin).toBe("anonymous");

return promise;
});
13 changes: 13 additions & 0 deletions test/configCases/crossorigin/set-crossorigin/webpack.config.js
@@ -0,0 +1,13 @@
module.exports = {
target: "web",
output: {
chunkFilename: "[name].web.js",
crossOriginLoading: "anonymous"
},
performance: {
hints: false
},
optimization: {
minimize: false
}
};
2 changes: 1 addition & 1 deletion test/configCases/split-chunks/runtime-chunk/a.js
Expand Up @@ -2,7 +2,7 @@ it("should be able to load the split chunk on demand", () => {
const promise = import(/* webpackChunkName: "shared" */ "./shared");

const script = document.head._children[0];
expect(script.src).toBe("dep~b~shared.js");
expect(script.src).toBe("https://test.cases/path/dep~b~shared.js");

__non_webpack_require__("./dep~b~shared.js");

Expand Down
4 changes: 2 additions & 2 deletions test/configCases/web/prefetch-preload/index.js
Expand Up @@ -10,11 +10,11 @@ beforeEach(() => {
afterEach(() => {
__webpack_nonce__ = oldNonce;
__webpack_public_path__ = oldPublicPath;
})
});

it("should prefetch and preload child chunks on chunk load", () => {
__webpack_nonce__ = "nonce";
__webpack_public_path__ = "/public/path/";
__webpack_public_path__ = "https://example.com/public/path/";

let link, script;

Expand Down
38 changes: 38 additions & 0 deletions test/helpers/FakeDocument.js
Expand Up @@ -20,6 +20,8 @@ class FakeElement {
this._type = type;
this._children = [];
this._attributes = Object.create(null);
this._src = undefined;
this._href = undefined;
}

appendChild(node) {
Expand All @@ -33,4 +35,40 @@ class FakeElement {
getAttribute(name) {
return this._attributes[name];
}

_toRealUrl(value) {
if (/^\//.test(value)) {
return `https://test.cases${value}`;
} else if (/^\.\.\//.test(value)) {
return `https://test.cases${value.substr(2)}`;
} else if (/^\.\//.test(value)) {
return `https://test.cases/path${value.substr(1)}`;
} else if (/^\w+:\/\//.test(value)) {
return value;
} else if (/^\/\//.test(value)) {
return `https:${value}`;
} else {
return `https://test.cases/path/${value}`;
}
}

set src(value) {
if (this._type === "script") {
this._src = this._toRealUrl(value);
}
}

get src() {
return this._src;
}

set href(value) {
if (this._type === "link") {
this._href = this._toRealUrl(value);
}
}

get href() {
return this._href;
}
}

0 comments on commit e08399a

Please sign in to comment.