feat: HTTP add TLS fingerprint fallback (#1444)

This commit is contained in:
Levi authored and GitHub committed 2026-08-20 20:21:02 +08:00
1 parent 99924296ef
commit cfdf87c3b8
68 files changed
+7243 -1604

No files matched your search

@@ -0,0 +1,13 @@
// META: global=window,worker
// https://fetch.spec.whatwg.org/#forbidden-method
for (const method of [
'CONNECT', 'TRACE', 'TRACK',
'connect', 'trace', 'track'
]) {
test(function() {
assert_throws_js(TypeError,
function() { new Request('./', {method: method}); }
);
}, 'Request() with a forbidden method ' + method + ' must throw.');
}
@@ -0,0 +1,22 @@
// META: global=window,worker
promise_test(async () => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("hello"));
controller.close();
}
});
const r1 = new Request("https://example.com/", { method: "POST", body: stream, duplex: "half" });
const r2 = r1.clone();
assert_false(r2.bodyUsed, "clone should not be marked as used");
assert_not_equals(r2.body, null, "clone should have a body");
// Constructing a new Request from the clone should preserve the body.
const r3 = new Request(r2);
assert_not_equals(r3.body, null, "Request constructed from clone should have a body");
const text = await r3.text();
assert_equals(text, "hello", "body content should be preserved through clone() and new Request()");
}, "new Request(clone) preserves a ReadableStream body that came from clone()");
@@ -0,0 +1,37 @@
promise_test(async function () {
const req1 = new Request("https://example.com/", {
body: "req1",
method: "POST",
});
const text1 = await req1.text();
assert_equals(
text1,
"req1",
"The body of the first request should be 'req1'."
);
const req2 = new Request(req1, { body: "req2" });
const bodyText = await req2.text();
assert_equals(
bodyText,
"req2",
"The body of the second request should be overridden to 'req2'."
);
}, "Check that the body of a new request can be overridden when created from an existing Request object");
promise_test(async function () {
const req1 = new Request("https://example.com/", {
body: "req1",
method: "POST",
});
const req2 = new Request("https://example.com/", req1);
const bodyText = await req2.text();
assert_equals(
bodyText,
"req1",
"The body of the second request should be the same as the first."
);
}, "Check that the body of a new request can be duplicated from an existing Request object");
@@ -0,0 +1,89 @@
// META: global=window,worker
// META: title=Request consume empty bodies
function checkBodyText(test, request) {
return request.text().then(function(bodyAsText) {
assert_equals(bodyAsText, "", "Resolved value should be empty");
assert_false(request.bodyUsed);
});
}
async function checkBodyBlob(test, request) {
const bodyAsBlob = await request.blob();
const body = await bodyAsBlob.text();
assert_equals(body, "", "Resolved value should be empty");
assert_false(request.bodyUsed);
}
function checkBodyArrayBuffer(test, request) {
return request.arrayBuffer().then(function(bodyAsArrayBuffer) {
assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty");
assert_false(request.bodyUsed);
});
}
function checkBodyJSON(test, request) {
return request.json().then(
function(bodyAsJSON) {
assert_unreached("JSON parsing should fail");
},
function() {
assert_false(request.bodyUsed);
});
}
function checkBodyFormData(test, request) {
return request.formData().then(function(bodyAsFormData) {
assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData");
assert_false(request.bodyUsed);
});
}
function checkBodyFormDataError(test, request) {
return promise_rejects_js(test, TypeError, request.formData()).then(function() {
assert_false(request.bodyUsed);
});
}
function checkRequestWithNoBody(bodyType, checkFunction, headers = []) {
promise_test(function(test) {
var request = new Request("", {"method": "POST", "headers": headers});
assert_false(request.bodyUsed);
return checkFunction(test, request);
}, "Consume request's body as " + bodyType);
}
checkRequestWithNoBody("text", checkBodyText);
checkRequestWithNoBody("blob", checkBodyBlob);
checkRequestWithNoBody("arrayBuffer", checkBodyArrayBuffer);
checkRequestWithNoBody("json (error case)", checkBodyJSON);
checkRequestWithNoBody("formData with correct multipart type (error case)", checkBodyFormDataError, [["Content-Type", 'multipart/form-data; boundary="boundary"']]);
checkRequestWithNoBody("formData with correct urlencoded type", checkBodyFormData, [["Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"]]);
checkRequestWithNoBody("formData without correct type (error case)", checkBodyFormDataError);
function checkRequestWithEmptyBody(bodyType, body, asText) {
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": body});
assert_false(request.bodyUsed, "bodyUsed is false at init");
if (asText) {
return request.text().then(function(bodyAsString) {
assert_equals(bodyAsString.length, 0, "Resolved value should be empty");
assert_true(request.bodyUsed, "bodyUsed is true after being consumed");
});
}
return request.arrayBuffer().then(function(bodyAsArrayBuffer) {
assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty");
assert_true(request.bodyUsed, "bodyUsed is true after being consumed");
});
}, "Consume empty " + bodyType + " request body as " + (asText ? "text" : "arrayBuffer"));
}
// FIXME: Add BufferSource, FormData and URLSearchParams.
checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), false);
checkRequestWithEmptyBody("text", "", false);
checkRequestWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), true);
checkRequestWithEmptyBody("text", "", true);
checkRequestWithEmptyBody("URLSearchParams", new URLSearchParams(""), true);
// FIXME: This test assumes that the empty string be returned but it is not clear whether that is right. See https://github.com/web-platform-tests/wpt/pull/3950.
checkRequestWithEmptyBody("FormData", new FormData(), true);
checkRequestWithEmptyBody("ArrayBuffer", new ArrayBuffer(), true);
@@ -0,0 +1,148 @@
// META: global=window,worker
// META: title=Request consume
// META: script=../resources/utils.js
function checkBodyText(request, expectedBody) {
return request.text().then(function(bodyAsText) {
assert_equals(bodyAsText, expectedBody, "Retrieve and verify request's body");
assert_true(request.bodyUsed, "body as text: bodyUsed turned true");
});
}
async function checkBodyBlob(request, expectedBody, checkContentType) {
const bodyAsBlob = await request.blob();
if (checkContentType)
assert_equals(bodyAsBlob.type, "text/plain", "Blob body type should be computed from the request Content-Type");
const body = await bodyAsBlob.text();
assert_equals(body, expectedBody, "Retrieve and verify request's body");
assert_true(request.bodyUsed, "body as blob: bodyUsed turned true");
}
function checkBodyArrayBuffer(request, expectedBody) {
return request.arrayBuffer().then(function(bodyAsArrayBuffer) {
validateBufferFromString(bodyAsArrayBuffer, expectedBody, "Retrieve and verify request's body");
assert_true(request.bodyUsed, "body as arrayBuffer: bodyUsed turned true");
});
}
function checkBodyBytes(request, expectedBody) {
return request.bytes().then(function(bodyAsUint8Array) {
assert_true(bodyAsUint8Array instanceof Uint8Array);
validateBufferFromString(bodyAsUint8Array.buffer, expectedBody, "Retrieve and verify request's body");
assert_true(request.bodyUsed, "body as bytes: bodyUsed turned true");
});
}
function checkBodyJSON(request, expectedBody) {
return request.json().then(function(bodyAsJSON) {
var strBody = JSON.stringify(bodyAsJSON)
assert_equals(strBody, expectedBody, "Retrieve and verify request's body");
assert_true(request.bodyUsed, "body as json: bodyUsed turned true");
});
}
function checkBodyFormData(request, expectedBody) {
return request.formData().then(function(bodyAsFormData) {
assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData");
assert_true(request.bodyUsed, "body as formData: bodyUsed turned true");
});
}
function checkRequestBody(body, expected, bodyType) {
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": body, "headers": [["Content-Type", "text/PLAIN"]] });
assert_false(request.bodyUsed, "bodyUsed is false at init");
return checkBodyText(request, expected);
}, "Consume " + bodyType + " request's body as text");
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": body });
assert_false(request.bodyUsed, "bodyUsed is false at init");
return checkBodyBlob(request, expected);
}, "Consume " + bodyType + " request's body as blob");
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": body });
assert_false(request.bodyUsed, "bodyUsed is false at init");
return checkBodyArrayBuffer(request, expected);
}, "Consume " + bodyType + " request's body as arrayBuffer");
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": body });
assert_false(request.bodyUsed, "bodyUsed is false at init");
return checkBodyBytes(request, expected);
}, "Consume " + bodyType + " request's body as bytes");
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": body });
assert_false(request.bodyUsed, "bodyUsed is false at init");
return checkBodyJSON(request, expected);
}, "Consume " + bodyType + " request's body as JSON");
}
var textData = JSON.stringify("This is response's body");
var blob = new Blob([textData], { "type" : "text/plain" });
checkRequestBody(textData, textData, "String");
var string = "\"123456\"";
function getArrayBuffer() {
var arrayBuffer = new ArrayBuffer(8);
var int8Array = new Int8Array(arrayBuffer);
for (var cptr = 0; cptr < 8; cptr++)
int8Array[cptr] = string.charCodeAt(cptr);
return arrayBuffer;
}
function getArrayBufferWithZeros() {
var arrayBuffer = new ArrayBuffer(10);
var int8Array = new Int8Array(arrayBuffer);
for (var cptr = 0; cptr < 8; cptr++)
int8Array[cptr + 1] = string.charCodeAt(cptr);
return arrayBuffer;
}
checkRequestBody(getArrayBuffer(), string, "ArrayBuffer");
checkRequestBody(new Uint8Array(getArrayBuffer()), string, "Uint8Array");
checkRequestBody(new Int8Array(getArrayBufferWithZeros(), 1, 8), string, "Int8Array");
checkRequestBody(new Float32Array(getArrayBuffer()), string, "Float32Array");
checkRequestBody(new DataView(getArrayBufferWithZeros(), 1, 8), string, "DataView");
promise_test(function(test) {
var formData = new FormData();
formData.append("name", "value")
var request = new Request("", {"method": "POST", "body": formData });
assert_false(request.bodyUsed, "bodyUsed is false at init");
return checkBodyFormData(request, formData);
}, "Consume FormData request's body as FormData");
function checkBlobResponseBody(blobBody, blobData, bodyType, checkFunction) {
promise_test(function(test) {
var response = new Response(blobBody);
assert_false(response.bodyUsed, "bodyUsed is false at init");
return checkFunction(response, blobData);
}, "Consume blob response's body as " + bodyType);
}
checkBlobResponseBody(blob, textData, "blob", checkBodyBlob);
checkBlobResponseBody(blob, textData, "text", checkBodyText);
checkBlobResponseBody(blob, textData, "json", checkBodyJSON);
checkBlobResponseBody(blob, textData, "arrayBuffer", checkBodyArrayBuffer);
checkBlobResponseBody(blob, textData, "bytes", checkBodyBytes);
checkBlobResponseBody(new Blob([""]), "", "blob (empty blob as input)", checkBodyBlob);
var goodJSONValues = ["null", "1", "true", "\"string\""];
goodJSONValues.forEach(function(value) {
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": value});
return request.json().then(function(v) {
assert_equals(v, JSON.parse(value));
});
}, "Consume JSON from text: '" + JSON.stringify(value) + "'");
});
var badJSONValues = ["undefined", "{", "a", "["];
badJSONValues.forEach(function(value) {
promise_test(function(test) {
var request = new Request("", {"method": "POST", "body": value});
return promise_rejects_js(test, SyntaxError, request.json());
}, "Trying to consume bad JSON text as JSON: '" + value + "'");
});
@@ -0,0 +1,109 @@
// META: global=window,worker
// META: title=Request disturbed
// META: script=../resources/utils.js
var initValuesDict = {"method" : "POST",
"body" : "Request's body"
};
var noBodyConsumed = new Request("");
var bodyConsumed = new Request("", initValuesDict);
test(() => {
assert_equals(noBodyConsumed.body, null, "body's default value is null");
assert_false(noBodyConsumed.bodyUsed , "bodyUsed is false when request is not disturbed");
assert_not_equals(bodyConsumed.body, null, "non-null body");
assert_true(bodyConsumed.body instanceof ReadableStream, "non-null body type");
assert_false(noBodyConsumed.bodyUsed, "bodyUsed is false when request is not disturbed");
}, "Request's body: initial state");
noBodyConsumed.blob();
bodyConsumed.blob();
test(function() {
assert_false(noBodyConsumed.bodyUsed , "bodyUsed is false when request is not disturbed");
try {
noBodyConsumed.clone();
} catch (e) {
assert_unreached("Can use request not disturbed for creating or cloning request");
}
}, "Request without body cannot be disturbed");
test(function() {
assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
assert_throws_js(TypeError, function() { bodyConsumed.clone(); });
}, "Check cloning a disturbed request");
test(function() {
assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
assert_throws_js(TypeError, function() { new Request(bodyConsumed); });
}, "Check creating a new request from a disturbed request");
promise_test(function() {
assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
const originalBody = bodyConsumed.body;
const bodyReplaced = new Request(bodyConsumed, { body: "Replaced body" });
assert_not_equals(bodyReplaced.body, originalBody, "new request's body is new");
assert_false(bodyReplaced.bodyUsed, "bodyUsed is false when request is not disturbed");
return bodyReplaced.text().then(text => {
assert_equals(text, "Replaced body");
});
}, "Check creating a new request with a new body from a disturbed request");
promise_test(function() {
var bodyRequest = new Request("", initValuesDict);
const originalBody = bodyRequest.body;
assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed");
var requestFromRequest = new Request(bodyRequest);
assert_true(bodyRequest.bodyUsed , "bodyUsed is true when request is disturbed");
assert_equals(bodyRequest.body, originalBody, "body should not change");
assert_not_equals(originalBody, undefined, "body should not be undefined");
assert_not_equals(originalBody, null, "body should not be null");
assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new");
return requestFromRequest.text().then(text => {
assert_equals(text, "Request's body");
});
}, "Input request used for creating new request became disturbed");
promise_test(() => {
const bodyRequest = new Request("", initValuesDict);
const originalBody = bodyRequest.body;
assert_false(bodyRequest.bodyUsed , "bodyUsed is false when request is not disturbed");
const requestFromRequest = new Request(bodyRequest, { body : "init body" });
assert_true(bodyRequest.bodyUsed , "bodyUsed is true when request is disturbed");
assert_equals(bodyRequest.body, originalBody, "body should not change");
assert_not_equals(originalBody, undefined, "body should not be undefined");
assert_not_equals(originalBody, null, "body should not be null");
assert_not_equals(requestFromRequest.body, originalBody, "new request's body is new");
return requestFromRequest.text().then(text => {
assert_equals(text, "init body");
});
}, "Input request used for creating new request became disturbed even if body is not used");
promise_test(function(test) {
assert_true(bodyConsumed.bodyUsed , "bodyUsed is true when request is disturbed");
return promise_rejects_js(test, TypeError, bodyConsumed.blob());
}, "Check consuming a disturbed request");
test(function() {
var req = new Request(URL, {method: 'POST', body: 'hello'});
assert_false(req.bodyUsed,
'Request should not be flagged as used if it has not been ' +
'consumed.');
assert_throws_js(TypeError,
function() { new Request(req, {method: 'GET'}); },
'A get request may not have body.');
assert_false(req.bodyUsed, 'After the GET case');
assert_throws_js(TypeError,
function() { new Request(req, {method: 'CONNECT'}); },
'Request() with a forbidden method must throw.');
assert_false(req.bodyUsed, 'After the forbidden method case');
var req2 = new Request(req);
assert_true(req.bodyUsed,
'Request should be flagged as used if it has been consumed.');
}, 'Request construction failure should not set "bodyUsed"');
@@ -0,0 +1,56 @@
// META: global=window,worker
// META: title=Request error
// META: script=request-error.js
// badRequestArgTests is from response-error.js
for (const { args, testName } of badRequestArgTests) {
test(() => {
assert_throws_js(
TypeError,
() => new Request(...args),
"Expect TypeError exception"
);
}, testName);
}
test(function() {
assert_throws_js(
TypeError,
() => Request("about:blank"),
"Calling Request constructor without 'new' must throw"
);
});
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders});
var request = new Request(initialRequest);
assert_equals(request.headers.get("Content-Type"), "potato");
}, "Request should get its content-type from the init request");
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders});
var headers = new Headers([]);
var request = new Request(initialRequest, {"headers" : headers});
assert_false(request.headers.has("Content-Type"));
}, "Request should not get its content-type from the init request if init headers are provided");
test(function() {
var initialHeaders = new Headers([["Content-Type-Extra", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
var request = new Request(initialRequest);
assert_equals(request.headers.get("Content-Type"), "text/plain;charset=UTF-8");
}, "Request should get its content-type from the body if none is provided");
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
var request = new Request(initialRequest);
assert_equals(request.headers.get("Content-Type"), "potato");
}, "Request should get its content-type from init headers if one is provided");
test(function() {
var options = {"cache": "only-if-cached", "mode": "same-origin"};
new Request("test", options);
}, "Request with cache mode: only-if-cached and fetch mode: same-origin");
@@ -0,0 +1,57 @@
const badRequestArgTests = [
{
args: ["", { "window": "http://test.url" }],
testName: "RequestInit's window is not null"
},
{
args: ["http://:not a valid URL"],
testName: "Input URL is not valid"
},
{
args: ["http://user:[email protected]"],
testName: "Input URL has credentials"
},
{
args: ["", { "mode": "navigate" }],
testName: "RequestInit's mode is navigate"
},
{
args: ["", { "referrer": "http://:not a valid URL" }],
testName: "RequestInit's referrer is invalid"
},
{
args: ["", { "method": "IN VALID" }],
testName: "RequestInit's method is invalid"
},
{
args: ["", { "method": "TRACE" }],
testName: "RequestInit's method is forbidden"
},
{
args: ["", { "mode": "no-cors", "method": "PUT" }],
testName: "RequestInit's mode is no-cors and method is not simple"
},
{
args: ["", { "mode": "cors", "cache": "only-if-cached" }],
testName: "RequestInit's cache mode is only-if-cached and mode is not same-origin"
},
{
args: ["test", { "cache": "only-if-cached", "mode": "cors" }],
testName: "Request with cache mode: only-if-cached and fetch mode cors"
},
{
args: ["test", { "cache": "only-if-cached", "mode": "no-cors" }],
testName: "Request with cache mode: only-if-cached and fetch mode no-cors"
}
];
badRequestArgTests.push(
...["referrerPolicy", "mode", "credentials", "cache", "redirect"].map(optionProp => {
const options = {};
options[optionProp] = "BAD";
return {
args: ["", options],
testName: `Bad ${optionProp} init parameter value`
};
})
);
@@ -0,0 +1,177 @@
// META: global=window,worker
// META: title=Request Headers
var validRequestHeaders = [
["Content-Type", "OK"],
["Potato", "OK"],
["proxy", "OK"],
["proxya", "OK"],
["sec", "OK"],
["secb", "OK"],
["Set-Cookie2", "OK"],
["User-Agent", "OK"],
];
var invalidRequestHeaders = [
["Accept-Charset", "KO"],
["accept-charset", "KO"],
["ACCEPT-ENCODING", "KO"],
["Accept-Encoding", "KO"],
["Access-Control-Request-Headers", "KO"],
["Access-Control-Request-Method", "KO"],
["Connection", "KO"],
["Content-Length", "KO"],
["Cookie", "KO"],
["Cookie2", "KO"],
["Date", "KO"],
["DNT", "KO"],
["Expect", "KO"],
["Host", "KO"],
["Keep-Alive", "KO"],
["Origin", "KO"],
["Referer", "KO"],
["Set-Cookie", "KO"],
["TE", "KO"],
["Trailer", "KO"],
["Transfer-Encoding", "KO"],
["Upgrade", "KO"],
["Via", "KO"],
["Proxy-", "KO"],
["proxy-a", "KO"],
["Sec-", "KO"],
["sec-b", "KO"],
];
var validRequestNoCorsHeaders = [
["Accept", "OK"],
["Accept-Language", "OK"],
["content-language", "OK"],
["content-type", "application/x-www-form-urlencoded"],
["content-type", "application/x-www-form-urlencoded;charset=UTF-8"],
["content-type", "multipart/form-data"],
["content-type", "multipart/form-data;charset=UTF-8"],
["content-TYPE", "text/plain"],
["CONTENT-type", "text/plain;charset=UTF-8"],
];
var invalidRequestNoCorsHeaders = [
["Content-Type", "KO"],
["Potato", "KO"],
["proxy", "KO"],
["proxya", "KO"],
["sec", "KO"],
["secb", "KO"],
["Empty-Value", ""],
];
validRequestHeaders.forEach(function(header) {
test(function() {
var request = new Request("");
request.headers.set(header[0], header[1]);
assert_equals(request.headers.get(header[0]), header[1]);
}, "Adding valid request header \"" + header[0] + ": " + header[1] + "\"");
});
invalidRequestHeaders.forEach(function(header) {
test(function() {
var request = new Request("");
request.headers.set(header[0], header[1]);
assert_equals(request.headers.get(header[0]), null);
}, "Adding invalid request header \"" + header[0] + ": " + header[1] + "\"");
});
validRequestNoCorsHeaders.forEach(function(header) {
test(function() {
var requestNoCors = new Request("", {"mode": "no-cors"});
requestNoCors.headers.set(header[0], header[1]);
assert_equals(requestNoCors.headers.get(header[0]), header[1]);
}, "Adding valid no-cors request header \"" + header[0] + ": " + header[1] + "\"");
});
invalidRequestNoCorsHeaders.forEach(function(header) {
test(function() {
var requestNoCors = new Request("", {"mode": "no-cors"});
requestNoCors.headers.set(header[0], header[1]);
assert_equals(requestNoCors.headers.get(header[0]), null);
}, "Adding invalid no-cors request header \"" + header[0] + ": " + header[1] + "\"");
});
test(function() {
var headers = new Headers([["Cookie2", "potato"]]);
var request = new Request("", {"headers": headers});
assert_equals(request.headers.get("Cookie2"), null);
}, "Check that request constructor is filtering headers provided as init parameter");
test(function() {
var headers = new Headers([["Content-Type", "potato"]]);
var request = new Request("", {"headers": headers, "mode": "no-cors"});
assert_equals(request.headers.get("Content-Type"), null);
}, "Check that no-cors request constructor is filtering headers provided as init parameter");
test(function() {
var headers = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers": headers});
var request = new Request(initialRequest, {"mode": "no-cors"});
assert_equals(request.headers.get("Content-Type"), null);
}, "Check that no-cors request constructor is filtering headers provided as part of request parameter");
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders});
var request = new Request(initialRequest);
assert_equals(request.headers.get("Content-Type"), "potato");
}, "Request should get its content-type from the init request");
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders});
var headers = new Headers([]);
var request = new Request(initialRequest, {"headers" : headers});
assert_false(request.headers.has("Content-Type"));
}, "Request should not get its content-type from the init request if init headers are provided");
test(function() {
var initialHeaders = new Headers([["Content-Type-Extra", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
var request = new Request(initialRequest);
assert_equals(request.headers.get("Content-Type"), "text/plain;charset=UTF-8");
}, "Request should get its content-type from the body if none is provided");
test(function() {
var initialHeaders = new Headers([["Content-Type", "potato"]]);
var initialRequest = new Request("", {"headers" : initialHeaders, "body" : "this is my plate", "method" : "POST"});
var request = new Request(initialRequest);
assert_equals(request.headers.get("Content-Type"), "potato");
}, "Request should get its content-type from init headers if one is provided");
test(function() {
var array = [["hello", "worldAHH"]];
var object = {"hello": 'worldOOH'};
var headers = new Headers(array);
assert_equals(headers.get("hello"), "worldAHH");
var request1 = new Request("", {"headers": headers});
var request2 = new Request("", {"headers": array});
var request3 = new Request("", {"headers": object});
assert_equals(request1.headers.get("hello"), "worldAHH");
assert_equals(request2.headers.get("hello"), "worldAHH");
assert_equals(request3.headers.get("hello"), "worldOOH");
}, "Testing request header creations with various objects");
promise_test(function(test) {
var request = new Request("", {"headers" : [["Content-Type", ""]], "body" : "this is my plate", "method" : "POST"});
return request.blob().then(function(blob) {
assert_equals(blob.type, "", "Blob type should be the empty string");
});
}, "Testing empty Request Content-Type header");
test(function() {
const request1 = new Request("");
assert_equals(request1.headers, request1.headers);
const request2 = new Request("", {"headers": {"X-Foo": "bar"}});
assert_equals(request2.headers, request2.headers);
const headers = request2.headers;
request2.headers.set("X-Foo", "quux");
assert_equals(headers, request2.headers);
headers.set("X-Other-Header", "baz");
assert_equals(headers, request2.headers);
}, "Test that Request.headers has the [SameObject] extended attribute");
@@ -0,0 +1,60 @@
// META: global=window,worker
// META: title=Request init: headers and body
test(function() {
var headerDict = {"name1": "value1",
"name2": "value2",
"name3": "value3"
};
var headers = new Headers(headerDict);
var request = new Request("", { "headers" : headers })
for (var name in headerDict) {
assert_equals(request.headers.get(name), headerDict[name],
"request's headers has " + name + " : " + headerDict[name]);
}
}, "Initialize Request with headers values");
function makeRequestInit(body, method) {
return {"method": method, "body": body};
}
function checkRequestInit(body, bodyType, expectedTextBody) {
promise_test(function(test) {
var request = new Request("", makeRequestInit(body, "POST"));
if (body) {
assert_throws_js(TypeError, function() { new Request("", makeRequestInit(body, "GET")); });
assert_throws_js(TypeError, function() { new Request("", makeRequestInit(body, "HEAD")); });
} else {
new Request("", makeRequestInit(body, "GET")); // should not throw
}
var reqHeaders = request.headers;
var mime = reqHeaders.get("Content-Type");
assert_true(!body || (mime && mime.search(bodyType) > -1), "Content-Type header should be \"" + bodyType + "\", not \"" + mime + "\"");
return request.text().then(function(bodyAsText) {
//not equals: cannot guess formData exact value
assert_true( bodyAsText.search(expectedTextBody) > -1, "Retrieve and verify request body");
});
}, `Initialize Request's body with "${body}", ${bodyType}`);
}
var blob = new Blob(["This is a blob"], {type: "application/octet-binary"});
var formaData = new FormData();
formaData.append("name", "value");
var usvString = "This is a USVString"
checkRequestInit(undefined, undefined, "");
checkRequestInit(null, null, "");
checkRequestInit(blob, "application/octet-binary", "This is a blob");
checkRequestInit(formaData, "multipart/form-data", "name=\"name\"\r\n\r\nvalue");
checkRequestInit(usvString, "text/plain;charset=UTF-8", "This is a USVString");
checkRequestInit({toString: () => "hi!"}, "text/plain;charset=UTF-8", "hi!");
// Ensure test does not time out in case of missing URLSearchParams support.
if (self.URLSearchParams) {
var urlSearchParams = new URLSearchParams("name=value");
checkRequestInit(urlSearchParams, "application/x-www-form-urlencoded;charset=UTF-8", "name=value");
} else {
promise_test(function(test) {
return Promise.reject("URLSearchParams not supported");
}, "Initialize Request's body with application/x-www-form-urlencoded;charset=UTF-8");
}
@@ -0,0 +1,141 @@
function requestFromBody(body) {
return new Request(
"https://example.com",
{
method: "POST",
body,
duplex: "half",
},
);
}
test(() => {
const request = requestFromBody(undefined);
assert_equals(request.headers.get("Content-Type"), null);
}, "Default Content-Type for Request with empty body");
test(() => {
const blob = new Blob([]);
const request = requestFromBody(blob);
assert_equals(request.headers.get("Content-Type"), null);
}, "Default Content-Type for Request with Blob body (no type set)");
test(() => {
const blob = new Blob([], { type: "" });
const request = requestFromBody(blob);
assert_equals(request.headers.get("Content-Type"), null);
}, "Default Content-Type for Request with Blob body (empty type)");
test(() => {
const blob = new Blob([], { type: "a/b; c=d" });
const request = requestFromBody(blob);
assert_equals(request.headers.get("Content-Type"), "a/b; c=d");
}, "Default Content-Type for Request with Blob body (set type)");
test(() => {
const buffer = new Uint8Array();
const request = requestFromBody(buffer);
assert_equals(request.headers.get("Content-Type"), null);
}, "Default Content-Type for Request with buffer source body");
promise_test(async () => {
const formData = new FormData();
formData.append("a", "b");
const request = requestFromBody(formData);
const boundary = (await request.text()).split("\r\n")[0].slice(2);
assert_equals(
request.headers.get("Content-Type"),
`multipart/form-data; boundary=${boundary}`,
);
}, "Default Content-Type for Request with FormData body");
test(() => {
const usp = new URLSearchParams();
const request = requestFromBody(usp);
assert_equals(
request.headers.get("Content-Type"),
"application/x-www-form-urlencoded;charset=UTF-8",
);
}, "Default Content-Type for Request with URLSearchParams body");
test(() => {
const request = requestFromBody("");
assert_equals(
request.headers.get("Content-Type"),
"text/plain;charset=UTF-8",
);
}, "Default Content-Type for Request with string body");
test(() => {
const stream = new ReadableStream();
const request = requestFromBody(stream);
assert_equals(request.headers.get("Content-Type"), null);
}, "Default Content-Type for Request with ReadableStream body");
// -----------------------------------------------------------------------------
const OVERRIDE_MIME = "test/only; mime=type";
function requestFromBodyWithOverrideMime(body) {
return new Request(
"https://example.com",
{
method: "POST",
body,
headers: { "Content-Type": OVERRIDE_MIME },
duplex: "half",
},
);
}
test(() => {
const request = requestFromBodyWithOverrideMime(undefined);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with empty body");
test(() => {
const blob = new Blob([]);
const request = requestFromBodyWithOverrideMime(blob);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with Blob body (no type set)");
test(() => {
const blob = new Blob([], { type: "" });
const request = requestFromBodyWithOverrideMime(blob);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with Blob body (empty type)");
test(() => {
const blob = new Blob([], { type: "a/b; c=d" });
const request = requestFromBodyWithOverrideMime(blob);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with Blob body (set type)");
test(() => {
const buffer = new Uint8Array();
const request = requestFromBodyWithOverrideMime(buffer);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with buffer source body");
test(() => {
const formData = new FormData();
const request = requestFromBodyWithOverrideMime(formData);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with FormData body");
test(() => {
const usp = new URLSearchParams();
const request = requestFromBodyWithOverrideMime(usp);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with URLSearchParams body");
test(() => {
const request = requestFromBodyWithOverrideMime("");
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with string body");
test(() => {
const stream = new ReadableStream();
const request = requestFromBodyWithOverrideMime(stream);
assert_equals(request.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Request with ReadableStream body");
@@ -0,0 +1,26 @@
var priorities = ["high",
"low",
"auto"
];
for (idx in priorities) {
test(() => {
new Request("", {priority: priorities[idx]});
}, "new Request() with a '" + priorities[idx] + "' priority does not throw an error");
}
test(() => {
assert_throws_js(TypeError, () => {
new Request("", {priority: 'invalid'});
}, "a new Request() must throw a TypeError if RequestInit's priority is an invalid value");
}, "new Request() throws a TypeError if any of RequestInit's members' values are invalid");
for (idx in priorities) {
promise_test(function(t) {
return fetch('hello.txt', { priority: priorities[idx] });
}, "fetch() with a '" + priorities[idx] + "' priority completes successfully");
}
promise_test(function(t) {
return promise_rejects_js(t, TypeError, fetch('hello.txt', { priority: 'invalid' }));
}, "fetch() with an invalid priority returns a rejected promise with a TypeError");
@@ -0,0 +1,146 @@
// META: global=window,worker
"use strict";
const duplex = "half";
const method = "POST";
test(() => {
const body = new ReadableStream();
const request = new Request("...", { method, body, duplex });
assert_equals(request.body, body);
}, "Constructing a Request with a stream holds the original object.");
test((t) => {
const body = new ReadableStream();
body.getReader();
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "Constructing a Request with a stream on which getReader() is called");
test((t) => {
const body = new ReadableStream();
body.getReader().read();
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "Constructing a Request with a stream on which read() is called");
promise_test(async (t) => {
const body = new ReadableStream({ pull: c => c.enqueue(new Uint8Array()) });
const reader = body.getReader();
await reader.read();
reader.releaseLock();
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "Constructing a Request with a stream on which read() and releaseLock() are called");
test((t) => {
const request = new Request("...", { method: "POST", body: "..." });
request.body.getReader();
assert_throws_js(TypeError, () => new Request(request));
// This doesn't throw.
new Request(request, { body: "..." });
}, "Constructing a Request with a Request on which body.getReader() is called");
test((t) => {
const request = new Request("...", { method: "POST", body: "..." });
request.body.getReader().read();
assert_throws_js(TypeError, () => new Request(request));
// This doesn't throw.
new Request(request, { body: "..." });
}, "Constructing a Request with a Request on which body.getReader().read() is called");
promise_test(async (t) => {
const request = new Request("...", { method: "POST", body: "..." });
const reader = request.body.getReader();
await reader.read();
reader.releaseLock();
assert_throws_js(TypeError, () => new Request(request));
// This doesn't throw.
new Request(request, { body: "..." });
}, "Constructing a Request with a Request on which read() and releaseLock() are called");
test((t) => {
new Request("...", { method, body: null });
}, "It is OK to omit .duplex when the body is null.");
test((t) => {
new Request("...", { method, body: "..." });
}, "It is OK to omit .duplex when the body is a string.");
test((t) => {
new Request("...", { method, body: new Uint8Array(3) });
}, "It is OK to omit .duplex when the body is a Uint8Array.");
test((t) => {
new Request("...", { method, body: new Blob([]) });
}, "It is OK to omit .duplex when the body is a Blob.");
test((t) => {
const body = new ReadableStream();
assert_throws_js(TypeError,
() => new Request("...", { method, body }));
}, "It is error to omit .duplex when the body is a ReadableStream.");
test((t) => {
new Request("...", { method, body: null, duplex: "half" });
}, "It is OK to set .duplex = 'half' when the body is null.");
test((t) => {
new Request("...", { method, body: "...", duplex: "half" });
}, "It is OK to set .duplex = 'half' when the body is a string.");
test((t) => {
new Request("...", { method, body: new Uint8Array(3), duplex: "half" });
}, "It is OK to set .duplex = 'half' when the body is a Uint8Array.");
test((t) => {
new Request("...", { method, body: new Blob([]), duplex: "half" });
}, "It is OK to set .duplex = 'half' when the body is a Blob.");
test((t) => {
const body = new ReadableStream();
new Request("...", { method, body, duplex: "half" });
}, "It is OK to set .duplex = 'half' when the body is a ReadableStream.");
test((t) => {
const body = null;
const duplex = "full";
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "It is error to set .duplex = 'full' when the body is null.");
test((t) => {
const body = "...";
const duplex = "full";
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "It is error to set .duplex = 'full' when the body is a string.");
test((t) => {
const body = new Uint8Array(3);
const duplex = "full";
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "It is error to set .duplex = 'full' when the body is a Uint8Array.");
test((t) => {
const body = new Blob([]);
const duplex = "full";
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "It is error to set .duplex = 'full' when the body is a Blob.");
test((t) => {
const body = new ReadableStream();
const duplex = "full";
assert_throws_js(TypeError,
() => new Request("...", { method, body, duplex }));
}, "It is error to set .duplex = 'full' when the body is a ReadableStream.");
test((t) => {
const body = new ReadableStream();
const duplex = "half";
const req1 = new Request("...", { method, body, duplex });
const req2 = new Request(req1);
}, "It is OK to omit duplex when init.body is not given and input.body is given.");
@@ -0,0 +1,17 @@
// META: global=window,worker
// META: title=Request keepalive
// META: script=/common/utils.js
// META: script=/common/get-host-info.sub.js
test(() => {
assert_false(new Request('/').keepalive, 'default');
assert_true(new Request('/', {keepalive: true}).keepalive, 'true');
assert_false(new Request('/', {keepalive: false}).keepalive, 'false');
assert_true(new Request('/', {keepalive: 1}).keepalive, 'truish');
assert_false(new Request('/', {keepalive: 0}).keepalive, 'falsy');
}, 'keepalive flag');
test(() => {
const init = {method: 'POST', keepalive: true, body: new ReadableStream()};
assert_throws_js(TypeError, () => {new Request('/', init)});
}, 'keepalive flag with stream body');
@@ -0,0 +1,143 @@
// META: global=window,worker
// META: title=Request structure
var request = new Request("");
var methods = ["clone",
//Request implements Body
"arrayBuffer",
"blob",
"formData",
"json",
"text"
];
var attributes = ["method",
"url",
"headers",
"destination",
"referrer",
"referrerPolicy",
"mode",
"credentials",
"cache",
"redirect",
"integrity",
"isReloadNavigation",
"isHistoryNavigation",
"duplex",
//Request implements Body
"bodyUsed"
];
var internalAttributes = ["priority",
"internalpriority",
"blocking"
];
function isReadOnly(request, attributeToCheck) {
var defaultValue = undefined;
var newValue = undefined;
switch (attributeToCheck) {
case "method":
defaultValue = "GET";
newValue = "POST";
break;
case "url":
//default value is base url
//i.e http://example.com/fetch/api/request-structure.html
newValue = "http://url.test";
break;
case "headers":
request.headers = new Headers ({"name":"value"});
assert_false(request.headers.has("name"), "Headers attribute is read only");
return;
case "destination":
defaultValue = "";
newValue = "worker";
break;
case "referrer":
defaultValue = "about:client";
newValue = "http://url.test";
break;
case "referrerPolicy":
defaultValue = "";
newValue = "unsafe-url";
break;
case "mode":
defaultValue = "cors";
newValue = "navigate";
break;
case "credentials":
defaultValue = "same-origin";
newValue = "cors";
break;
case "cache":
defaultValue = "default";
newValue = "reload";
break;
case "redirect":
defaultValue = "follow";
newValue = "manual";
break;
case "integrity":
newValue = "CannotWriteIntegrity";
break;
case "bodyUsed":
defaultValue = false;
newValue = true;
break;
case "isReloadNavigation":
defaultValue = false;
newValue = true;
break;
case "isHistoryNavigation":
defaultValue = false;
newValue = true;
break;
case "duplex":
defaultValue = "half";
newValue = "full";
break;
default:
return;
}
request[attributeToCheck] = newValue;
if (defaultValue === undefined)
assert_not_equals(request[attributeToCheck], newValue, "Attribute " + attributeToCheck + " is read only");
else
assert_equals(request[attributeToCheck], defaultValue,
"Attribute " + attributeToCheck + " is read only. Default value is " + defaultValue);
}
for (var idx in methods) {
test(function() {
assert_true(methods[idx] in request, "request has " + methods[idx] + " method");
}, "Request has " + methods[idx] + " method");
}
for (var idx in attributes) {
test(function() {
assert_true(attributes[idx] in request, "request has " + attributes[idx] + " attribute");
isReadOnly(request, attributes[idx]);
}, "Check " + attributes[idx] + " attribute");
}
for (var idx in internalAttributes) {
test(function() {
assert_false(internalAttributes[idx] in request, "request does not expose " + internalAttributes[idx] + " attribute");
}, "Request does not expose " + internalAttributes[idx] + " attribute");
}