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 @@
[
["accept", "\""],
["accept", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"],
["accept-language", "\u0001"],
["accept-language", "@"],
["authorization", "basics"],
["content-language", "\u0001"],
["content-language", "@"],
["content-type", "text/html"],
["content-type", "text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901"],
["range", "bytes 0-"],
["test", "hi"]
]
@@ -0,0 +1,266 @@
// META: title=Headers set-cookie special cases
// META: global=window,worker
const headerList = [
["set-cookie", "foo=bar"],
["Set-Cookie", "fizz=buzz; domain=example.com"],
];
const setCookie2HeaderList = [
["set-cookie2", "foo2=bar2"],
["Set-Cookie2", "fizz2=buzz2; domain=example2.com"],
];
function assert_nested_array_equals(actual, expected) {
assert_equals(actual.length, expected.length, "Array length is not equal");
for (let i = 0; i < expected.length; i++) {
assert_array_equals(actual[i], expected[i]);
}
}
test(function () {
const headers = new Headers(headerList);
assert_equals(
headers.get("set-cookie"),
"foo=bar, fizz=buzz; domain=example.com",
);
}, "Headers.prototype.get combines set-cookie headers in order");
test(function () {
const headers = new Headers(headerList);
const list = [...headers];
assert_nested_array_equals(list, [
["set-cookie", "foo=bar"],
["set-cookie", "fizz=buzz; domain=example.com"],
]);
}, "Headers iterator does not combine set-cookie headers");
test(function () {
const headers = new Headers(setCookie2HeaderList);
const list = [...headers];
assert_nested_array_equals(list, [
["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
]);
}, "Headers iterator does not special case set-cookie2 headers");
test(function () {
const headers = new Headers([...headerList, ...setCookie2HeaderList]);
const list = [...headers];
assert_nested_array_equals(list, [
["set-cookie", "foo=bar"],
["set-cookie", "fizz=buzz; domain=example.com"],
["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
]);
}, "Headers iterator does not combine set-cookie & set-cookie2 headers");
test(function () {
// Values are in non alphabetic order, and the iterator should yield in the
// headers in the exact order of the input.
const headers = new Headers([
["set-cookie", "z=z"],
["set-cookie", "a=a"],
["set-cookie", "n=n"],
]);
const list = [...headers];
assert_nested_array_equals(list, [
["set-cookie", "z=z"],
["set-cookie", "a=a"],
["set-cookie", "n=n"],
]);
}, "Headers iterator preserves set-cookie ordering");
test(
function () {
const headers = new Headers([
["xylophone-header", "1"],
["best-header", "2"],
["set-cookie", "3"],
["a-cool-header", "4"],
["set-cookie", "5"],
["a-cool-header", "6"],
["best-header", "7"],
]);
const list = [...headers];
assert_nested_array_equals(list, [
["a-cool-header", "4, 6"],
["best-header", "2, 7"],
["set-cookie", "3"],
["set-cookie", "5"],
["xylophone-header", "1"],
]);
},
"Headers iterator preserves per header ordering, but sorts keys alphabetically",
);
test(
function () {
const headers = new Headers([
["xylophone-header", "7"],
["best-header", "6"],
["set-cookie", "5"],
["a-cool-header", "4"],
["set-cookie", "3"],
["a-cool-header", "2"],
["best-header", "1"],
]);
const list = [...headers];
assert_nested_array_equals(list, [
["a-cool-header", "4, 2"],
["best-header", "6, 1"],
["set-cookie", "5"],
["set-cookie", "3"],
["xylophone-header", "7"],
]);
},
"Headers iterator preserves per header ordering, but sorts keys alphabetically (and ignores value ordering)",
);
test(function () {
const headers = new Headers([["fizz", "buzz"], ["X-Header", "test"]]);
const iterator = headers[Symbol.iterator]();
assert_array_equals(iterator.next().value, ["fizz", "buzz"]);
headers.append("Set-Cookie", "a=b");
assert_array_equals(iterator.next().value, ["set-cookie", "a=b"]);
headers.append("Accept", "text/html");
assert_array_equals(iterator.next().value, ["set-cookie", "a=b"]);
assert_array_equals(iterator.next().value, ["x-header", "test"]);
headers.append("set-cookie", "c=d");
assert_array_equals(iterator.next().value, ["x-header", "test"]);
assert_true(iterator.next().done);
}, "Headers iterator is correctly updated with set-cookie changes");
test(function () {
const headers = new Headers([
["set-cookie", "a"],
["set-cookie", "b"],
["set-cookie", "c"]
]);
const iterator = headers[Symbol.iterator]();
assert_array_equals(iterator.next().value, ["set-cookie", "a"]);
headers.delete("set-cookie");
headers.append("set-cookie", "d");
headers.append("set-cookie", "e");
headers.append("set-cookie", "f");
assert_array_equals(iterator.next().value, ["set-cookie", "e"]);
assert_array_equals(iterator.next().value, ["set-cookie", "f"]);
assert_true(iterator.next().done);
}, "Headers iterator is correctly updated with set-cookie changes #2");
test(function () {
const headers = new Headers(headerList);
assert_true(headers.has("sEt-cOoKiE"));
}, "Headers.prototype.has works for set-cookie");
test(function () {
const headers = new Headers(setCookie2HeaderList);
headers.append("set-Cookie", "foo=bar");
headers.append("sEt-cOoKiE", "fizz=buzz");
const list = [...headers];
assert_nested_array_equals(list, [
["set-cookie", "foo=bar"],
["set-cookie", "fizz=buzz"],
["set-cookie2", "foo2=bar2, fizz2=buzz2; domain=example2.com"],
]);
}, "Headers.prototype.append works for set-cookie");
test(function () {
const headers = new Headers(headerList);
headers.set("set-cookie", "foo2=bar2");
const list = [...headers];
assert_nested_array_equals(list, [
["set-cookie", "foo2=bar2"],
]);
}, "Headers.prototype.set works for set-cookie");
test(function () {
const headers = new Headers(headerList);
headers.delete("set-Cookie");
const list = [...headers];
assert_nested_array_equals(list, []);
}, "Headers.prototype.delete works for set-cookie");
test(function () {
const headers = new Headers();
assert_array_equals(headers.getSetCookie(), []);
}, "Headers.prototype.getSetCookie with no headers present");
test(function () {
const headers = new Headers([headerList[0]]);
assert_array_equals(headers.getSetCookie(), ["foo=bar"]);
}, "Headers.prototype.getSetCookie with one header");
test(function () {
const headers = new Headers({ "Set-Cookie": "foo=bar" });
assert_array_equals(headers.getSetCookie(), ["foo=bar"]);
}, "Headers.prototype.getSetCookie with one header created from an object");
test(function () {
const headers = new Headers(headerList);
assert_array_equals(headers.getSetCookie(), [
"foo=bar",
"fizz=buzz; domain=example.com",
]);
}, "Headers.prototype.getSetCookie with multiple headers");
test(function () {
const headers = new Headers([["set-cookie", ""]]);
assert_array_equals(headers.getSetCookie(), [""]);
}, "Headers.prototype.getSetCookie with an empty header");
test(function () {
const headers = new Headers([["set-cookie", "x"], ["set-cookie", "x"]]);
assert_array_equals(headers.getSetCookie(), ["x", "x"]);
}, "Headers.prototype.getSetCookie with two equal headers");
test(function () {
const headers = new Headers([
["set-cookie2", "x"],
["set-cookie", "y"],
["set-cookie2", "z"],
]);
assert_array_equals(headers.getSetCookie(), ["y"]);
}, "Headers.prototype.getSetCookie ignores set-cookie2 headers");
test(function () {
// Values are in non alphabetic order, and the iterator should yield in the
// headers in the exact order of the input.
const headers = new Headers([
["set-cookie", "z=z"],
["set-cookie", "a=a"],
["set-cookie", "n=n"],
]);
assert_array_equals(headers.getSetCookie(), ["z=z", "a=a", "n=n"]);
}, "Headers.prototype.getSetCookie preserves header ordering");
test(function () {
const headers = new Headers({"Set-Cookie": " a=b\n"});
headers.append("set-cookie", "\n\rc=d ");
assert_nested_array_equals([...headers], [
["set-cookie", "a=b"],
["set-cookie", "c=d"]
]);
headers.set("set-cookie", "\te=f ");
assert_nested_array_equals([...headers], [["set-cookie", "e=f"]]);
}, "Adding Set-Cookie headers normalizes their value");
test(function () {
assert_throws_js(TypeError, () => {
new Headers({"set-cookie": "\0"});
});
const headers = new Headers();
assert_throws_js(TypeError, () => {
headers.append("Set-Cookie", "a\nb");
});
assert_throws_js(TypeError, () => {
headers.set("Set-Cookie", "a\rb");
});
}, "Adding invalid Set-Cookie headers throws");
test(function () {
const response = new Response();
response.headers.append("Set-Cookie", "foo=bar");
assert_array_equals(response.headers.getSetCookie(), []);
response.headers.append("sEt-cOokIe", "bar=baz");
assert_array_equals(response.headers.getSetCookie(), []);
}, "Set-Cookie is a forbidden response header");
@@ -0,0 +1,275 @@
// META: title=Headers structure
// META: global=window,worker
"use strict";
test(function() {
new Headers();
}, "Create headers from no parameter");
test(function() {
new Headers(undefined);
}, "Create headers from undefined parameter");
test(function() {
new Headers({});
}, "Create headers from empty object");
var parameters = [null, 1];
parameters.forEach(function(parameter) {
test(function() {
assert_throws_js(TypeError, function() { new Headers(parameter) });
}, "Create headers with " + parameter + " should throw");
});
var headerDict = {"name1": "value1",
"name2": "value2",
"name3": "value3",
"name4": null,
"name5": undefined,
"name6": 1,
"Content-Type": "value4"
};
var headerSeq = [];
for (var name in headerDict)
headerSeq.push([name, headerDict[name]]);
test(function() {
var headers = new Headers(headerSeq);
for (name in headerDict) {
assert_equals(headers.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
}
assert_equals(headers.get("length"), null, "init should be treated as a sequence, not as a dictionary");
}, "Create headers with sequence");
test(function() {
var headers = new Headers(headerDict);
for (name in headerDict) {
assert_equals(headers.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
}
}, "Create headers with record");
test(function() {
var headers = new Headers(headerDict);
var headers2 = new Headers(headers);
for (name in headerDict) {
assert_equals(headers2.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
}
}, "Create headers with existing headers");
test(function() {
var headers = new Headers()
headers[Symbol.iterator] = function *() {
yield ["test", "test"]
}
var headers2 = new Headers(headers)
assert_equals(headers2.get("test"), "test")
}, "Create headers with existing headers with custom iterator");
test(function() {
var headers = new Headers();
for (name in headerDict) {
headers.append(name, headerDict[name]);
assert_equals(headers.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
}
}, "Check append method");
test(function() {
var headers = new Headers();
for (name in headerDict) {
headers.set(name, headerDict[name]);
assert_equals(headers.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
}
}, "Check set method");
test(function() {
var headers = new Headers(headerDict);
for (name in headerDict)
assert_true(headers.has(name),"headers has name " + name);
assert_false(headers.has("nameNotInHeaders"),"headers do not have header: nameNotInHeaders");
}, "Check has method");
test(function() {
var headers = new Headers(headerDict);
for (name in headerDict) {
assert_true(headers.has(name),"headers have a header: " + name);
headers.delete(name)
assert_true(!headers.has(name),"headers do not have anymore a header: " + name);
}
}, "Check delete method");
test(function() {
var headers = new Headers(headerDict);
for (name in headerDict)
assert_equals(headers.get(name), String(headerDict[name]),
"name: " + name + " has value: " + headerDict[name]);
assert_equals(headers.get("nameNotInHeaders"), null, "header: nameNotInHeaders has no value");
}, "Check get method");
var headerEntriesDict = {"name1": "value1",
"Name2": "value2",
"name": "value3",
"content-Type": "value4",
"Content-Typ": "value5",
"Content-Types": "value6"
};
var sortedHeaderDict = {};
var headerValues = [];
var sortedHeaderKeys = Object.keys(headerEntriesDict).map(function(value) {
sortedHeaderDict[value.toLowerCase()] = headerEntriesDict[value];
headerValues.push(headerEntriesDict[value]);
return value.toLowerCase();
}).sort();
var iteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
function checkIteratorProperties(iterator) {
var prototype = Object.getPrototypeOf(iterator);
assert_equals(Object.getPrototypeOf(prototype), iteratorPrototype);
var descriptor = Object.getOwnPropertyDescriptor(prototype, "next");
assert_true(descriptor.configurable, "configurable");
assert_true(descriptor.enumerable, "enumerable");
assert_true(descriptor.writable, "writable");
}
test(function() {
var headers = new Headers(headerEntriesDict);
var actual = headers.keys();
checkIteratorProperties(actual);
sortedHeaderKeys.forEach(function(key) {
const entry = actual.next();
assert_false(entry.done);
assert_equals(entry.value, key);
});
assert_true(actual.next().done);
assert_true(actual.next().done);
for (const key of headers.keys())
assert_true(sortedHeaderKeys.indexOf(key) != -1);
}, "Check keys method");
test(function() {
var headers = new Headers(headerEntriesDict);
var actual = headers.values();
checkIteratorProperties(actual);
sortedHeaderKeys.forEach(function(key) {
const entry = actual.next();
assert_false(entry.done);
assert_equals(entry.value, sortedHeaderDict[key]);
});
assert_true(actual.next().done);
assert_true(actual.next().done);
for (const value of headers.values())
assert_true(headerValues.indexOf(value) != -1);
}, "Check values method");
test(function() {
var headers = new Headers(headerEntriesDict);
var actual = headers.entries();
checkIteratorProperties(actual);
sortedHeaderKeys.forEach(function(key) {
const entry = actual.next();
assert_false(entry.done);
assert_equals(entry.value[0], key);
assert_equals(entry.value[1], sortedHeaderDict[key]);
});
assert_true(actual.next().done);
assert_true(actual.next().done);
for (const entry of headers.entries())
assert_equals(entry[1], sortedHeaderDict[entry[0]]);
}, "Check entries method");
test(function() {
var headers = new Headers(headerEntriesDict);
var actual = headers[Symbol.iterator]();
sortedHeaderKeys.forEach(function(key) {
const entry = actual.next();
assert_false(entry.done);
assert_equals(entry.value[0], key);
assert_equals(entry.value[1], sortedHeaderDict[key]);
});
assert_true(actual.next().done);
assert_true(actual.next().done);
}, "Check Symbol.iterator method");
test(function() {
var headers = new Headers(headerEntriesDict);
var reference = sortedHeaderKeys[Symbol.iterator]();
headers.forEach(function(value, key, container) {
assert_equals(headers, container);
const entry = reference.next();
assert_false(entry.done);
assert_equals(key, entry.value);
assert_equals(value, sortedHeaderDict[entry.value]);
});
assert_true(reference.next().done);
}, "Check forEach method");
test(() => {
const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0"});
const actualKeys = [];
const actualValues = [];
for (const [header, value] of headers) {
actualKeys.push(header);
actualValues.push(value);
headers.delete("foo");
}
assert_array_equals(actualKeys, ["bar", "baz"]);
assert_array_equals(actualValues, ["0", "1"]);
}, "Iteration skips elements removed while iterating");
test(() => {
const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"});
const actualKeys = [];
const actualValues = [];
for (const [header, value] of headers) {
actualKeys.push(header);
actualValues.push(value);
if (header === "baz")
headers.delete("bar");
}
assert_array_equals(actualKeys, ["bar", "baz", "quux"]);
assert_array_equals(actualValues, ["0", "1", "3"]);
}, "Removing elements already iterated over causes an element to be skipped during iteration");
test(() => {
const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"});
const actualKeys = [];
const actualValues = [];
for (const [header, value] of headers) {
actualKeys.push(header);
actualValues.push(value);
if (header === "baz")
headers.append("X-yZ", "4");
}
assert_array_equals(actualKeys, ["bar", "baz", "foo", "quux", "x-yz"]);
assert_array_equals(actualValues, ["0", "1", "2", "3", "4"]);
}, "Appending a value pair during iteration causes it to be reached during iteration");
test(() => {
const headers = new Headers({"foo": "2", "baz": "1", "BAR": "0", "quux": "3"});
const actualKeys = [];
const actualValues = [];
for (const [header, value] of headers) {
actualKeys.push(header);
actualValues.push(value);
if (header === "baz")
headers.append("abc", "-1");
}
assert_array_equals(actualKeys, ["bar", "baz", "baz", "foo", "quux"]);
assert_array_equals(actualValues, ["0", "1", "1", "2", "3"]);
}, "Prepending a value pair before the current element position causes it to be skipped during iteration and adds the current element a second time");
@@ -0,0 +1,54 @@
// META: title=Headers case management
// META: global=window,worker
"use strict";
var headerDictCase = {"UPPERCASE": "value1",
"lowercase": "value2",
"mixedCase": "value3",
"Content-TYPE": "value4"
};
function checkHeadersCase(originalName, headersToCheck, expectedDict) {
var lowCaseName = originalName.toLowerCase();
var upCaseName = originalName.toUpperCase();
var expectedValue = expectedDict[originalName];
assert_equals(headersToCheck.get(originalName), expectedValue,
"name: " + originalName + " has value: " + expectedValue);
assert_equals(headersToCheck.get(lowCaseName), expectedValue,
"name: " + lowCaseName + " has value: " + expectedValue);
assert_equals(headersToCheck.get(upCaseName), expectedValue,
"name: " + upCaseName + " has value: " + expectedValue);
}
test(function() {
var headers = new Headers(headerDictCase);
for (const name in headerDictCase)
checkHeadersCase(name, headers, headerDictCase)
}, "Create headers, names use characters with different case");
test(function() {
var headers = new Headers();
for (const name in headerDictCase) {
headers.append(name, headerDictCase[name]);
checkHeadersCase(name, headers, headerDictCase);
}
}, "Check append method, names use characters with different case");
test(function() {
var headers = new Headers();
for (const name in headerDictCase) {
headers.set(name, headerDictCase[name]);
checkHeadersCase(name, headers, headerDictCase);
}
}, "Check set method, names use characters with different case");
test(function() {
var headers = new Headers();
for (const name in headerDictCase)
headers.set(name, headerDictCase[name]);
for (const name in headerDictCase)
headers.delete(name.toLowerCase());
for (const name in headerDictCase)
assert_false(headers.has(name), "header " + name + " should have been deleted");
}, "Check delete method, names use characters with different case");
@@ -0,0 +1,66 @@
// META: title=Headers have combined (and sorted) values
// META: global=window,worker
"use strict";
var headerSeqCombine = [["single", "singleValue"],
["double", "doubleValue1"],
["double", "doubleValue2"],
["triple", "tripleValue1"],
["triple", "tripleValue2"],
["triple", "tripleValue3"]
];
var expectedDict = {"single": "singleValue",
"double": "doubleValue1, doubleValue2",
"triple": "tripleValue1, tripleValue2, tripleValue3"
};
test(function() {
var headers = new Headers(headerSeqCombine);
for (const name in expectedDict)
assert_equals(headers.get(name), expectedDict[name]);
}, "Create headers using same name for different values");
test(function() {
var headers = new Headers(headerSeqCombine);
for (const name in expectedDict) {
assert_true(headers.has(name), "name: " + name + " has value(s)");
headers.delete(name);
assert_false(headers.has(name), "name: " + name + " has no value(s) anymore");
}
}, "Check delete and has methods when using same name for different values");
test(function() {
var headers = new Headers(headerSeqCombine);
for (const name in expectedDict) {
headers.set(name,"newSingleValue");
assert_equals(headers.get(name), "newSingleValue", "name: " + name + " has value: newSingleValue");
}
}, "Check set methods when called with already used name");
test(function() {
var headers = new Headers(headerSeqCombine);
for (const name in expectedDict) {
var value = headers.get(name);
headers.append(name,"newSingleValue");
assert_equals(headers.get(name), (value + ", " + "newSingleValue"));
}
}, "Check append methods when called with already used name");
test(() => {
const headers = new Headers([["1", "a"],["1", "b"]]);
for(let header of headers) {
assert_array_equals(header, ["1", "a, b"]);
}
}, "Iterate combined values");
test(() => {
const headers = new Headers([["2", "a"], ["1", "b"], ["2", "b"]]),
expected = [["1", "b"], ["2", "a, b"]];
let i = 0;
for(let header of headers) {
assert_array_equals(header, expected[i]);
i++;
}
assert_equals(i, 2);
}, "Iterate combined values in sorted order")
@@ -0,0 +1,96 @@
// META: title=Headers errors
// META: global=window,worker
"use strict";
test(function() {
assert_throws_js(TypeError, function() { new Headers([["name"]]); });
}, "Create headers giving an array having one string as init argument");
test(function() {
assert_throws_js(TypeError, function() { new Headers([["invalid", "invalidValue1", "invalidValue2"]]); });
}, "Create headers giving an array having three strings as init argument");
test(function() {
assert_throws_js(TypeError, function() { new Headers([["invalidĀ", "Value1"]]); });
}, "Create headers giving bad header name as init argument");
test(function() {
assert_throws_js(TypeError, function() { new Headers([["name", "invalidValueĀ"]]); });
}, "Create headers giving bad header value as init argument");
var badNames = ["invalidĀ", {}];
var badValues = ["invalidĀ"];
badNames.forEach(function(name) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.get(name); });
}, "Check headers get with an invalid name " + name);
});
badNames.forEach(function(name) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.delete(name); });
}, "Check headers delete with an invalid name " + name);
});
badNames.forEach(function(name) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.has(name); });
}, "Check headers has with an invalid name " + name);
});
badNames.forEach(function(name) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.set(name, "Value1"); });
}, "Check headers set with an invalid name " + name);
});
badValues.forEach(function(value) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.set("name", value); });
}, "Check headers set with an invalid value " + value);
});
badNames.forEach(function(name) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.append("invalidĀ", "Value1"); });
}, "Check headers append with an invalid name " + name);
});
badValues.forEach(function(value) {
test(function() {
var headers = new Headers();
assert_throws_js(TypeError, function() { headers.append("name", value); });
}, "Check headers append with an invalid value " + value);
});
test(function() {
var headers = new Headers([["name", "value"]]);
assert_throws_js(TypeError, function() { headers.forEach(); });
assert_throws_js(TypeError, function() { headers.forEach(undefined); });
assert_throws_js(TypeError, function() { headers.forEach(1); });
}, "Headers forEach throws if argument is not callable");
test(function() {
var headers = new Headers([["name1", "value1"], ["name2", "value2"], ["name3", "value3"]]);
var counter = 0;
try {
headers.forEach(function(value, name) {
counter++;
if (name == "name2")
throw "error";
});
} catch (e) {
assert_equals(counter, 2);
assert_equals(e, "error");
return;
}
assert_unreached();
}, "Headers forEach loop should stop if callback is throwing exception");
@@ -0,0 +1,51 @@
// META: global=window,worker
let forbiddenMethods = [
"TRACE",
"TRACK",
"CONNECT",
"trace",
"track",
"connect",
"\rtrace",
"\ttrack",
"\nconnect",
"trace,",
"GET,track ",
" connect",
];
let overrideHeaders = [
"x-http-method-override",
"x-http-method",
"x-method-override",
"X-HTTP-METHOD-OVERRIDE",
"X-HTTP-METHOD",
"X-METHOD-OVERRIDE",
];
for (forbiddenMethod of forbiddenMethods) {
for (overrideHeader of overrideHeaders) {
test(() => {
let r = new Request("https://site.example/");
r.headers.append(overrideHeader, forbiddenMethod);
assert_false(r.headers.has(overrideHeader));
}, `header ${overrideHeader} is forbidden to use value ${forbiddenMethod}`);
}
}
let permittedValues = [
"GETTRACE",
"GET",
"\",TRACE\",",
];
for (permittedValue of permittedValues) {
for (overrideHeader of overrideHeaders) {
test(() => {
let r = new Request("https://site.example/");
r.headers.append(overrideHeader, permittedValue);
assert_equals(permittedValue, r.headers.get(overrideHeader));
}, `header ${overrideHeader} is allowed to use value ${permittedValue}`);
}
}
@@ -0,0 +1,59 @@
// META: global=window,worker
"use strict";
promise_test(() => fetch("../cors/resources/not-cors-safelisted.json").then(res => res.json().then(runTests)), "Loading data…");
const longValue = "s".repeat(127);
[
{
"headers": ["accept", "accept-language", "content-language"],
"values": [longValue, "", longValue]
},
{
"headers": ["accept", "accept-language", "content-language"],
"values": ["", longValue]
},
{
"headers": ["content-type"],
"values": ["text/plain;" + "s".repeat(116), "text/plain"]
}
].forEach(testItem => {
testItem.headers.forEach(header => {
test(() => {
const noCorsHeaders = new Request("about:blank", { mode: "no-cors" }).headers;
testItem.values.forEach((value) => {
noCorsHeaders.append(header, value);
assert_equals(noCorsHeaders.get(header), testItem.values[0], '1');
});
noCorsHeaders.set(header, testItem.values.join(", "));
assert_equals(noCorsHeaders.get(header), testItem.values[0], '2');
noCorsHeaders.delete(header);
assert_false(noCorsHeaders.has(header));
}, "\"no-cors\" Headers object cannot have " + header + " set to " + testItem.values.join(", "));
});
});
function runTests(testArray) {
testArray = testArray.concat([
["dpr", "2"],
["rtt", "1.0"],
["downlink", "-1.0"],
["ect", "6g"],
["save-data", "on"],
["viewport-width", "100"],
["width", "100"],
["unknown", "doesitmatter"]
]);
testArray.forEach(testItem => {
const [headerName, headerValue] = testItem;
test(() => {
const noCorsHeaders = new Request("about:blank", { mode: "no-cors" }).headers;
noCorsHeaders.append(headerName, headerValue);
assert_false(noCorsHeaders.has(headerName));
noCorsHeaders.set(headerName, headerValue);
assert_false(noCorsHeaders.has(headerName));
}, "\"no-cors\" Headers object cannot have " + headerName + "/" + headerValue + " as header");
});
}
@@ -0,0 +1,56 @@
// META: title=Headers normalize values
// META: global=window,worker
"use strict";
const expectations = {
"name1": [" space ", "space"],
"name2": ["\ttab\t", "tab"],
"name3": [" spaceAndTab\t", "spaceAndTab"],
"name4": ["\r\n newLine", "newLine"], //obs-fold cases
"name5": ["newLine\r\n ", "newLine"],
"name6": ["\r\n\tnewLine", "newLine"],
"name7": ["\t\f\tnewLine\n", "\f\tnewLine"],
"name8": ["newLine\xa0", "newLine\xa0"], // \xa0 == non breaking space
};
test(function () {
const headerDict = Object.fromEntries(
Object.entries(expectations).map(([name, [actual]]) => [name, actual]),
);
var headers = new Headers(headerDict);
for (const name in expectations) {
const expected = expectations[name][1];
assert_equals(
headers.get(name),
expected,
"name: " + name + " has normalized value: " + expected,
);
}
}, "Create headers with not normalized values");
test(function () {
var headers = new Headers();
for (const name in expectations) {
headers.append(name, expectations[name][0]);
const expected = expectations[name][1];
assert_equals(
headers.get(name),
expected,
"name: " + name + " has value: " + expected,
);
}
}, "Check append method with not normalized values");
test(function () {
var headers = new Headers();
for (const name in expectations) {
headers.set(name, expectations[name][0]);
const expected = expectations[name][1];
assert_equals(
headers.get(name),
expected,
"name: " + name + " has value: " + expected,
);
}
}, "Check set method with not normalized values");
@@ -0,0 +1,357 @@
// META: global=window,worker
"use strict";
var log = [];
function clearLog() {
log = [];
}
function addLogEntry(name, args) {
log.push([ name, ...args ]);
}
var loggingHandler = {
};
setup(function() {
for (let prop of Object.getOwnPropertyNames(Reflect)) {
loggingHandler[prop] = function(...args) {
addLogEntry(prop, args);
return Reflect[prop](...args);
}
}
});
test(function() {
var h = new Headers();
assert_equals([...h].length, 0);
}, "Passing nothing to Headers constructor");
test(function() {
var h = new Headers(undefined);
assert_equals([...h].length, 0);
}, "Passing undefined to Headers constructor");
test(function() {
assert_throws_js(TypeError, function() {
var h = new Headers(null);
});
}, "Passing null to Headers constructor");
test(function() {
this.add_cleanup(clearLog);
var record = { a: "b" };
var proxy = new Proxy(record, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 4);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Check the results.
assert_equals([...h].length, 1);
assert_array_equals([...h.keys()], ["a"]);
assert_true(h.has("a"));
assert_equals(h.get("a"), "b");
}, "Basic operation with one property");
test(function() {
this.add_cleanup(clearLog);
var recordProto = { c: "d" };
var record = Object.create(recordProto, { a: { value: "b", enumerable: true } });
var proxy = new Proxy(record, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 4);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Check the results.
assert_equals([...h].length, 1);
assert_array_equals([...h.keys()], ["a"]);
assert_true(h.has("a"));
assert_equals(h.get("a"), "b");
}, "Basic operation with one property and a proto");
test(function() {
this.add_cleanup(clearLog);
var record = { a: "b", c: "d" };
var proxy = new Proxy(record, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 6);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "c"]);
// Then the second [[Get]] from step 5.2.
assert_array_equals(log[5], ["get", record, "c", proxy]);
// Check the results.
assert_equals([...h].length, 2);
assert_array_equals([...h.keys()], ["a", "c"]);
assert_true(h.has("a"));
assert_equals(h.get("a"), "b");
assert_true(h.has("c"));
assert_equals(h.get("c"), "d");
}, "Correct operation ordering with two properties");
test(function() {
this.add_cleanup(clearLog);
var record = { a: "b", "\uFFFF": "d" };
var proxy = new Proxy(record, loggingHandler);
assert_throws_js(TypeError, function() {
var h = new Headers(proxy);
});
assert_equals(log.length, 5);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "\uFFFF"]);
// The second [[Get]] never happens, because we convert the invalid name to a
// ByteString first and throw.
}, "Correct operation ordering with two properties one of which has an invalid name");
test(function() {
this.add_cleanup(clearLog);
var record = { a: "\uFFFF", c: "d" }
var proxy = new Proxy(record, loggingHandler);
assert_throws_js(TypeError, function() {
var h = new Headers(proxy);
});
assert_equals(log.length, 4);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Nothing else after this, because converting the result of that [[Get]] to a
// ByteString throws.
}, "Correct operation ordering with two properties one of which has an invalid value");
test(function() {
this.add_cleanup(clearLog);
var record = {};
Object.defineProperty(record, "a", { value: "b", enumerable: false });
Object.defineProperty(record, "c", { value: "d", enumerable: true });
Object.defineProperty(record, "e", { value: "f", enumerable: false });
var proxy = new Proxy(record, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 6);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// No [[Get]] because not enumerable
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[3], ["getOwnPropertyDescriptor", record, "c"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[4], ["get", record, "c", proxy]);
// Then the third [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[5], ["getOwnPropertyDescriptor", record, "e"]);
// No [[Get]] because not enumerable
// Check the results.
assert_equals([...h].length, 1);
assert_array_equals([...h.keys()], ["c"]);
assert_true(h.has("c"));
assert_equals(h.get("c"), "d");
}, "Correct operation ordering with non-enumerable properties");
test(function() {
this.add_cleanup(clearLog);
var record = {a: "b", c: "d", e: "f"};
var lyingHandler = {
getOwnPropertyDescriptor: function(target, name) {
if (name == "a" || name == "e") {
return undefined;
}
return Reflect.getOwnPropertyDescriptor(target, name);
}
};
var lyingProxy = new Proxy(record, lyingHandler);
var proxy = new Proxy(lyingProxy, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 6);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", lyingProxy]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", lyingProxy, "a"]);
// No [[Get]] because no descriptor
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[3], ["getOwnPropertyDescriptor", lyingProxy, "c"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[4], ["get", lyingProxy, "c", proxy]);
// Then the third [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[5], ["getOwnPropertyDescriptor", lyingProxy, "e"]);
// No [[Get]] because no descriptor
// Check the results.
assert_equals([...h].length, 1);
assert_array_equals([...h.keys()], ["c"]);
assert_true(h.has("c"));
assert_equals(h.get("c"), "d");
}, "Correct operation ordering with undefined descriptors");
test(function() {
this.add_cleanup(clearLog);
var record = {a: "b", c: "d"};
var lyingHandler = {
ownKeys: function() {
return [ "a", "c", "a", "c" ];
},
};
var lyingProxy = new Proxy(record, lyingHandler);
var proxy = new Proxy(lyingProxy, loggingHandler);
// Returning duplicate keys from ownKeys() throws a TypeError.
assert_throws_js(TypeError,
function() { var h = new Headers(proxy); });
assert_equals(log.length, 2);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", lyingProxy]);
}, "Correct operation ordering with repeated keys");
test(function() {
this.add_cleanup(clearLog);
var record = {
a: "b",
[Symbol.toStringTag]: {
// Make sure the ToString conversion of the value happens
// after the ToString conversion of the key.
toString: function () { addLogEntry("toString", [this]); return "nope"; }
},
c: "d" };
var proxy = new Proxy(record, loggingHandler);
assert_throws_js(TypeError,
function() { var h = new Headers(proxy); });
assert_equals(log.length, 7);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[4], ["getOwnPropertyDescriptor", record, "c"]);
// Then the second [[Get]] from step 5.2.
assert_array_equals(log[5], ["get", record, "c", proxy]);
// Then the third [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[6], ["getOwnPropertyDescriptor", record,
Symbol.toStringTag]);
// Then we throw an exception converting the Symbol to a string, before we do
// the third [[Get]].
}, "Basic operation with Symbol keys");
test(function() {
this.add_cleanup(clearLog);
var record = {
a: {
toString: function() { addLogEntry("toString", [this]); return "b"; }
},
[Symbol.toStringTag]: {
toString: function () { addLogEntry("toString", [this]); return "nope"; }
},
c: {
toString: function() { addLogEntry("toString", [this]); return "d"; }
}
};
// Now make that Symbol-named property not enumerable.
Object.defineProperty(record, Symbol.toStringTag, { enumerable: false });
assert_array_equals(Reflect.ownKeys(record),
["a", "c", Symbol.toStringTag]);
var proxy = new Proxy(record, loggingHandler);
var h = new Headers(proxy);
assert_equals(log.length, 9);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", record, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://webidl.spec.whatwg.org/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", record]);
// Then the [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[2], ["getOwnPropertyDescriptor", record, "a"]);
// Then the [[Get]] from step 5.2.
assert_array_equals(log[3], ["get", record, "a", proxy]);
// Then the ToString on the value.
assert_array_equals(log[4], ["toString", record.a]);
// Then the second [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[5], ["getOwnPropertyDescriptor", record, "c"]);
// Then the second [[Get]] from step 5.2.
assert_array_equals(log[6], ["get", record, "c", proxy]);
// Then the ToString on the value.
assert_array_equals(log[7], ["toString", record.c]);
// Then the third [[GetOwnProperty]] from step 5.1.
assert_array_equals(log[8], ["getOwnPropertyDescriptor", record,
Symbol.toStringTag]);
// No [[Get]] because not enumerable.
// Check the results.
assert_equals([...h].length, 2);
assert_array_equals([...h.keys()], ["a", "c"]);
assert_true(h.has("a"));
assert_equals(h.get("a"), "b");
assert_true(h.has("c"));
assert_equals(h.get("c"), "d");
}, "Operation with non-enumerable Symbol keys");
@@ -0,0 +1,20 @@
// META: title=Headers basic
// META: global=window,worker
"use strict";
var headers = new Headers();
var methods = ["append",
"delete",
"get",
"has",
"set",
//Headers is iterable
"entries",
"keys",
"values"
];
for (var idx in methods)
test(function() {
assert_true(methods[idx] in headers, "headers has " + methods[idx] + " method");
}, "Headers has " + methods[idx] + " method");
@@ -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");
}
@@ -0,0 +1,120 @@
var RESOURCES_DIR = "../resources/";
function dirname(path) {
return path.replace(/\/[^\/]*$/, '/')
}
function checkRequest(request, ExpectedValuesDict) {
for (var attribute in ExpectedValuesDict) {
switch(attribute) {
case "headers":
for (var key in ExpectedValuesDict["headers"].keys()) {
assert_equals(request["headers"].get(key), ExpectedValuesDict["headers"].get(key),
"Check headers attribute has " + key + ":" + ExpectedValuesDict["headers"].get(key));
}
break;
case "body":
//for checking body's content, a dedicated asyncronous/promise test should be used
assert_true(request["headers"].has("Content-Type") , "Check request has body using Content-Type header")
break;
case "method":
case "referrer":
case "referrerPolicy":
case "credentials":
case "cache":
case "redirect":
case "integrity":
case "url":
case "destination":
assert_equals(request[attribute], ExpectedValuesDict[attribute], "Check " + attribute + " attribute")
break;
default:
break;
}
}
}
function stringToArray(str) {
var array = new Uint8Array(str.length);
for (var i=0, strLen = str.length; i < strLen; i++)
array[i] = str.charCodeAt(i);
return array;
}
function encode_utf8(str)
{
if (self.TextEncoder)
return (new TextEncoder).encode(str);
return stringToArray(unescape(encodeURIComponent(str)));
}
function validateBufferFromString(buffer, expectedValue, message)
{
return assert_array_equals(new Uint8Array(buffer !== undefined ? buffer : []), stringToArray(expectedValue), message);
}
function validateStreamFromString(reader, expectedValue, retrievedArrayBuffer) {
// Passing Uint8Array for byte streams; non-byte streams will simply ignore it
return reader.read(new Uint8Array(64)).then(function(data) {
if (!data.done) {
assert_true(data.value instanceof Uint8Array, "Fetch ReadableStream chunks should be Uint8Array");
var newBuffer;
if (retrievedArrayBuffer) {
newBuffer = new Uint8Array(data.value.length + retrievedArrayBuffer.length);
newBuffer.set(retrievedArrayBuffer, 0);
newBuffer.set(data.value, retrievedArrayBuffer.length);
} else {
newBuffer = data.value;
}
return validateStreamFromString(reader, expectedValue, newBuffer);
}
validateBufferFromString(retrievedArrayBuffer, expectedValue, "Retrieve and verify stream");
});
}
function validateStreamFromPartialString(reader, expectedValue, retrievedArrayBuffer) {
// Passing Uint8Array for byte streams; non-byte streams will simply ignore it
return reader.read(new Uint8Array(64)).then(function(data) {
if (!data.done) {
assert_true(data.value instanceof Uint8Array, "Fetch ReadableStream chunks should be Uint8Array");
var newBuffer;
if (retrievedArrayBuffer) {
newBuffer = new Uint8Array(data.value.length + retrievedArrayBuffer.length);
newBuffer.set(retrievedArrayBuffer, 0);
newBuffer.set(data.value, retrievedArrayBuffer.length);
} else {
newBuffer = data.value;
}
return validateStreamFromPartialString(reader, expectedValue, newBuffer);
}
var string = new TextDecoder("utf-8").decode(retrievedArrayBuffer);
return assert_true(string.search(expectedValue) != -1, "Retrieve and verify stream");
});
}
// From streams tests
function delay(milliseconds)
{
return new Promise(function(resolve) {
step_timeout(resolve, milliseconds);
});
}
function requestForbiddenHeaders(desc, forbiddenHeaders) {
var url = RESOURCES_DIR + "inspect-headers.py";
var requestInit = {"headers": forbiddenHeaders}
var urlParameters = "?headers=" + Object.keys(forbiddenHeaders).join("|");
promise_test(function(test){
return fetch(url + urlParameters, requestInit).then(function(resp) {
assert_equals(resp.status, 200, "HTTP status is 200");
assert_equals(resp.type , "basic", "Response's type is basic");
for (var header in forbiddenHeaders)
assert_not_equals(resp.headers.get("x-request-" + header), forbiddenHeaders[header], header + " does not have the value we defined");
});
}, desc);
}
@@ -0,0 +1,88 @@
// META: global=window,worker
// META: title=Response consume empty bodies
function checkBodyText(test, response) {
return response.text().then(function(bodyAsText) {
assert_equals(bodyAsText, "", "Resolved value should be empty");
assert_false(response.bodyUsed);
});
}
async function checkBodyBlob(test, response) {
const bodyAsBlob = await response.blob();
const body = await bodyAsBlob.text();
assert_equals(body, "", "Resolved value should be empty");
assert_false(response.bodyUsed);
}
function checkBodyArrayBuffer(test, response) {
return response.arrayBuffer().then(function(bodyAsArrayBuffer) {
assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty");
assert_false(response.bodyUsed);
});
}
function checkBodyJSON(test, response) {
return response.json().then(
function(bodyAsJSON) {
assert_unreached("JSON parsing should fail");
},
function() {
assert_false(response.bodyUsed);
});
}
function checkBodyFormData(test, response) {
return response.formData().then(function(bodyAsFormData) {
assert_true(bodyAsFormData instanceof FormData, "Should receive a FormData");
assert_false(response.bodyUsed);
});
}
function checkBodyFormDataError(test, response) {
return promise_rejects_js(test, TypeError, response.formData()).then(function() {
assert_false(response.bodyUsed);
});
}
function checkResponseWithNoBody(bodyType, checkFunction, headers = []) {
promise_test(function(test) {
var response = new Response(undefined, { "headers": headers });
assert_false(response.bodyUsed);
return checkFunction(test, response);
}, "Consume response's body as " + bodyType);
}
checkResponseWithNoBody("text", checkBodyText);
checkResponseWithNoBody("blob", checkBodyBlob);
checkResponseWithNoBody("arrayBuffer", checkBodyArrayBuffer);
checkResponseWithNoBody("json (error case)", checkBodyJSON);
checkResponseWithNoBody("formData with correct multipart type (error case)", checkBodyFormDataError, [["Content-Type", 'multipart/form-data; boundary="boundary"']]);
checkResponseWithNoBody("formData with correct urlencoded type", checkBodyFormData, [["Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"]]);
checkResponseWithNoBody("formData without correct type (error case)", checkBodyFormDataError);
function checkResponseWithEmptyBody(bodyType, body, asText) {
promise_test(function(test) {
var response = new Response(body);
assert_false(response.bodyUsed, "bodyUsed is false at init");
if (asText) {
return response.text().then(function(bodyAsString) {
assert_equals(bodyAsString.length, 0, "Resolved value should be empty");
assert_true(response.bodyUsed, "bodyUsed is true after being consumed");
});
}
return response.arrayBuffer().then(function(bodyAsArrayBuffer) {
assert_equals(bodyAsArrayBuffer.byteLength, 0, "Resolved value should be empty");
assert_true(response.bodyUsed, "bodyUsed is true after being consumed");
});
}, "Consume empty " + bodyType + " response body as " + (asText ? "text" : "arrayBuffer"));
}
checkResponseWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), false);
checkResponseWithEmptyBody("text", "", false);
checkResponseWithEmptyBody("blob", new Blob([], { "type" : "text/plain" }), true);
checkResponseWithEmptyBody("text", "", true);
checkResponseWithEmptyBody("URLSearchParams", new URLSearchParams(""), true);
checkResponseWithEmptyBody("FormData", new FormData(), true);
checkResponseWithEmptyBody("ArrayBuffer", new ArrayBuffer(), true);
@@ -0,0 +1,80 @@
// META: global=window,worker
// META: title=Response consume
// META: script=../resources/utils.js
promise_test(function(test) {
var body = "";
var response = new Response("");
return validateStreamFromString(response.body.getReader(), "");
}, "Read empty text response's body as readableStream");
promise_test(function(test) {
var response = new Response(new Blob([], { "type" : "text/plain" }));
return validateStreamFromString(response.body.getReader(), "");
}, "Read empty blob response's body as readableStream");
var formData = new FormData();
formData.append("name", "value");
var textData = JSON.stringify("This is response's body");
var blob = new Blob([textData], { "type" : "text/plain" });
var urlSearchParamsData = "name=value";
var urlSearchParams = new URLSearchParams(urlSearchParamsData);
for (const mode of [undefined, "byob"]) {
promise_test(function(test) {
var response = new Response(blob);
return validateStreamFromString(response.body.getReader({ mode }), textData);
}, `Read blob response's body as readableStream with mode=${mode}`);
promise_test(function(test) {
var response = new Response(textData);
return validateStreamFromString(response.body.getReader({ mode }), textData);
}, `Read text response's body as readableStream with mode=${mode}`);
promise_test(function(test) {
var response = new Response(urlSearchParams);
return validateStreamFromString(response.body.getReader({ mode }), urlSearchParamsData);
}, `Read URLSearchParams response's body as readableStream with mode=${mode}`);
promise_test(function(test) {
var arrayBuffer = new ArrayBuffer(textData.length);
var int8Array = new Int8Array(arrayBuffer);
for (var cptr = 0; cptr < textData.length; cptr++)
int8Array[cptr] = textData.charCodeAt(cptr);
return validateStreamFromString(new Response(arrayBuffer).body.getReader({ mode }), textData);
}, `Read array buffer response's body as readableStream with mode=${mode}`);
promise_test(function(test) {
var response = new Response(formData);
return validateStreamFromPartialString(response.body.getReader({ mode }),
"Content-Disposition: form-data; name=\"name\"\r\n\r\nvalue");
}, `Read form data response's body as readableStream with mode=${mode}`);
}
test(function() {
assert_equals(Response.error().body, null);
}, "Getting an error Response stream");
test(function() {
assert_equals(Response.redirect("/").body, null);
}, "Getting a redirect Response stream");
promise_test(async function(test) {
var buffer = new ArrayBuffer(textData.length);
var body = new Response(textData).body;
const reader = body.getReader( {mode: 'byob'} );
let offset = 3;
while (offset < textData.length) {
const {done, value} = await reader.read(new Uint8Array(buffer, offset));
if (done) {
break;
}
buffer = value.buffer;
offset += value.byteLength;
}
validateBufferFromString(buffer, `\0\0\0\"This is response's bo`, 'Buffer should be validated');
}, `Reading with offset from Response stream`);
@@ -0,0 +1,27 @@
// META: global=window,worker
// META: title=Response error
var invalidStatus = [0, 100, 199, 600, 1000];
invalidStatus.forEach(function(status) {
test(function() {
assert_throws_js(RangeError, function() { new Response("", { "status" : status }); },
"Expect RangeError exception when status is " + status);
},"Throws RangeError when responseInit's status is " + status);
});
var invalidStatusText = ["\n", "Ā"];
invalidStatusText.forEach(function(statusText) {
test(function() {
assert_throws_js(TypeError, function() { new Response("", { "statusText" : statusText }); },
"Expect TypeError exception " + statusText);
},"Throws TypeError when responseInit's statusText is " + statusText);
});
var nullBodyStatus = [204, 205, 304];
nullBodyStatus.forEach(function(status) {
test(function() {
assert_throws_js(TypeError,
function() { new Response("body", {"status" : status }); },
"Expect TypeError exception ");
},"Throws TypeError when building a response with body and a body status of " + status);
});
@@ -0,0 +1,23 @@
// META: global=window,worker
"use strict";
test(() => {
const stream = new ReadableStream();
stream.getReader();
assert_throws_js(TypeError, () => new Response(stream));
}, "Constructing a Response with a stream on which getReader() is called");
test(() => {
const stream = new ReadableStream();
stream.getReader().read();
assert_throws_js(TypeError, () => new Response(stream));
}, "Constructing a Response with a stream on which read() is called");
promise_test(async () => {
const stream = new ReadableStream({ pull: c => c.enqueue(new Uint8Array()) }),
reader = stream.getReader();
await reader.read();
reader.releaseLock();
assert_throws_js(TypeError, () => new Response(stream));
}, "Constructing a Response with a stream on which read() and releaseLock() are called");
@@ -0,0 +1,8 @@
// META: global=window,worker
// META: title=Response: error static method
promise_test (async () => {
const response = await fetch("../resources/data.json");
assert_throws_js(TypeError, () => { response.headers.append("name", "value"); });
assert_not_equals(response.headers.get("name"), "value", "response headers should be immutable");
}, "Ensure response headers are immutable");
@@ -0,0 +1,64 @@
// META: global=window,worker
// META: title=Response init: simple cases
var defaultValues = { "type" : "default",
"url" : "",
"ok" : true,
"status" : 200,
"statusText" : "",
"body" : null
};
var statusCodes = { "givenValues" : [200, 300, 400, 500, 599],
"expectedValues" : [200, 300, 400, 500, 599]
};
var statusTexts = { "givenValues" : ["", "OK", "with space", String.fromCharCode(0x80)],
"expectedValues" : ["", "OK", "with space", String.fromCharCode(0x80)]
};
var initValuesDict = { "status" : statusCodes,
"statusText" : statusTexts
};
function isOkStatus(status) {
return 200 <= status && 299 >= status;
}
var response = new Response();
for (var attributeName in defaultValues) {
test(function() {
var expectedValue = defaultValues[attributeName];
assert_equals(response[attributeName], expectedValue,
"Expect default response." + attributeName + " is " + expectedValue);
}, "Check default value for " + attributeName + " attribute");
}
for (var attributeName in initValuesDict) {
test(function() {
var valuesToTest = initValuesDict[attributeName];
for (var valueIdx in valuesToTest["givenValues"]) {
var givenValue = valuesToTest["givenValues"][valueIdx];
var expectedValue = valuesToTest["expectedValues"][valueIdx];
var responseInit = {};
responseInit[attributeName] = givenValue;
var response = new Response("", responseInit);
assert_equals(response[attributeName], expectedValue,
"Expect response." + attributeName + " is " + expectedValue +
" when initialized with " + givenValue);
assert_equals(response.ok, isOkStatus(response.status),
"Expect response.ok is " + isOkStatus(response.status));
}
}, "Check " + attributeName + " init values and associated getter");
}
test(function() {
const response1 = new Response("");
assert_equals(response1.headers, response1.headers);
const response2 = new Response("", {"headers": {"X-Foo": "bar"}});
assert_equals(response2.headers, response2.headers);
const headers = response2.headers;
response2.headers.set("X-Foo", "quux");
assert_equals(headers, response2.headers);
headers.set("X-Other-Header", "baz");
assert_equals(headers, response2.headers);
}, "Test that Response.headers has the [SameObject] extended attribute");
@@ -0,0 +1,61 @@
// META: global=window,worker
// META: title=Response init: body and headers
// META: script=../resources/utils.js
test(function() {
var headerDict = {"name1": "value1",
"name2": "value2",
"name3": "value3"
};
var headers = new Headers(headerDict);
var response = new Response("", { "headers" : headers })
for (var name in headerDict) {
assert_equals(response.headers.get(name), headerDict[name],
"response's headers has " + name + " : " + headerDict[name]);
}
}, "Initialize Response with headers values");
function checkResponseInit(body, bodyType, expectedTextBody) {
promise_test(function(test) {
var response = new Response(body);
var resHeaders = response.headers;
var mime = resHeaders.get("Content-Type");
assert_true(mime && mime.search(bodyType) > -1, "Content-Type header should be \"" + bodyType + "\" ");
return response.text().then(function(bodyAsText) {
//not equals: cannot guess formData exact value
assert_true(bodyAsText.search(expectedTextBody) > -1, "Retrieve and verify response body");
});
}, "Initialize Response's body with " + bodyType);
}
var blob = new Blob(["This is a blob"], {type: "application/octet-binary"});
var formaData = new FormData();
formaData.append("name", "value");
var urlSearchParams = "URLSearchParams are not supported";
//avoid test timeout if not implemented
if (self.URLSearchParams)
urlSearchParams = new URLSearchParams("name=value");
var usvString = "This is a USVString"
checkResponseInit(blob, "application/octet-binary", "This is a blob");
checkResponseInit(formaData, "multipart/form-data", "name=\"name\"\r\n\r\nvalue");
checkResponseInit(urlSearchParams, "application/x-www-form-urlencoded;charset=UTF-8", "name=value");
checkResponseInit(usvString, "text/plain;charset=UTF-8", "This is a USVString");
promise_test(function(test) {
var body = "This is response body";
var response = new Response(body);
return validateStreamFromString(response.body.getReader(), body);
}, "Read Response's body as readableStream");
promise_test(function(test) {
var response = new Response("This is my fork", {"headers" : [["Content-Type", ""]]});
return response.blob().then(function(blob) {
assert_equals(blob.type, "", "Blob type should be the empty string");
});
}, "Testing empty Response Content-Type header");
test(function() {
var response = new Response(null, {status: 204});
assert_equals(response.body, null);
}, "Testing null Response body");
@@ -0,0 +1,125 @@
test(() => {
const response = new Response();
assert_equals(response.headers.get("Content-Type"), null);
}, "Default Content-Type for Response with empty body");
test(() => {
const blob = new Blob([]);
const response = new Response(blob);
assert_equals(response.headers.get("Content-Type"), null);
}, "Default Content-Type for Response with Blob body (no type set)");
test(() => {
const blob = new Blob([], { type: "" });
const response = new Response(blob);
assert_equals(response.headers.get("Content-Type"), null);
}, "Default Content-Type for Response with Blob body (empty type)");
test(() => {
const blob = new Blob([], { type: "a/b; c=d" });
const response = new Response(blob);
assert_equals(response.headers.get("Content-Type"), "a/b; c=d");
}, "Default Content-Type for Response with Blob body (set type)");
test(() => {
const buffer = new Uint8Array();
const response = new Response(buffer);
assert_equals(response.headers.get("Content-Type"), null);
}, "Default Content-Type for Response with buffer source body");
promise_test(async () => {
const formData = new FormData();
formData.append("a", "b");
const response = new Response(formData);
const boundary = (await response.text()).split("\r\n")[0].slice(2);
assert_equals(
response.headers.get("Content-Type"),
`multipart/form-data; boundary=${boundary}`,
);
}, "Default Content-Type for Response with FormData body");
test(() => {
const usp = new URLSearchParams();
const response = new Response(usp);
assert_equals(
response.headers.get("Content-Type"),
"application/x-www-form-urlencoded;charset=UTF-8",
);
}, "Default Content-Type for Response with URLSearchParams body");
test(() => {
const response = new Response("");
assert_equals(
response.headers.get("Content-Type"),
"text/plain;charset=UTF-8",
);
}, "Default Content-Type for Response with string body");
test(() => {
const stream = new ReadableStream();
const response = new Response(stream);
assert_equals(response.headers.get("Content-Type"), null);
}, "Default Content-Type for Response with ReadableStream body");
// -----------------------------------------------------------------------------
const OVERRIDE_MIME = "test/only; mime=type";
function responseWithOverrideMime(body) {
return new Response(
body,
{ headers: { "Content-Type": OVERRIDE_MIME } },
);
}
test(() => {
const response = responseWithOverrideMime(undefined);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with empty body");
test(() => {
const blob = new Blob([]);
const response = responseWithOverrideMime(blob);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with Blob body (no type set)");
test(() => {
const blob = new Blob([], { type: "" });
const response = responseWithOverrideMime(blob);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with Blob body (empty type)");
test(() => {
const blob = new Blob([], { type: "a/b; c=d" });
const response = responseWithOverrideMime(blob);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with Blob body (set type)");
test(() => {
const buffer = new Uint8Array();
const response = responseWithOverrideMime(buffer);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with buffer source body");
test(() => {
const formData = new FormData();
const response = responseWithOverrideMime(formData);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with FormData body");
test(() => {
const usp = new URLSearchParams();
const response = responseWithOverrideMime(usp);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with URLSearchParams body");
test(() => {
const response = responseWithOverrideMime("");
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with string body");
test(() => {
const stream = new ReadableStream();
const response = responseWithOverrideMime(stream);
assert_equals(response.headers.get("Content-Type"), OVERRIDE_MIME);
}, "Can override Content-Type for Response with ReadableStream body");
@@ -0,0 +1,22 @@
// META: global=window,worker
// META: title=Response: error static method
test(function() {
var responseError = Response.error();
assert_equals(responseError.type, "error", "Network error response's type is error");
assert_equals(responseError.status, 0, "Network error response's status is 0");
assert_equals(responseError.statusText, "", "Network error response's statusText is empty");
assert_equals(responseError.body, null, "Network error response's body is null");
assert_true(responseError.headers.entries().next().done, "Headers should be empty");
}, "Check response returned by static method error()");
test(function() {
const headers = Response.error().headers;
// Avoid false positives if expected API is not available
assert_true(!!headers);
assert_equals(typeof headers.append, 'function');
assert_throws_js(TypeError, function () { headers.append('name', 'value'); });
}, "the 'guard' of the Headers instance should be immutable");
@@ -0,0 +1,96 @@
// META: global=window,worker
// META: title=Response: json static method
const APPLICATION_JSON = "application/json";
const FOO_BAR = "foo/bar";
const INIT_TESTS = [
[undefined, 200, "", APPLICATION_JSON, {}],
[{ status: 400 }, 400, "", APPLICATION_JSON, {}],
[{ statusText: "foo" }, 200, "foo", APPLICATION_JSON, {}],
[{ headers: {} }, 200, "", APPLICATION_JSON, {}],
[{ headers: { "content-type": FOO_BAR } }, 200, "", FOO_BAR, {}],
[{ headers: { "x-foo": "bar" } }, 200, "", APPLICATION_JSON, { "x-foo": "bar" }],
];
for (const [init, expectedStatus, expectedStatusText, expectedContentType, expectedHeaders] of INIT_TESTS) {
promise_test(async function () {
const response = Response.json("hello world", init);
assert_equals(response.type, "default", "Response's type is default");
assert_equals(response.status, expectedStatus, "Response's status is " + expectedStatus);
assert_equals(response.statusText, expectedStatusText, "Response's statusText is " + JSON.stringify(expectedStatusText));
assert_equals(response.headers.get("content-type"), expectedContentType, "Response's content-type is " + expectedContentType);
for (const key in expectedHeaders) {
assert_equals(response.headers.get(key), expectedHeaders[key], "Response's header " + key + " is " + JSON.stringify(expectedHeaders[key]));
}
const data = await response.json();
assert_equals(data, "hello world", "Response's body is 'hello world'");
}, `Check response returned by static json() with init ${JSON.stringify(init)}`);
}
const nullBodyStatus = [204, 205, 304];
for (const status of nullBodyStatus) {
test(function () {
assert_throws_js(
TypeError,
function () {
Response.json("hello world", { status: status });
},
);
}, `Throws TypeError when calling static json() with a status of ${status}`);
}
promise_test(async function () {
const response = Response.json({ foo: "bar" });
const data = await response.json();
assert_equals(typeof data, "object", "Response's json body is an object");
assert_equals(data.foo, "bar", "Response's json body is { foo: 'bar' }");
}, "Check static json() encodes JSON objects correctly");
test(function () {
assert_throws_js(
TypeError,
function () {
Response.json(Symbol("foo"));
},
);
}, "Check static json() throws when data is not encodable");
test(function () {
const a = { b: 1 };
a.a = a;
assert_throws_js(
TypeError,
function () {
Response.json(a);
},
);
}, "Check static json() throws when data is circular");
promise_test(async function () {
class CustomError extends Error {
name = "CustomError";
}
assert_throws_js(
CustomError,
function () {
Response.json({ get foo() { throw new CustomError("bar") }});
}
)
}, "Check static json() propagates JSON serializer errors");
const encodingChecks = [
["𝌆", [34, 240, 157, 140, 134, 34]],
["\uDF06\uD834", [34, 92, 117, 100, 102, 48, 54, 92, 117, 100, 56, 51, 52, 34]],
["\uDEAD", [34, 92, 117, 100, 101, 97, 100, 34]],
];
for (const [input, expected] of encodingChecks) {
promise_test(async function () {
const response = Response.json(input);
const buffer = await response.arrayBuffer();
const data = new Uint8Array(buffer);
assert_array_equals(data, expected);
}, `Check response returned by static json() with input ${input}`);
}
@@ -0,0 +1,40 @@
// META: global=window,worker
// META: title=Response: redirect static method
var url = "http://test.url:1234/";
test(function() {
const redirectResponse = Response.redirect(url);
assert_equals(redirectResponse.type, "default");
assert_false(redirectResponse.redirected);
assert_false(redirectResponse.ok);
assert_equals(redirectResponse.status, 302, "Default redirect status is 302");
assert_equals(redirectResponse.headers.get("Location"), url,
"redirected response has Location header with the correct url");
assert_equals(redirectResponse.statusText, "");
}, "Check default redirect response");
[301, 302, 303, 307, 308].forEach(function(status) {
test(function() {
const redirectResponse = Response.redirect(url, status);
assert_equals(redirectResponse.type, "default");
assert_false(redirectResponse.redirected);
assert_false(redirectResponse.ok);
assert_equals(redirectResponse.status, status, "Redirect status is " + status);
assert_equals(redirectResponse.headers.get("Location"), url);
assert_equals(redirectResponse.statusText, "");
}, "Check response returned by static method redirect(), status = " + status);
});
test(function() {
var invalidUrl = "http://:This is not an url";
assert_throws_js(TypeError, function() { Response.redirect(invalidUrl); },
"Expect TypeError exception");
}, "Check error returned when giving invalid url to redirect()");
var invalidRedirectStatus = [200, 309, 400, 500];
invalidRedirectStatus.forEach(function(invalidStatus) {
test(function() {
assert_throws_js(RangeError, function() { Response.redirect(url, invalidStatus); },
"Expect RangeError exception");
}, "Check error returned when giving invalid status to redirect(), status = " + invalidStatus);
});
@@ -0,0 +1,25 @@
// META: global=window,worker
// META: title=Response causes TypeError from bad chunk type
function runChunkTest(responseReaderMethod, testDescription) {
promise_test(test => {
let stream = new ReadableStream({
start(controller) {
controller.enqueue("not Uint8Array");
controller.close();
}
});
return promise_rejects_js(test, TypeError,
new Response(stream)[responseReaderMethod](),
'TypeError should propagate'
)
}, testDescription)
}
runChunkTest('arrayBuffer', 'ReadableStream with non-Uint8Array chunk passed to Response.arrayBuffer() causes TypeError');
runChunkTest('blob', 'ReadableStream with non-Uint8Array chunk passed to Response.blob() causes TypeError');
runChunkTest('bytes', 'ReadableStream with non-Uint8Array chunk passed to Response.bytes() causes TypeError');
runChunkTest('formData', 'ReadableStream with non-Uint8Array chunk passed to Response.formData() causes TypeError');
runChunkTest('json', 'ReadableStream with non-Uint8Array chunk passed to Response.json() causes TypeError');
runChunkTest('text', 'ReadableStream with non-Uint8Array chunk passed to Response.text() causes TypeError');