mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
chore: align Headers
to spec (#10199)
This commit aligns `Headers` to spec. It also removes the now unused 03_dom_iterable.js file. We now pass all relevant `Headers` WPT. We do not implement any sort of header filtering, as we are a server side runtime. This is likely not the most efficient implementation of `Headers` yet. It is however spec compliant. Once all the APIs in the `HTTP` hot loop are correct we can start optimizing them. It is likely that this commit reduces bench throughput temporarily.
This commit is contained in:
parent
0c5ecec8f6
commit
0552eaf569
14 changed files with 552 additions and 514 deletions
|
@ -1,88 +0,0 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
||||||
function setup() {
|
|
||||||
const dataSymbol = Symbol("data symbol");
|
|
||||||
class Base {
|
|
||||||
[dataSymbol] = new Map<string, number>();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
data: Array<[string, number]> | IterableIterator<[string, number]>,
|
|
||||||
) {
|
|
||||||
for (const [key, value] of data) {
|
|
||||||
this[dataSymbol].set(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
Base,
|
|
||||||
// This is using an internal API we don't want published as types, so having
|
|
||||||
// to cast to any to "trick" TypeScript
|
|
||||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
|
||||||
DomIterable: Deno[Deno.internal].DomIterableMixin(Base, dataSymbol),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unitTest(function testDomIterable(): void {
|
|
||||||
const { DomIterable, Base } = setup();
|
|
||||||
|
|
||||||
const fixture: Array<[string, number]> = [
|
|
||||||
["foo", 1],
|
|
||||||
["bar", 2],
|
|
||||||
];
|
|
||||||
|
|
||||||
const domIterable = new DomIterable(fixture);
|
|
||||||
|
|
||||||
assertEquals(Array.from(domIterable.entries()), fixture);
|
|
||||||
assertEquals(Array.from(domIterable.values()), [1, 2]);
|
|
||||||
assertEquals(Array.from(domIterable.keys()), ["foo", "bar"]);
|
|
||||||
|
|
||||||
let result: Array<[string, number]> = [];
|
|
||||||
for (const [key, value] of domIterable) {
|
|
||||||
assert(key != null);
|
|
||||||
assert(value != null);
|
|
||||||
result.push([key, value]);
|
|
||||||
}
|
|
||||||
assertEquals(fixture, result);
|
|
||||||
|
|
||||||
result = [];
|
|
||||||
const scope = {};
|
|
||||||
function callback(
|
|
||||||
this: typeof scope,
|
|
||||||
value: number,
|
|
||||||
key: string,
|
|
||||||
parent: typeof domIterable,
|
|
||||||
): void {
|
|
||||||
assertEquals(parent, domIterable);
|
|
||||||
assert(key != null);
|
|
||||||
assert(value != null);
|
|
||||||
assert(this === scope);
|
|
||||||
result.push([key, value]);
|
|
||||||
}
|
|
||||||
domIterable.forEach(callback, scope);
|
|
||||||
assertEquals(fixture, result);
|
|
||||||
|
|
||||||
assertEquals(DomIterable.name, Base.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
unitTest(function testDomIterableScope(): void {
|
|
||||||
const { DomIterable } = setup();
|
|
||||||
|
|
||||||
const domIterable = new DomIterable([["foo", 1]]);
|
|
||||||
|
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
function checkScope(thisArg: any, expected: any): void {
|
|
||||||
function callback(this: typeof thisArg): void {
|
|
||||||
assertEquals(this, expected);
|
|
||||||
}
|
|
||||||
domIterable.forEach(callback, thisArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkScope(0, Object(0));
|
|
||||||
checkScope("", Object(""));
|
|
||||||
checkScope(null, window);
|
|
||||||
checkScope(undefined, window);
|
|
||||||
});
|
|
|
@ -661,8 +661,8 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"hello: World\r\n",
|
|
||||||
"foo: Bar\r\n",
|
"foo: Bar\r\n",
|
||||||
|
"hello: World\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
@ -695,9 +695,9 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"hello: World\r\n",
|
|
||||||
"foo: Bar\r\n",
|
|
||||||
"content-type: text/plain;charset=UTF-8\r\n",
|
"content-type: text/plain;charset=UTF-8\r\n",
|
||||||
|
"foo: Bar\r\n",
|
||||||
|
"hello: World\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
@ -733,8 +733,8 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"hello: World\r\n",
|
|
||||||
"foo: Bar\r\n",
|
"foo: Bar\r\n",
|
||||||
|
"hello: World\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
@ -1115,8 +1115,8 @@ unitTest(
|
||||||
const actual = new TextDecoder().decode(buf.bytes());
|
const actual = new TextDecoder().decode(buf.bytes());
|
||||||
const expected = [
|
const expected = [
|
||||||
"POST /blah HTTP/1.1\r\n",
|
"POST /blah HTTP/1.1\r\n",
|
||||||
"hello: World\r\n",
|
|
||||||
"foo: Bar\r\n",
|
"foo: Bar\r\n",
|
||||||
|
"hello: World\r\n",
|
||||||
"accept: */*\r\n",
|
"accept: */*\r\n",
|
||||||
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
`user-agent: Deno/${Deno.version.deno}\r\n`,
|
||||||
"accept-encoding: gzip, br\r\n",
|
"accept-encoding: gzip, br\r\n",
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
import {
|
import { assert, assertEquals, unitTest } from "./test_util.ts";
|
||||||
assert,
|
|
||||||
assertEquals,
|
|
||||||
assertStringIncludes,
|
|
||||||
unitTest,
|
|
||||||
} from "./test_util.ts";
|
|
||||||
const {
|
const {
|
||||||
inspectArgs,
|
inspectArgs,
|
||||||
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
||||||
|
@ -25,10 +20,7 @@ unitTest(function newHeaderTest(): void {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
new Headers(null as any);
|
new Headers(null as any);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
assertEquals(
|
assert(e instanceof TypeError);
|
||||||
e.message,
|
|
||||||
"Failed to construct 'Headers'; The provided value was not valid",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -271,13 +263,11 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
||||||
methodRequireOneParam.forEach((method): void => {
|
methodRequireOneParam.forEach((method): void => {
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
let hasThrown = 0;
|
let hasThrown = 0;
|
||||||
let errMsg = "";
|
|
||||||
try {
|
try {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
(headers as any)[method]();
|
(headers as any)[method]();
|
||||||
hasThrown = 1;
|
hasThrown = 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errMsg = err.message;
|
|
||||||
if (err instanceof TypeError) {
|
if (err instanceof TypeError) {
|
||||||
hasThrown = 2;
|
hasThrown = 2;
|
||||||
} else {
|
} else {
|
||||||
|
@ -285,23 +275,17 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(hasThrown, 2);
|
assertEquals(hasThrown, 2);
|
||||||
assertStringIncludes(
|
|
||||||
errMsg,
|
|
||||||
`${method} requires at least 1 argument, but only 0 present`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
methodRequireTwoParams.forEach((method): void => {
|
methodRequireTwoParams.forEach((method): void => {
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
let hasThrown = 0;
|
let hasThrown = 0;
|
||||||
let errMsg = "";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
(headers as any)[method]();
|
(headers as any)[method]();
|
||||||
hasThrown = 1;
|
hasThrown = 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errMsg = err.message;
|
|
||||||
if (err instanceof TypeError) {
|
if (err instanceof TypeError) {
|
||||||
hasThrown = 2;
|
hasThrown = 2;
|
||||||
} else {
|
} else {
|
||||||
|
@ -309,19 +293,13 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(hasThrown, 2);
|
assertEquals(hasThrown, 2);
|
||||||
assertStringIncludes(
|
|
||||||
errMsg,
|
|
||||||
`${method} requires at least 2 arguments, but only 0 present`,
|
|
||||||
);
|
|
||||||
|
|
||||||
hasThrown = 0;
|
hasThrown = 0;
|
||||||
errMsg = "";
|
|
||||||
try {
|
try {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
(headers as any)[method]("foo");
|
(headers as any)[method]("foo");
|
||||||
hasThrown = 1;
|
hasThrown = 1;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errMsg = err.message;
|
|
||||||
if (err instanceof TypeError) {
|
if (err instanceof TypeError) {
|
||||||
hasThrown = 2;
|
hasThrown = 2;
|
||||||
} else {
|
} else {
|
||||||
|
@ -329,10 +307,6 @@ unitTest(function headerParamsArgumentsCheck(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(hasThrown, 2);
|
assertEquals(hasThrown, 2);
|
||||||
assertStringIncludes(
|
|
||||||
errMsg,
|
|
||||||
`${method} requires at least 2 arguments, but only 1 present`,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -361,8 +335,8 @@ unitTest(function headersAppendMultiple(): void {
|
||||||
const actual = [...headers];
|
const actual = [...headers];
|
||||||
assertEquals(actual, [
|
assertEquals(actual, [
|
||||||
["set-cookie", "foo=bar"],
|
["set-cookie", "foo=bar"],
|
||||||
["x-deno", "foo, bar"],
|
|
||||||
["set-cookie", "bar=baz"],
|
["set-cookie", "bar=baz"],
|
||||||
|
["x-deno", "foo, bar"],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -372,22 +346,12 @@ unitTest(function headersAppendDuplicateSetCookieKey(): void {
|
||||||
headers.append("Set-cookie", "baz=bar");
|
headers.append("Set-cookie", "baz=bar");
|
||||||
const actual = [...headers];
|
const actual = [...headers];
|
||||||
assertEquals(actual, [
|
assertEquals(actual, [
|
||||||
|
["set-cookie", "foo=bar"],
|
||||||
["set-cookie", "foo=baz"],
|
["set-cookie", "foo=baz"],
|
||||||
["set-cookie", "baz=bar"],
|
["set-cookie", "baz=bar"],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
unitTest(function headersSetDuplicateCookieKey(): void {
|
|
||||||
const headers = new Headers([["Set-Cookie", "foo=bar"]]);
|
|
||||||
headers.set("set-Cookie", "foo=baz");
|
|
||||||
headers.set("set-cookie", "bar=qat");
|
|
||||||
const actual = [...headers];
|
|
||||||
assertEquals(actual, [
|
|
||||||
["set-cookie", "foo=baz"],
|
|
||||||
["set-cookie", "bar=qat"],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
unitTest(function headersGetSetCookie(): void {
|
unitTest(function headersGetSetCookie(): void {
|
||||||
const headers = new Headers([
|
const headers = new Headers([
|
||||||
["Set-Cookie", "foo=bar"],
|
["Set-Cookie", "foo=bar"],
|
||||||
|
@ -411,7 +375,7 @@ unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
|
||||||
const singleHeader = new Headers([["Content-Type", "application/json"]]);
|
const singleHeader = new Headers([["Content-Type", "application/json"]]);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stringify(singleHeader),
|
stringify(singleHeader),
|
||||||
"Headers { content-type: application/json }",
|
`Headers { "content-type": "application/json" }`,
|
||||||
);
|
);
|
||||||
const multiParamHeader = new Headers([
|
const multiParamHeader = new Headers([
|
||||||
["Content-Type", "application/json"],
|
["Content-Type", "application/json"],
|
||||||
|
@ -419,6 +383,6 @@ unitTest(function customInspectReturnsCorrectHeadersFormat(): void {
|
||||||
]);
|
]);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
stringify(multiParamHeader),
|
stringify(multiParamHeader),
|
||||||
"Headers { content-type: application/json, content-length: 1337 }",
|
`Headers { "content-length": "1337", "content-type": "application/json" }`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,6 @@ import "./io_test.ts";
|
||||||
import "./link_test.ts";
|
import "./link_test.ts";
|
||||||
import "./make_temp_test.ts";
|
import "./make_temp_test.ts";
|
||||||
import "./metrics_test.ts";
|
import "./metrics_test.ts";
|
||||||
import "./dom_iterable_test.ts";
|
|
||||||
import "./mkdir_test.ts";
|
import "./mkdir_test.ts";
|
||||||
import "./net_test.ts";
|
import "./net_test.ts";
|
||||||
import "./os_test.ts";
|
import "./os_test.ts";
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
((window) => {
|
|
||||||
const { requiredArguments } = window.__bootstrap.fetchUtil;
|
|
||||||
|
|
||||||
function DomIterableMixin(
|
|
||||||
Base,
|
|
||||||
dataSymbol,
|
|
||||||
) {
|
|
||||||
// we have to cast `this` as `any` because there is no way to describe the
|
|
||||||
// Base class in a way where the Symbol `dataSymbol` is defined. So the
|
|
||||||
// runtime code works, but we do lose a little bit of type safety.
|
|
||||||
|
|
||||||
// Additionally, we have to not use .keys() nor .values() since the internal
|
|
||||||
// slot differs in type - some have a Map, which yields [K, V] in
|
|
||||||
// Symbol.iterator, and some have an Array, which yields V, in this case
|
|
||||||
// [K, V] too as they are arrays of tuples.
|
|
||||||
|
|
||||||
const DomIterable = class extends Base {
|
|
||||||
*entries() {
|
|
||||||
for (const entry of this[dataSymbol]) {
|
|
||||||
yield entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*keys() {
|
|
||||||
for (const [key] of this[dataSymbol]) {
|
|
||||||
yield key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*values() {
|
|
||||||
for (const [, value] of this[dataSymbol]) {
|
|
||||||
yield value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(
|
|
||||||
callbackfn,
|
|
||||||
thisArg,
|
|
||||||
) {
|
|
||||||
requiredArguments(
|
|
||||||
`${this.constructor.name}.forEach`,
|
|
||||||
arguments.length,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
callbackfn = callbackfn.bind(
|
|
||||||
thisArg == null ? globalThis : Object(thisArg),
|
|
||||||
);
|
|
||||||
for (const [key, value] of this[dataSymbol]) {
|
|
||||||
callbackfn(value, key, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
for (const entry of this[dataSymbol]) {
|
|
||||||
yield entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// we want the Base class name to be the name of the class.
|
|
||||||
Object.defineProperty(DomIterable, "name", {
|
|
||||||
value: Base.name,
|
|
||||||
configurable: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
return DomIterable;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.__bootstrap.internals = {
|
|
||||||
...window.__bootstrap.internals ?? {},
|
|
||||||
DomIterableMixin,
|
|
||||||
};
|
|
||||||
|
|
||||||
window.__bootstrap.domIterable = {
|
|
||||||
DomIterableMixin,
|
|
||||||
};
|
|
||||||
})(this);
|
|
|
@ -1,247 +1,327 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// @ts-check
|
||||||
|
/// <reference path="../webidl/internal.d.ts" />
|
||||||
|
/// <reference path="../web/internal.d.ts" />
|
||||||
|
/// <reference path="../file/internal.d.ts" />
|
||||||
|
/// <reference path="../file/lib.deno_file.d.ts" />
|
||||||
|
/// <reference path="./internal.d.ts" />
|
||||||
|
/// <reference path="./11_streams_types.d.ts" />
|
||||||
|
/// <reference path="./lib.deno_fetch.d.ts" />
|
||||||
|
/// <reference lib="esnext" />
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
const { DomIterableMixin } = window.__bootstrap.domIterable;
|
const webidl = window.__bootstrap.webidl;
|
||||||
const { requiredArguments } = window.__bootstrap.fetchUtil;
|
const {
|
||||||
|
HTTP_WHITESPACE_PREFIX_RE,
|
||||||
|
HTTP_WHITESPACE_SUFFIX_RE,
|
||||||
|
HTTP_TOKEN_CODE_POINT_RE,
|
||||||
|
byteLowerCase,
|
||||||
|
} = window.__bootstrap.infra;
|
||||||
|
|
||||||
// From node-fetch
|
const _headerList = Symbol("header list");
|
||||||
// Copyright (c) 2016 David Frank. MIT License.
|
const _iterableHeaders = Symbol("iterable headers");
|
||||||
const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
|
const _guard = Symbol("guard");
|
||||||
const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
|
|
||||||
|
|
||||||
function isHeaders(value) {
|
/**
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
* @typedef Header
|
||||||
return value instanceof Headers;
|
* @type {[string, string]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef HeaderList
|
||||||
|
* @type {Header[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {string} potentialValue
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function normalizeHeaderValue(potentialValue) {
|
||||||
|
potentialValue = potentialValue.replaceAll(HTTP_WHITESPACE_PREFIX_RE, "");
|
||||||
|
potentialValue = potentialValue.replaceAll(HTTP_WHITESPACE_SUFFIX_RE, "");
|
||||||
|
return potentialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headersData = Symbol("headers data");
|
/**
|
||||||
|
* @param {Headers} headers
|
||||||
// TODO(bartlomieju): headerGuard? Investigate if it is needed
|
* @param {HeadersInit} object
|
||||||
// node-fetch did not implement this but it is in the spec
|
*/
|
||||||
function normalizeParams(name, value) {
|
function fillHeaders(headers, object) {
|
||||||
name = String(name).toLowerCase();
|
if (Array.isArray(object)) {
|
||||||
value = String(value).trim();
|
for (const header of object) {
|
||||||
return [name, value];
|
if (header.length !== 2) {
|
||||||
}
|
throw new TypeError(
|
||||||
|
`Invalid header. Length must be 2, but is ${header.length}`,
|
||||||
// The following name/value validations are copied from
|
);
|
||||||
// https://github.com/bitinn/node-fetch/blob/master/src/headers.js
|
|
||||||
// Copyright (c) 2016 David Frank. MIT License.
|
|
||||||
function validateName(name) {
|
|
||||||
if (invalidTokenRegex.test(name) || name === "") {
|
|
||||||
throw new TypeError(`${name} is not a legal HTTP header name`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateValue(value) {
|
|
||||||
if (invalidHeaderCharRegex.test(value)) {
|
|
||||||
throw new TypeError(`${value} is not a legal HTTP header value`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Appends a key and value to the header list.
|
|
||||||
*
|
|
||||||
* The spec indicates that when a key already exists, the append adds the new
|
|
||||||
* value onto the end of the existing value. The behaviour of this though
|
|
||||||
* varies when the key is `set-cookie`. In this case, if the key of the cookie
|
|
||||||
* already exists, the value is replaced, but if the key of the cookie does not
|
|
||||||
* exist, and additional `set-cookie` header is added.
|
|
||||||
*
|
|
||||||
* The browser specification of `Headers` is written for clients, and not
|
|
||||||
* servers, and Deno is a server, meaning that it needs to follow the patterns
|
|
||||||
* expected for servers, of which a `set-cookie` header is expected for each
|
|
||||||
* unique cookie key, but duplicate cookie keys should not exist. */
|
|
||||||
function dataAppend(
|
|
||||||
data,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
) {
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
const [dataKey] = data[i];
|
|
||||||
if (key === "set-cookie" && dataKey === "set-cookie") {
|
|
||||||
const [, dataValue] = data[i];
|
|
||||||
const [dataCookieKey] = dataValue.split("=");
|
|
||||||
const [cookieKey] = value.split("=");
|
|
||||||
if (dataCookieKey === cookieKey) {
|
|
||||||
data[i][1] = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dataKey === key) {
|
|
||||||
data[i][1] += `, ${value}`;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
appendHeader(headers, header[0], header[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key of Object.keys(object)) {
|
||||||
|
appendHeader(headers, key, object[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.push([key, value]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets a value of a key in the headers list.
|
/**
|
||||||
*
|
* https://fetch.spec.whatwg.org/#concept-headers-append
|
||||||
* This varies slightly from spec behaviour in that when the key is `set-cookie`
|
* @param {Headers} headers
|
||||||
* the value returned will look like a concatenated value, when in fact, if the
|
* @param {string} name
|
||||||
* headers were iterated over, each individual `set-cookie` value is a unique
|
* @param {string} value
|
||||||
* entry in the headers list. */
|
*/
|
||||||
function dataGet(
|
function appendHeader(headers, name, value) {
|
||||||
data,
|
// 1.
|
||||||
key,
|
value = normalizeHeaderValue(value);
|
||||||
) {
|
|
||||||
const setCookieValues = [];
|
// 2.
|
||||||
for (const [dataKey, value] of data) {
|
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||||
if (dataKey === key) {
|
throw new TypeError("Header name is not valid.");
|
||||||
if (key === "set-cookie") {
|
}
|
||||||
setCookieValues.push(value);
|
if (
|
||||||
} else {
|
value.includes("\x00") || value.includes("\x0A") || value.includes("\x0D")
|
||||||
return value;
|
) {
|
||||||
}
|
throw new TypeError("Header value is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.
|
||||||
|
if (headers[_guard] == "immutable") {
|
||||||
|
throw new TypeError("Headers are immutable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7.
|
||||||
|
const list = headers[_headerList];
|
||||||
|
const lowercaseName = byteLowerCase(name);
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||||
|
name = list[i][0];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (setCookieValues.length) {
|
list.push([name, value]);
|
||||||
return setCookieValues.join(", ");
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets a value of a key in the headers list.
|
/**
|
||||||
*
|
* @param {HeaderList} list
|
||||||
* The spec indicates that the value should be replaced if the key already
|
* @param {string} name
|
||||||
* exists. The behaviour here varies, where if the key is `set-cookie` the key
|
*/
|
||||||
* of the cookie is inspected, and if the key of the cookie already exists,
|
function getHeader(list, name) {
|
||||||
* then the value is replaced. If the key of the cookie is not found, then
|
const lowercaseName = byteLowerCase(name);
|
||||||
* the value of the `set-cookie` is added to the list of headers.
|
const entries = list.filter((entry) =>
|
||||||
*
|
byteLowerCase(entry[0]) === lowercaseName
|
||||||
* The browser specification of `Headers` is written for clients, and not
|
).map((entry) => entry[1]);
|
||||||
* servers, and Deno is a server, meaning that it needs to follow the patterns
|
if (entries.length === 0) {
|
||||||
* expected for servers, of which a `set-cookie` header is expected for each
|
return null;
|
||||||
* unique cookie key, but duplicate cookie keys should not exist. */
|
} else {
|
||||||
function dataSet(
|
return entries.join("\x2C\x20");
|
||||||
data,
|
}
|
||||||
key,
|
}
|
||||||
value,
|
|
||||||
) {
|
class Headers {
|
||||||
for (let i = 0; i < data.length; i++) {
|
/** @type {HeaderList} */
|
||||||
const [dataKey] = data[i];
|
[_headerList] = [];
|
||||||
if (dataKey === key) {
|
/** @type {"immutable"| "request"| "request-no-cors"| "response" | "none"} */
|
||||||
// there could be multiple set-cookie headers, but all others are unique
|
[_guard];
|
||||||
if (key === "set-cookie") {
|
|
||||||
const [, dataValue] = data[i];
|
get [_iterableHeaders]() {
|
||||||
const [dataCookieKey] = dataValue.split("=");
|
const list = this[_headerList];
|
||||||
const [cookieKey] = value.split("=");
|
|
||||||
if (cookieKey === dataCookieKey) {
|
const headers = [];
|
||||||
data[i][1] = value;
|
const headerNamesSet = new Set();
|
||||||
return;
|
for (const entry of list) {
|
||||||
|
headerNamesSet.add(byteLowerCase(entry[0]));
|
||||||
|
}
|
||||||
|
const names = [...headerNamesSet].sort();
|
||||||
|
for (const name of names) {
|
||||||
|
// The following if statement, and if block of the following statement
|
||||||
|
// are not spec compliant. `set-cookie` is the only header that can not
|
||||||
|
// be concatentated, so must be given to the user as multiple headers.
|
||||||
|
// The else block of the if statement is spec compliant again.
|
||||||
|
if (name == "set-cookie") {
|
||||||
|
const setCookie = list.filter((entry) =>
|
||||||
|
byteLowerCase(entry[0]) === "set-cookie"
|
||||||
|
);
|
||||||
|
if (setCookie.length === 0) throw new TypeError("Unreachable");
|
||||||
|
for (const entry of setCookie) {
|
||||||
|
headers.push([name, entry[1]]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data[i][1] = value;
|
const value = getHeader(list, name);
|
||||||
return;
|
if (value === null) throw new TypeError("Unreachable");
|
||||||
|
headers.push([name, value]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
data.push([key, value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataDelete(data, key) {
|
/** @param {HeadersInit} [init] */
|
||||||
let i = 0;
|
constructor(init = undefined) {
|
||||||
while (i < data.length) {
|
const prefix = "Failed to construct 'Event'";
|
||||||
const [dataKey] = data[i];
|
if (init !== undefined) {
|
||||||
if (dataKey === key) {
|
init = webidl.converters["HeadersInit"](init, {
|
||||||
data.splice(i, 1);
|
prefix,
|
||||||
} else {
|
context: "Argument 1",
|
||||||
i++;
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dataHas(data, key) {
|
this[webidl.brand] = webidl.brand;
|
||||||
for (const [dataKey] of data) {
|
this[_guard] = "none";
|
||||||
if (dataKey === key) {
|
if (init !== undefined) {
|
||||||
return true;
|
fillHeaders(this, init);
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://fetch.spec.whatwg.org/#dom-headers
|
|
||||||
class HeadersBase {
|
|
||||||
constructor(init) {
|
|
||||||
if (init === null) {
|
|
||||||
throw new TypeError(
|
|
||||||
"Failed to construct 'Headers'; The provided value was not valid",
|
|
||||||
);
|
|
||||||
} else if (isHeaders(init)) {
|
|
||||||
this[headersData] = [...init];
|
|
||||||
} else {
|
|
||||||
this[headersData] = [];
|
|
||||||
if (Array.isArray(init)) {
|
|
||||||
for (const tuple of init) {
|
|
||||||
// If header does not contain exactly two items,
|
|
||||||
// then throw a TypeError.
|
|
||||||
// ref: https://fetch.spec.whatwg.org/#concept-headers-fill
|
|
||||||
requiredArguments(
|
|
||||||
"Headers.constructor tuple array argument",
|
|
||||||
tuple.length,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.append(tuple[0], tuple[1]);
|
|
||||||
}
|
|
||||||
} else if (init) {
|
|
||||||
for (const [rawName, rawValue] of Object.entries(init)) {
|
|
||||||
this.append(rawName, rawValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.for("Deno.customInspect")]() {
|
/**
|
||||||
let length = this[headersData].length;
|
* @param {string} name
|
||||||
let output = "";
|
* @param {string} value
|
||||||
for (const [key, value] of this[headersData]) {
|
*/
|
||||||
const prefix = length === this[headersData].length ? " " : "";
|
|
||||||
const postfix = length === 1 ? " " : ", ";
|
|
||||||
output = output + `${prefix}${key}: ${value}${postfix}`;
|
|
||||||
length--;
|
|
||||||
}
|
|
||||||
return `Headers {${output}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ref: https://fetch.spec.whatwg.org/#concept-headers-append
|
|
||||||
append(name, value) {
|
append(name, value) {
|
||||||
requiredArguments("Headers.append", arguments.length, 2);
|
webidl.assertBranded(this, Headers);
|
||||||
const [newname, newvalue] = normalizeParams(name, value);
|
const prefix = "Failed to execute 'append' on 'Headers'";
|
||||||
validateName(newname);
|
webidl.requiredArguments(arguments.length, 2, { prefix });
|
||||||
validateValue(newvalue);
|
name = webidl.converters["ByteString"](name, {
|
||||||
dataAppend(this[headersData], newname, newvalue);
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
value = webidl.converters["ByteString"](value, {
|
||||||
|
prefix,
|
||||||
|
context: "Argument 2",
|
||||||
|
});
|
||||||
|
appendHeader(this, name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
delete(name) {
|
delete(name) {
|
||||||
requiredArguments("Headers.delete", arguments.length, 1);
|
const prefix = "Failed to execute 'delete' on 'Headers'";
|
||||||
const [newname] = normalizeParams(name);
|
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||||
validateName(newname);
|
name = webidl.converters["ByteString"](name, {
|
||||||
dataDelete(this[headersData], newname);
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||||
|
throw new TypeError("Header name is not valid.");
|
||||||
|
}
|
||||||
|
if (this[_guard] == "immutable") {
|
||||||
|
throw new TypeError("Headers are immutable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = this[_headerList];
|
||||||
|
const lowercaseName = byteLowerCase(name);
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||||
|
list.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
get(name) {
|
get(name) {
|
||||||
requiredArguments("Headers.get", arguments.length, 1);
|
const prefix = "Failed to execute 'get' on 'Headers'";
|
||||||
const [newname] = normalizeParams(name);
|
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||||
validateName(newname);
|
name = webidl.converters["ByteString"](name, {
|
||||||
return dataGet(this[headersData], newname) ?? null;
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||||
|
throw new TypeError("Header name is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = this[_headerList];
|
||||||
|
return getHeader(list, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
has(name) {
|
has(name) {
|
||||||
requiredArguments("Headers.has", arguments.length, 1);
|
const prefix = "Failed to execute 'has' on 'Headers'";
|
||||||
const [newname] = normalizeParams(name);
|
webidl.requiredArguments(arguments.length, 1, { prefix });
|
||||||
validateName(newname);
|
name = webidl.converters["ByteString"](name, {
|
||||||
return dataHas(this[headersData], newname);
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||||
|
throw new TypeError("Header name is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = this[_headerList];
|
||||||
|
const lowercaseName = byteLowerCase(name);
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
set(name, value) {
|
set(name, value) {
|
||||||
requiredArguments("Headers.set", arguments.length, 2);
|
webidl.assertBranded(this, Headers);
|
||||||
const [newName, newValue] = normalizeParams(name, value);
|
const prefix = "Failed to execute 'set' on 'Headers'";
|
||||||
validateName(newName);
|
webidl.requiredArguments(arguments.length, 2, { prefix });
|
||||||
validateValue(newValue);
|
name = webidl.converters["ByteString"](name, {
|
||||||
dataSet(this[headersData], newName, newValue);
|
prefix,
|
||||||
|
context: "Argument 1",
|
||||||
|
});
|
||||||
|
value = webidl.converters["ByteString"](value, {
|
||||||
|
prefix,
|
||||||
|
context: "Argument 2",
|
||||||
|
});
|
||||||
|
|
||||||
|
value = normalizeHeaderValue(value);
|
||||||
|
|
||||||
|
// 2.
|
||||||
|
if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
|
||||||
|
throw new TypeError("Header name is not valid.");
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
value.includes("\x00") || value.includes("\x0A") ||
|
||||||
|
value.includes("\x0D")
|
||||||
|
) {
|
||||||
|
throw new TypeError("Header value is not valid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this[_guard] == "immutable") {
|
||||||
|
throw new TypeError("Headers are immutable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = this[_headerList];
|
||||||
|
const lowercaseName = byteLowerCase(name);
|
||||||
|
let added = false;
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (byteLowerCase(list[i][0]) === lowercaseName) {
|
||||||
|
if (!added) {
|
||||||
|
list[i][1] = value;
|
||||||
|
added = true;
|
||||||
|
} else {
|
||||||
|
list.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!added) {
|
||||||
|
list.push([name, value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.for("Deno.customInspect")](inspect) {
|
||||||
|
const headers = {};
|
||||||
|
for (const header of this) {
|
||||||
|
headers[header[0]] = header[1];
|
||||||
|
}
|
||||||
|
return `Headers ${inspect(headers)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get [Symbol.toStringTag]() {
|
get [Symbol.toStringTag]() {
|
||||||
|
@ -249,7 +329,35 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Headers extends DomIterableMixin(HeadersBase, headersData) {}
|
webidl.mixinPairIterable("Headers", Headers, _iterableHeaders, 0, 1);
|
||||||
|
|
||||||
|
webidl.converters["sequence<ByteString>"] = webidl
|
||||||
|
.createSequenceConverter(webidl.converters["ByteString"]);
|
||||||
|
webidl.converters["sequence<sequence<ByteString>>"] = webidl
|
||||||
|
.createSequenceConverter(webidl.converters["sequence<ByteString>"]);
|
||||||
|
webidl.converters["record<ByteString, ByteString>"] = webidl
|
||||||
|
.createRecordConverter(
|
||||||
|
webidl.converters["ByteString"],
|
||||||
|
webidl.converters["ByteString"],
|
||||||
|
);
|
||||||
|
webidl.converters["HeadersInit"] = (V, opts) => {
|
||||||
|
// Union for (sequence<sequence<ByteString>> or record<ByteString, ByteString>)
|
||||||
|
if (typeof V === "object" && V !== null) {
|
||||||
|
if (V[Symbol.iterator] !== undefined) {
|
||||||
|
return webidl.converters["sequence<sequence<ByteString>>"](V, opts);
|
||||||
|
}
|
||||||
|
return webidl.converters["record<ByteString, ByteString>"](V, opts);
|
||||||
|
}
|
||||||
|
throw webidl.makeException(
|
||||||
|
TypeError,
|
||||||
|
"The provided value is not of type '(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'",
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
webidl.converters["Headers"] = webidl.createInterfaceConverter(
|
||||||
|
"Headers",
|
||||||
|
Headers,
|
||||||
|
);
|
||||||
|
|
||||||
window.__bootstrap.headers = {
|
window.__bootstrap.headers = {
|
||||||
Headers,
|
Headers,
|
||||||
|
|
|
@ -58,10 +58,6 @@ pub fn init(isolate: &mut JsRuntime) {
|
||||||
"deno:op_crates/fetch/01_fetch_util.js",
|
"deno:op_crates/fetch/01_fetch_util.js",
|
||||||
include_str!("01_fetch_util.js"),
|
include_str!("01_fetch_util.js"),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"deno:op_crates/fetch/03_dom_iterable.js",
|
|
||||||
include_str!("03_dom_iterable.js"),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"deno:op_crates/fetch/11_streams.js",
|
"deno:op_crates/fetch/11_streams.js",
|
||||||
include_str!("11_streams.js"),
|
include_str!("11_streams.js"),
|
||||||
|
|
|
@ -8,6 +8,74 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
|
const ASCII_DIGIT = ["\u0030-\u0039"];
|
||||||
|
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
|
||||||
|
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
|
||||||
|
const ASCII_ALPHA = [...ASCII_UPPER_ALPHA, ...ASCII_LOWER_ALPHA];
|
||||||
|
const ASCII_ALPHANUMERIC = [...ASCII_DIGIT, ...ASCII_ALPHA];
|
||||||
|
|
||||||
|
const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"];
|
||||||
|
const HTTP_WHITESPACE = ["\u000A", "\u000D", ...HTTP_TAB_OR_SPACE];
|
||||||
|
|
||||||
|
const HTTP_TOKEN_CODE_POINT = [
|
||||||
|
"\u0021",
|
||||||
|
"\u0023",
|
||||||
|
"\u0024",
|
||||||
|
"\u0025",
|
||||||
|
"\u0026",
|
||||||
|
"\u0027",
|
||||||
|
"\u002A",
|
||||||
|
"\u002B",
|
||||||
|
"\u002D",
|
||||||
|
"\u002E",
|
||||||
|
"\u005E",
|
||||||
|
"\u005F",
|
||||||
|
"\u0060",
|
||||||
|
"\u007C",
|
||||||
|
"\u007E",
|
||||||
|
...ASCII_ALPHANUMERIC,
|
||||||
|
];
|
||||||
|
const HTTP_TOKEN_CODE_POINT_RE = new RegExp(
|
||||||
|
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
|
||||||
|
);
|
||||||
|
const HTTP_QUOTED_STRING_TOKEN_POINT = [
|
||||||
|
"\u0009",
|
||||||
|
"\u0020-\u007E",
|
||||||
|
"\u0080-\u00FF",
|
||||||
|
];
|
||||||
|
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
|
||||||
|
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
|
||||||
|
);
|
||||||
|
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
|
||||||
|
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
|
||||||
|
`^[${HTTP_WHITESPACE_MATCHER}]+`,
|
||||||
|
"g",
|
||||||
|
);
|
||||||
|
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp(
|
||||||
|
`[${HTTP_WHITESPACE_MATCHER}]+$`,
|
||||||
|
"g",
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn a string of chars into a regex safe matcher.
|
||||||
|
* @param {string[]} chars
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function regexMatcher(chars) {
|
||||||
|
const matchers = chars.map((char) => {
|
||||||
|
if (char.length === 1) {
|
||||||
|
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
||||||
|
} else if (char.length === 3 && char[1] === "-") {
|
||||||
|
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}-\\u${
|
||||||
|
char.charCodeAt(2).toString(16).padStart(4, "0")
|
||||||
|
}`;
|
||||||
|
} else {
|
||||||
|
throw TypeError("unreachable");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return matchers.join("");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
* https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
|
@ -25,7 +93,43 @@
|
||||||
return { result: input.slice(start, position), position };
|
return { result: input.slice(start, position), position };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function byteUpperCase(s) {
|
||||||
|
return String(s).replace(/[a-z]/g, function byteUpperCaseReplace(c) {
|
||||||
|
return c.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} s
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function byteLowerCase(s) {
|
||||||
|
return String(s).replace(/[A-Z]/g, function byteUpperCaseReplace(c) {
|
||||||
|
return c.toLowerCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.__bootstrap.infra = {
|
window.__bootstrap.infra = {
|
||||||
collectSequenceOfCodepoints,
|
collectSequenceOfCodepoints,
|
||||||
|
ASCII_DIGIT,
|
||||||
|
ASCII_UPPER_ALPHA,
|
||||||
|
ASCII_LOWER_ALPHA,
|
||||||
|
ASCII_ALPHA,
|
||||||
|
ASCII_ALPHANUMERIC,
|
||||||
|
HTTP_TAB_OR_SPACE,
|
||||||
|
HTTP_WHITESPACE,
|
||||||
|
HTTP_TOKEN_CODE_POINT,
|
||||||
|
HTTP_TOKEN_CODE_POINT_RE,
|
||||||
|
HTTP_QUOTED_STRING_TOKEN_POINT,
|
||||||
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||||
|
HTTP_WHITESPACE_PREFIX_RE,
|
||||||
|
HTTP_WHITESPACE_SUFFIX_RE,
|
||||||
|
regexMatcher,
|
||||||
|
byteUpperCase,
|
||||||
|
byteLowerCase,
|
||||||
};
|
};
|
||||||
})(globalThis);
|
})(globalThis);
|
||||||
|
|
|
@ -8,72 +8,14 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
const { collectSequenceOfCodepoints } = window.__bootstrap.infra;
|
const {
|
||||||
|
collectSequenceOfCodepoints,
|
||||||
/**
|
HTTP_WHITESPACE,
|
||||||
* @param {string[]} chars
|
HTTP_WHITESPACE_PREFIX_RE,
|
||||||
* @returns {string}
|
HTTP_WHITESPACE_SUFFIX_RE,
|
||||||
*/
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||||
function regexMatcher(chars) {
|
HTTP_TOKEN_CODE_POINT_RE,
|
||||||
const matchers = chars.map((char) => {
|
} = window.__bootstrap.infra;
|
||||||
if (char.length === 1) {
|
|
||||||
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
|
|
||||||
} else if (char.length === 3 && char[1] === "-") {
|
|
||||||
return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}-\\u${
|
|
||||||
char.charCodeAt(2).toString(16).padStart(4, "0")
|
|
||||||
}`;
|
|
||||||
} else {
|
|
||||||
throw TypeError("unreachable");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return matchers.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
const HTTP_TAB_OR_SPACE = ["\u0009", "\u0020"];
|
|
||||||
const HTTP_WHITESPACE = ["\u000A", "\u000D", ...HTTP_TAB_OR_SPACE];
|
|
||||||
|
|
||||||
const ASCII_DIGIT = ["\u0030-\u0039"];
|
|
||||||
const ASCII_UPPER_ALPHA = ["\u0041-\u005A"];
|
|
||||||
const ASCII_LOWER_ALPHA = ["\u0061-\u007A"];
|
|
||||||
const ASCII_ALPHA = [...ASCII_UPPER_ALPHA, ...ASCII_LOWER_ALPHA];
|
|
||||||
const ASCII_ALPHANUMERIC = [...ASCII_DIGIT, ...ASCII_ALPHA];
|
|
||||||
const HTTP_TOKEN_CODE_POINT = [
|
|
||||||
"\u0021",
|
|
||||||
"\u0023",
|
|
||||||
"\u0025",
|
|
||||||
"\u0026",
|
|
||||||
"\u0027",
|
|
||||||
"\u002A",
|
|
||||||
"\u002B",
|
|
||||||
"\u002D",
|
|
||||||
"\u002E",
|
|
||||||
"\u005E",
|
|
||||||
"\u005F",
|
|
||||||
"\u0060",
|
|
||||||
"\u007C",
|
|
||||||
"\u007E",
|
|
||||||
...ASCII_ALPHANUMERIC,
|
|
||||||
];
|
|
||||||
const HTTP_TOKEN_CODE_POINT_RE = new RegExp(
|
|
||||||
`^[${regexMatcher(HTTP_TOKEN_CODE_POINT)}]+$`,
|
|
||||||
);
|
|
||||||
const HTTP_QUOTED_STRING_TOKEN_POINT = [
|
|
||||||
"\u0009",
|
|
||||||
"\u0020-\u007E",
|
|
||||||
"\u0080-\u00FF",
|
|
||||||
];
|
|
||||||
const HTTP_QUOTED_STRING_TOKEN_POINT_RE = new RegExp(
|
|
||||||
`^[${regexMatcher(HTTP_QUOTED_STRING_TOKEN_POINT)}]+$`,
|
|
||||||
);
|
|
||||||
const HTTP_WHITESPACE_MATCHER = regexMatcher(HTTP_WHITESPACE);
|
|
||||||
const HTTP_WHITESPACE_PREFIX_RE = new RegExp(
|
|
||||||
`^[${HTTP_WHITESPACE_MATCHER}]+`,
|
|
||||||
"g",
|
|
||||||
);
|
|
||||||
const HTTP_WHITESPACE_SUFFIX_RE = new RegExp(
|
|
||||||
`[${HTTP_WHITESPACE_MATCHER}]+$`,
|
|
||||||
"g",
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
|
||||||
|
@ -131,8 +73,16 @@
|
||||||
return { result: input.substring(positionStart, position + 1), position };
|
return { result: input.substring(positionStart, position + 1), position };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef MimeType
|
||||||
|
* @property {string} type
|
||||||
|
* @property {string} subtype
|
||||||
|
* @property {Map<string,string>} parameters
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
|
* @returns {MimeType | null}
|
||||||
*/
|
*/
|
||||||
function parseMimeType(input) {
|
function parseMimeType(input) {
|
||||||
// 1.
|
// 1.
|
||||||
|
|
25
op_crates/web/internal.d.ts
vendored
25
op_crates/web/internal.d.ts
vendored
|
@ -17,15 +17,32 @@ declare namespace globalThis {
|
||||||
result: string;
|
result: string;
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
|
ASCII_DIGIT: string[];
|
||||||
|
ASCII_UPPER_ALPHA: string[];
|
||||||
|
ASCII_LOWER_ALPHA: string[];
|
||||||
|
ASCII_ALPHA: string[];
|
||||||
|
ASCII_ALPHANUMERIC: string[];
|
||||||
|
HTTP_TAB_OR_SPACE: string[];
|
||||||
|
HTTP_WHITESPACE: string[];
|
||||||
|
HTTP_TOKEN_CODE_POINT: string[];
|
||||||
|
HTTP_TOKEN_CODE_POINT_RE: RegExp;
|
||||||
|
HTTP_QUOTED_STRING_TOKEN_POINT: string[];
|
||||||
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE: RegExp;
|
||||||
|
HTTP_WHITESPACE_PREFIX_RE: RegExp;
|
||||||
|
HTTP_WHITESPACE_SUFFIX_RE: RegExp;
|
||||||
|
regexMatcher(chars: string[]): string;
|
||||||
|
byteUpperCase(s: string): string;
|
||||||
|
byteLowerCase(s: string): string;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare var mimesniff: {
|
declare namespace mimesniff {
|
||||||
parseMimeType(input: string): {
|
declare interface MimeType {
|
||||||
type: string;
|
type: string;
|
||||||
subtype: string;
|
subtype: string;
|
||||||
parameters: Map<string, string>;
|
parameters: Map<string, string>;
|
||||||
} | null;
|
}
|
||||||
};
|
declare function parseMimeType(input: string): MimeType | null;
|
||||||
|
}
|
||||||
|
|
||||||
declare var eventTarget: {
|
declare var eventTarget: {
|
||||||
EventTarget: typeof EventTarget;
|
EventTarget: typeof EventTarget;
|
||||||
|
|
|
@ -764,12 +764,16 @@
|
||||||
opts,
|
opts,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const keys = Reflect.ownKeys(V);
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const key of V) {
|
for (const key of keys) {
|
||||||
const typedKey = keyConverter(key, opts);
|
const desc = Object.getOwnPropertyDescriptor(V, key);
|
||||||
const value = V[key];
|
if (desc !== undefined && desc.enumerable === true) {
|
||||||
const typedValue = valueConverter(value, opts);
|
const typedKey = keyConverter(key, opts);
|
||||||
result[typedKey] = typedValue;
|
const value = V[key];
|
||||||
|
const typedValue = valueConverter(value, opts);
|
||||||
|
result[typedKey] = typedValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
@ -802,29 +806,81 @@
|
||||||
throw new TypeError("Illegal constructor");
|
throw new TypeError("Illegal constructor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function define(target, source) {
|
||||||
|
for (const key of Reflect.ownKeys(source)) {
|
||||||
|
const descriptor = Reflect.getOwnPropertyDescriptor(source, key);
|
||||||
|
if (descriptor && !Reflect.defineProperty(target, key, descriptor)) {
|
||||||
|
throw new TypeError(`Cannot redefine property: ${String(key)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _iteratorInternal = Symbol("iterator internal");
|
||||||
|
|
||||||
|
const globalIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf(
|
||||||
|
[][Symbol.iterator](),
|
||||||
|
));
|
||||||
|
|
||||||
function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) {
|
function mixinPairIterable(name, prototype, dataSymbol, keyKey, valueKey) {
|
||||||
const methods = {
|
const iteratorPrototype = Object.create(globalIteratorPrototype, {
|
||||||
*entries() {
|
[Symbol.toStringTag]: { configurable: true, value: `${name} Iterator` },
|
||||||
assertBranded(this, prototype);
|
});
|
||||||
for (const entry of this[dataSymbol]) {
|
define(iteratorPrototype, {
|
||||||
yield [entry[keyKey], entry[valueKey]];
|
next() {
|
||||||
|
const internal = this && this[_iteratorInternal];
|
||||||
|
if (!internal) {
|
||||||
|
throw new TypeError(
|
||||||
|
`next() called on a value that is not a ${name} iterator object`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
const { target, kind, index } = internal;
|
||||||
|
const values = target[dataSymbol];
|
||||||
|
const len = values.length;
|
||||||
|
if (index >= len) {
|
||||||
|
return { value: undefined, done: true };
|
||||||
|
}
|
||||||
|
const pair = values[index];
|
||||||
|
internal.index = index + 1;
|
||||||
|
let result;
|
||||||
|
switch (kind) {
|
||||||
|
case "key":
|
||||||
|
result = pair[keyKey];
|
||||||
|
break;
|
||||||
|
case "value":
|
||||||
|
result = pair[valueKey];
|
||||||
|
break;
|
||||||
|
case "key+value":
|
||||||
|
result = [pair[keyKey], pair[valueKey]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return { value: result, done: false };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
function createDefaultIterator(target, kind) {
|
||||||
|
const iterator = Object.create(iteratorPrototype);
|
||||||
|
Object.defineProperty(iterator, _iteratorInternal, {
|
||||||
|
value: { target, kind, index: 0 },
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
return iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
const methods = {
|
||||||
|
entries() {
|
||||||
|
assertBranded(this, prototype);
|
||||||
|
return createDefaultIterator(this, "key+value");
|
||||||
},
|
},
|
||||||
[Symbol.iterator]() {
|
[Symbol.iterator]() {
|
||||||
assertBranded(this, prototype);
|
assertBranded(this, prototype);
|
||||||
return this.entries();
|
return createDefaultIterator(this, "key+value");
|
||||||
},
|
},
|
||||||
*keys() {
|
keys() {
|
||||||
assertBranded(this, prototype);
|
assertBranded(this, prototype);
|
||||||
for (const entry of this[dataSymbol]) {
|
return createDefaultIterator(this, "key");
|
||||||
yield entry[keyKey];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
*values() {
|
values() {
|
||||||
assertBranded(this, prototype);
|
assertBranded(this, prototype);
|
||||||
for (const entry of this[dataSymbol]) {
|
return createDefaultIterator(this, "value");
|
||||||
yield entry[valueKey];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
forEach(idlCallback, thisArg) {
|
forEach(idlCallback, thisArg) {
|
||||||
assertBranded(this, prototype);
|
assertBranded(this, prototype);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit e19bdbe96243f2ba548c1fd01c0812d645ba0c6f
|
Subproject commit 579608584916d582d38d0159666aae9a6aaf07ad
|
|
@ -684,6 +684,15 @@
|
||||||
"Check isReloadNavigation attribute",
|
"Check isReloadNavigation attribute",
|
||||||
"Check isHistoryNavigation attribute"
|
"Check isHistoryNavigation attribute"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"headers-basic.any.js": true,
|
||||||
|
"headers-casing.any.js": true,
|
||||||
|
"headers-combine.any.js": true,
|
||||||
|
"headers-errors.any.js": true,
|
||||||
|
"headers-normalize.any.js": true,
|
||||||
|
"headers-record.any.js": true,
|
||||||
|
"headers-structure.any.js": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"data-urls": {
|
"data-urls": {
|
||||||
|
|
|
@ -25,6 +25,9 @@ export async function runWithTestUtil<T>(
|
||||||
}
|
}
|
||||||
const passedTime = performance.now() - start;
|
const passedTime = performance.now() - start;
|
||||||
if (passedTime > 15000) {
|
if (passedTime > 15000) {
|
||||||
|
proc.kill(2);
|
||||||
|
await proc.status();
|
||||||
|
proc.close();
|
||||||
throw new Error("Timed out while trying to start wpt test util.");
|
throw new Error("Timed out while trying to start wpt test util.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue