Skip to content

Commit 0e32f9c

Browse files
committed
Check for 416 responses as well in validateRangeRequestedFlag
https://bugs.webkit.org/show_bug.cgi?id=305548 Reviewed by Youenn Fablet. See whatwg/fetch#1906 for context and web-platform-tests/wpt#57225 for the tests. Canonical link: https://commits.webkit.org/305866@main
1 parent 54b3bb5 commit 0e32f9c

File tree

9 files changed

+100
-14
lines changed

9 files changed

+100
-14
lines changed
Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
11
"""
2-
This generates a partial response containing valid JavaScript.
2+
This generates a partial response containing valid JavaScript or image data.
33
"""
44

55
def main(request, response):
66
require_range = request.GET.first(b'require-range', b'')
77
pretend_offset = int(request.GET.first(b'pretend-offset', b'0'))
8+
range_not_satisfiable = request.GET.first(b'range-not-satisfiable', b'')
9+
content_type = request.GET.first(b'type', b'text/plain')
810
range_header = request.headers.get(b'Range', b'')
911

1012
if require_range and not range_header:
1113
response.set_error(412, u"Range header required")
1214
response.write()
1315
return
1416

15-
response.headers.set(b"Content-Type", b"text/plain")
16-
response.headers.set(b"Accept-Ranges", b"bytes")
17-
response.headers.set(b"Cache-Control", b"no-cache")
18-
response.status = 206
17+
# 1x1 red PNG image (67 bytes)
18+
png_data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\xcf\xc0\x00\x00\x00\x03\x00\x01\x00\x18\xdd\x8d\xb4\x00\x00\x00\x00IEND\xaeB`\x82'
1919

20-
to_send = b'self.scriptExecuted = true;'
21-
length = len(to_send)
20+
if content_type == b'image/png':
21+
to_send = png_data
22+
else:
23+
to_send = b'self.scriptExecuted = true;'
2224

23-
content_range = b"bytes %d-%d/%d" % (
24-
pretend_offset, pretend_offset + length - 1, pretend_offset + length)
25+
length = len(to_send)
2526

26-
response.headers.set(b"Content-Range", content_range)
27+
response.headers.set(b"Content-Type", content_type)
28+
response.headers.set(b"Accept-Ranges", b"bytes")
29+
response.headers.set(b"Cache-Control", b"no-cache")
2730
response.headers.set(b"Content-Length", length)
2831

32+
if range_not_satisfiable:
33+
response.status = 416
34+
response.headers.set(b"Content-Range", b"bytes */%d" % (pretend_offset + length))
35+
else:
36+
response.status = 206
37+
content_range = b"bytes %d-%d/%d" % (
38+
pretend_offset, pretend_offset + length - 1, pretend_offset + length)
39+
response.headers.set(b"Content-Range", content_range)
40+
2941
response.content = to_send

LayoutTests/imported/w3c/web-platform-tests/fetch/range/resources/range-sw.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ function storeRangedResponse(event) {
144144
function useStoredRangeResponse(event) {
145145
event.respondWith(async function() {
146146
const response = await storedRangeResponseP;
147-
if (!response) throw Error("Expected stored range response");
148147
return response.clone();
149148
}());
150149
}

LayoutTests/imported/w3c/web-platform-tests/fetch/range/resources/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ function loadScript(url, { doc = document }={}) {
88
})
99
}
1010

11+
function loadImage(url, { doc = document }={}) {
12+
return new Promise((resolve, reject) => {
13+
const img = doc.createElement('img');
14+
img.onload = () => resolve();
15+
img.onerror = () => reject(Error("Image load failed"));
16+
img.src = url;
17+
doc.body.appendChild(img);
18+
})
19+
}
20+
1121
function preloadImage(url, { doc = document }={}) {
1222
return new Promise((resolve, reject) => {
1323
const preload = doc.createElement('link');
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
PASS 416 response not allowed following no-cors ranged request
3+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<!-- This file is required for WebKit test infrastructure to run the templated test -->
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// META: script=../../../service-workers/service-worker/resources/test-helpers.sub.js
2+
// META: script=/common/utils.js
3+
// META: script=/common/get-host-info.sub.js
4+
// META: script=resources/utils.js
5+
6+
const { REMOTE_HOST } = get_host_info();
7+
const BASE_SCOPE = 'resources/basic.html?';
8+
9+
async function cleanup() {
10+
for (const iframe of document.querySelectorAll('.test-iframe')) {
11+
iframe.parentNode.removeChild(iframe);
12+
}
13+
14+
for (const reg of await navigator.serviceWorker.getRegistrations()) {
15+
await reg.unregister();
16+
}
17+
}
18+
19+
async function setupRegistration(t, scope) {
20+
await cleanup();
21+
const reg = await navigator.serviceWorker.register('resources/range-sw.js', { scope });
22+
await wait_for_state(t, reg.installing, 'activated');
23+
return reg;
24+
}
25+
26+
function awaitMessage(obj, id) {
27+
return new Promise(resolve => {
28+
obj.addEventListener('message', function listener(event) {
29+
if (event.data.id !== id) return;
30+
obj.removeEventListener('message', listener);
31+
resolve(event.data);
32+
});
33+
});
34+
}
35+
36+
promise_test(async t => {
37+
const scope = BASE_SCOPE + Math.random();
38+
await setupRegistration(t, scope);
39+
const iframe = await with_iframe(scope);
40+
const w = iframe.contentWindow;
41+
const id = Math.random() + '';
42+
const storedRangeResponse = awaitMessage(w.navigator.serviceWorker, id);
43+
44+
const url = new URL('partial-script.py', w.location);
45+
url.searchParams.set('require-range', '1');
46+
url.searchParams.set('range-not-satisfiable', '1');
47+
url.searchParams.set('type', 'image/png');
48+
url.searchParams.set('action', 'store-ranged-response');
49+
url.searchParams.set('id', id);
50+
url.hostname = REMOTE_HOST;
51+
52+
appendAudio(w.document, url);
53+
54+
await storedRangeResponse;
55+
56+
const fetchPromise = w.fetch('?action=use-stored-ranged-response', { mode: 'no-cors' });
57+
await promise_rejects_js(t, w.TypeError, fetchPromise);
58+
59+
const loadImagePromise = loadImage('?action=use-stored-ranged-response', { doc: w.document });
60+
await promise_rejects_js(t, Error, loadImagePromise);
61+
}, `416 response not allowed following no-cors ranged request`);

LayoutTests/imported/w3c/web-platform-tests/fetch/range/sw.https.window.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ promise_test(async t => {
9292
const loadScriptPromise = loadScript('?action=use-stored-ranged-response', { doc: w.document });
9393
await promise_rejects_js(t, Error, loadScriptPromise);
9494

95-
await loadScriptPromise.catch(() => {});
96-
9795
assert_false(!!w.scriptExecuted, `Partial response shouldn't be executed`);
9896
}, `Ranged response not allowed following no-cors ranged request`);
9997

LayoutTests/imported/w3c/web-platform-tests/fetch/range/w3c-import.log

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ List of files:
1919
/LayoutTests/imported/w3c/web-platform-tests/fetch/range/general.any.js
2020
/LayoutTests/imported/w3c/web-platform-tests/fetch/range/general.window.js
2121
/LayoutTests/imported/w3c/web-platform-tests/fetch/range/non-matching-range-response.html
22+
/LayoutTests/imported/w3c/web-platform-tests/fetch/range/sw-416.https.window.js
2223
/LayoutTests/imported/w3c/web-platform-tests/fetch/range/sw.https.window.js

Source/WebCore/loader/CrossOriginAccessControl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "DocumentSecurityOrigin.h"
3535
#include "HTTPHeaderNames.h"
3636
#include "HTTPParsers.h"
37+
#include "HTTPStatusCodes.h"
3738
#include "LegacySchemeRegistry.h"
3839
#include "OriginAccessPatterns.h"
3940
#include "Page.h"
@@ -364,7 +365,7 @@ std::optional<ResourceError> validateCrossOriginResourcePolicy(CrossOriginEmbedd
364365

365366
std::optional<ResourceError> validateRangeRequestedFlag(const ResourceRequest& request, const ResourceResponse& response)
366367
{
367-
if (response.isRangeRequested() && response.httpStatusCode() == 206 && response.type() == ResourceResponse::Type::Opaque && !request.hasHTTPHeaderField(HTTPHeaderName::Range))
368+
if (response.isRangeRequested() && (response.httpStatusCode() == httpStatus206PartialContent || response.httpStatusCode() == httpStatus416RangeNotSatisfiable) && response.type() == ResourceResponse::Type::Opaque && !request.hasHTTPHeaderField(HTTPHeaderName::Range))
368369
return ResourceError({ }, 0, response.url(), { }, ResourceError::Type::General);
369370
return std::nullopt;
370371
}

0 commit comments

Comments
 (0)