Skip to content

Commit 590d697

Browse files
hayatoitochromium-wpt-export-bot
authored andcommitted
Support credentials mode in <script type=webbundle>
The current <script>-based API doesn't allow to specify a credential mode, while <link>-based APIs allowed that by its `crossorigin` attribute. This is one of the gaps between two APIs. This CL supports credentials for <script type=webbundle>. Now <script type=webbundle> can have a `credentials` to specify a request's credentials mode, such as: <script type="webbundle"> { source: "subresources.wbn", credentials: "omit", resources: ["a.js", "b.js", "c.png"], } </script> Regarding 're-using webbundle resources', we also check an equality of credentials mode of two script elements, in addition to their bundle URLs so that we don't re-use webbundle resources wrongly. See [1] for details. The related spec and explainer are: - The spec issue is WICG/webpackage#692. - PR is WICG/webpackage#694, which was already merged. - Updataed Explainer: https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#requests-mode-and-credentials-mode [1] https://docs.google.com/document/d/1q_SodTcLuwya4cXt1gIRaVrkiaBfwWyPvkY1fqRKkgM/edit?resourcekey=0-dqrFOGVCYsg8WRZ4RFgwuw#heading=h.mnfqu6560twe Bug: 1262005 Change-Id: I897e272962219ae10adf3dcd9c0c0e689b6c1d7b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3234563 Reviewed-by: Kunihiko Sakamoto <ksakamoto@chromium.org> Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org> Reviewed-by: Hiroshige Hayashizaki <hiroshige@chromium.org> Commit-Queue: Hayato Ito <hayato@chromium.org> Cr-Commit-Position: refs/heads/main@{#934410}
1 parent 4287b41 commit 590d697

7 files changed

+266
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import os
2+
3+
4+
def main(request, response):
5+
origin = request.headers.get(b"origin")
6+
7+
if origin is not None:
8+
response.headers.set(b"Access-Control-Allow-Origin", origin)
9+
response.headers.set(b"Access-Control-Allow-Methods", b"GET")
10+
response.headers.set(b"Access-Control-Allow-Credentials", b"true")
11+
12+
headers = [
13+
(b"Content-Type", b"application/webbundle"),
14+
(b"X-Content-Type-Options", b"nosniff"),
15+
]
16+
17+
cookie = request.cookies.first(b"milk", None)
18+
if (cookie is not None) and cookie.value == b"1":
19+
if request.GET.get(b"bundle", None) == b"cross-origin":
20+
bundle = "./wbn/simple-cross-origin.wbn"
21+
else:
22+
bundle = "./wbn/subresource.wbn"
23+
with open(
24+
os.path.join(os.path.dirname(__file__), bundle),
25+
"rb",
26+
) as f:
27+
return (200, headers, f.read())
28+
else:
29+
return (400, [], "")

web-bundle/resources/generate-test-wbns.sh

+5
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,8 @@ gen-bundle \
156156
-version b2 \
157157
-har uuid-in-package.har \
158158
-o wbn/uuid-in-package.wbn
159+
160+
gen-bundle \
161+
-version b2 \
162+
-har simple-cross-origin.har \
163+
-o wbn/simple-cross-origin.wbn
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"log": {
3+
"entries": [
4+
{
5+
"request": {
6+
"method": "GET",
7+
"url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/simple-cross-origin.txt",
8+
"headers": []
9+
},
10+
"response": {
11+
"status": 200,
12+
"headers": [
13+
{
14+
"name": "Content-type",
15+
"value": "text/plain"
16+
},
17+
{
18+
"name": "Access-Control-Allow-Origin",
19+
"value": "*"
20+
}
21+
],
22+
"content": {
23+
"text": "hello from simple-cross-origin.txt"
24+
}
25+
}
26+
}
27+
]
28+
}
29+
}

web-bundle/resources/test-helpers.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,11 @@ function createWebBundleElement(url, resources, options) {
158158
}
159159
const script = document.createElement("script");
160160
script.type = "webbundle";
161-
script.textContent =
162-
JSON.stringify({"source": url, "resources": resources});
163-
// TODO(crbug.com/1245166): Support |options.crossOrigin|.
161+
const json_rule = {"source": url, "resources": resources};
162+
if (options && options.credentials) {
163+
json_rule.credentials = options.credentials;
164+
}
165+
script.textContent = JSON.stringify(json_rule);
164166
// TODO(crbug.com/1245166): Support |options.scopes|.
165167
return script;
166168
}
246 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<!DOCTYPE html>
2+
<title>
3+
Credentials in WebBundle subresource loading
4+
</title>
5+
<link
6+
rel="help"
7+
href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#requests-mode-and-credentials-mode"
8+
/>
9+
<script src="/resources/testharness.js"></script>
10+
<script src="/resources/testharnessreport.js"></script>
11+
<script src="../resources/test-helpers.js"></script>
12+
<body>
13+
<script>
14+
// In this wpt, we test a request's credential mode, which controls
15+
// whether UA sends a credential or not to fetch a bundle.
16+
17+
// If UA sends a credential, check-cookie-and-return-{cross-oriigin}-bundle.py
18+
// returns a valid format webbundle. Then, a subresource fetch should be successful.
19+
// Otherwise, a subresource fetch should be rejected.
20+
21+
window.TEST_WEB_BUNDLE_ELEMENT_TYPE = "script";
22+
setup(() => {
23+
assert_true(HTMLScriptElement.supports("webbundle"));
24+
});
25+
26+
document.cookie = "milk=1; path=/";
27+
28+
// Make sure to set a cookie for a cross-origin domain from where a cross
29+
// origin bundle is served.
30+
const setCookiePromise = fetch(
31+
"https://{{domains[www1]}}:{{ports[https][0]}}/cookies/resources/set-cookie.py?name=milk&path=/web-bundle/resources/",
32+
{
33+
mode: "no-cors",
34+
credentials: "include",
35+
}
36+
);
37+
38+
const same_origin_bundle = "../resources/check-cookie-and-return-bundle.py";
39+
const cross_origin_bundle = "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/check-cookie-and-return-bundle.py?bundle=cross-origin";
40+
41+
const same_origin_bundle_subresource = "../resources/wbn/root.js";
42+
const cross_origin_bundle_subresource = "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/simple-cross-origin.txt";
43+
44+
async function assertSubresourceCanBeFetched() {
45+
const response = await fetch(same_origin_bundle_subresource);
46+
const text = await response.text();
47+
assert_equals(text, "export * from './submodule.js';\n");
48+
}
49+
50+
async function assertCrossOriginSubresourceCanBeFetched() {
51+
const response = await fetch(cross_origin_bundle_subresource);
52+
const text = await response.text();
53+
assert_equals(text, "hello from simple-cross-origin.txt");
54+
}
55+
56+
function createScriptWebBundle(credentials) {
57+
const options = {};
58+
if (credentials) {
59+
options.credentials = credentials;
60+
}
61+
return createWebBundleElement(same_origin_bundle, [same_origin_bundle_subresource], options);
62+
}
63+
64+
function createScriptWebBundleCrossOrigin(credentials) {
65+
const options = {};
66+
if (credentials) {
67+
options.credentials = credentials;
68+
}
69+
return createWebBundleElement(cross_origin_bundle, [cross_origin_bundle_subresource], options);
70+
}
71+
72+
promise_test(async (t) => {
73+
const script = createScriptWebBundle();
74+
document.body.append(script);
75+
t.add_cleanup(() => script.remove());
76+
77+
await assertSubresourceCanBeFetched();
78+
}, "The default should send a credential to a same origin bundle");
79+
80+
promise_test(async (t) => {
81+
const script = createScriptWebBundle("invalid");
82+
document.body.append(script);
83+
t.add_cleanup(() => script.remove());
84+
85+
await assertSubresourceCanBeFetched();
86+
}, "An invalid value should send a credential to a same origin bundle");
87+
88+
promise_test(async (t) => {
89+
const script = createScriptWebBundle("omit");
90+
document.body.append(script);
91+
t.add_cleanup(() => script.remove());
92+
93+
return promise_rejects_js(t, TypeError, fetch(same_origin_bundle_subresource))
94+
}, "'omit' should not send a credential to a same origin bundle");
95+
96+
promise_test(async (t) => {
97+
const script = createScriptWebBundle("same-origin");
98+
document.body.append(script);
99+
t.add_cleanup(() => script.remove());
100+
101+
await assertSubresourceCanBeFetched();
102+
}, "'same-origin' should send a credential to a same origin bundle");
103+
104+
promise_test(async (t) => {
105+
const script = createScriptWebBundle("include");
106+
document.body.append(script);
107+
t.add_cleanup(() => script.remove());
108+
109+
await assertSubresourceCanBeFetched();
110+
}, "'include' should send a credential to a same origin bundle");
111+
112+
promise_test(async (t) => {
113+
await setCookiePromise;
114+
115+
const script = createScriptWebBundleCrossOrigin("omit");
116+
document.body.append(script);
117+
t.add_cleanup(() => script.remove());
118+
119+
return promise_rejects_js(t, TypeError, fetch(cross_origin_bundle_subresource))
120+
}, "'omit' should not send a credential to a cross origin bundle");
121+
122+
promise_test(async (t) => {
123+
await setCookiePromise;
124+
125+
const script = createScriptWebBundleCrossOrigin("same-origin");
126+
document.body.append(script);
127+
t.add_cleanup(() => script.remove());
128+
129+
return promise_rejects_js(t, TypeError, fetch(cross_origin_bundle_subresource))
130+
}, "'same-origin' should not send a credential to a cross origin bundle");
131+
132+
promise_test(async (t) => {
133+
await setCookiePromise;
134+
135+
const script = createScriptWebBundleCrossOrigin("include");
136+
document.body.append(script);
137+
t.add_cleanup(() => script.remove());
138+
139+
await assertCrossOriginSubresourceCanBeFetched();
140+
}, "'include' should send a credential to a cross origin bundle");
141+
142+
143+
promise_test(async (t) => {
144+
const script = createScriptWebBundleCrossOrigin("invalid");
145+
document.body.append(script);
146+
t.add_cleanup(() => script.remove());
147+
148+
return promise_rejects_js(t, TypeError, fetch(cross_origin_bundle_subresource))
149+
}, "An invalid value should not send a credential to a cross origin bundle");
150+
</script>
151+
</body>

web-bundle/subresource-loading/script-reuse-web-bundle-resource.https.tentative.html

+47-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
return createWebBundleElement(wbn_url, /*resources=*/ [resource1]);
5353
}
5454

55-
function createScriptWebBundle2() {
56-
return createWebBundleElement(wbn_url, /*resources=*/ [resource2]);
55+
function createScriptWebBundle2(options) {
56+
return createWebBundleElement(wbn_url, /*resources=*/ [resource2], /*options=*/ options);
5757
}
5858

5959
async function appendScriptWebBundle1AndFetchResource1() {
@@ -105,6 +105,51 @@
105105
assert_equals(webBundleFetchCount(), 0);
106106
}, "'remove(), then append()' should reuse webbundle resources");
107107

108+
promise_test(async (t) => {
109+
t.add_cleanup(cleanUp);
110+
await appendScriptWebBundle1AndFetchResource1();
111+
clearWebBundleFetchCount();
112+
113+
// Remove script1, then append script2 with an explicit 'same-origin' credentials mode.
114+
script1.remove();
115+
script2 = createScriptWebBundle2({ credentials: "same-origin" });
116+
document.body.append(script2);
117+
118+
await assertResource1CanNotBeFetched();
119+
await assertResource2CanBeFetched();
120+
assert_equals(webBundleFetchCount(), 0);
121+
}, "Should reuse webbundle resources if a credential mode is same");
122+
123+
promise_test(async (t) => {
124+
t.add_cleanup(cleanUp);
125+
await appendScriptWebBundle1AndFetchResource1();
126+
clearWebBundleFetchCount();
127+
128+
// Remove script1, then append script2 with a different credentials mode.
129+
script1.remove();
130+
script2 = createScriptWebBundle2({ credentials: "omit" });
131+
document.body.append(script2);
132+
133+
await assertResource1CanNotBeFetched();
134+
await assertResource2CanBeFetched();
135+
assert_equals(webBundleFetchCount(), 1);
136+
}, "Should not reuse webbundle resources if a credentials mode is different (same-origin vs omit)");
137+
138+
promise_test(async (t) => {
139+
t.add_cleanup(cleanUp);
140+
await appendScriptWebBundle1AndFetchResource1();
141+
clearWebBundleFetchCount();
142+
143+
// Remove script1, then append script2 with a different credentials mode.
144+
script1.remove();
145+
script2 = createScriptWebBundle2({ credentials: "include" });
146+
document.body.append(script2);
147+
148+
await assertResource1CanNotBeFetched();
149+
await assertResource2CanBeFetched();
150+
assert_equals(webBundleFetchCount(), 1);
151+
}, "Should not reuse webbundle resources if a credential mode is different (same-origin vs include");
152+
108153
promise_test(async (t) => {
109154
t.add_cleanup(cleanUp);
110155
await appendScriptWebBundle1AndFetchResource1();

0 commit comments

Comments
 (0)