summaryrefslogtreecommitdiff
path: root/op_crates
diff options
context:
space:
mode:
Diffstat (limited to 'op_crates')
-rw-r--r--op_crates/fetch/03_dom_iterable.js80
-rw-r--r--op_crates/fetch/20_headers.js502
-rw-r--r--op_crates/fetch/lib.rs4
-rw-r--r--op_crates/web/00_infra.js104
-rw-r--r--op_crates/web/01_mimesniff.js82
-rw-r--r--op_crates/web/internal.d.ts25
-rw-r--r--op_crates/webidl/00_webidl.js92
7 files changed, 520 insertions, 369 deletions
diff --git a/op_crates/fetch/03_dom_iterable.js b/op_crates/fetch/03_dom_iterable.js
deleted file mode 100644
index 2a3c72fba..000000000
--- a/op_crates/fetch/03_dom_iterable.js
+++ /dev/null
@@ -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);
diff --git a/op_crates/fetch/20_headers.js b/op_crates/fetch/20_headers.js
index df11d40b6..ce46e5dee 100644
--- a/op_crates/fetch/20_headers.js
+++ b/op_crates/fetch/20_headers.js
@@ -1,247 +1,327 @@
// 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";
((window) => {
- const { DomIterableMixin } = window.__bootstrap.domIterable;
- const { requiredArguments } = window.__bootstrap.fetchUtil;
-
- // From node-fetch
- // Copyright (c) 2016 David Frank. MIT License.
- const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
- const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
-
- function isHeaders(value) {
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
- return value instanceof Headers;
- }
+ const webidl = window.__bootstrap.webidl;
+ const {
+ HTTP_WHITESPACE_PREFIX_RE,
+ HTTP_WHITESPACE_SUFFIX_RE,
+ HTTP_TOKEN_CODE_POINT_RE,
+ byteLowerCase,
+ } = window.__bootstrap.infra;
- const headersData = Symbol("headers data");
+ const _headerList = Symbol("header list");
+ const _iterableHeaders = Symbol("iterable headers");
+ const _guard = Symbol("guard");
- // TODO(bartlomieju): headerGuard? Investigate if it is needed
- // node-fetch did not implement this but it is in the spec
- function normalizeParams(name, value) {
- name = String(name).toLowerCase();
- value = String(value).trim();
- return [name, value];
- }
+ /**
+ * @typedef Header
+ * @type {[string, string]}
+ */
- // 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`);
- }
- }
+ /**
+ * @typedef HeaderList
+ * @type {Header[]}
+ */
- function validateValue(value) {
- if (invalidHeaderCharRegex.test(value)) {
- throw new TypeError(`${value} is not a legal HTTP header value`);
- }
+ /**
+ * @typedef {string} potentialValue
+ * @returns {string}
+ */
+ function normalizeHeaderValue(potentialValue) {
+ potentialValue = potentialValue.replaceAll(HTTP_WHITESPACE_PREFIX_RE, "");
+ potentialValue = potentialValue.replaceAll(HTTP_WHITESPACE_SUFFIX_RE, "");
+ return potentialValue;
}
- /** 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;
+ /**
+ * @param {Headers} headers
+ * @param {HeadersInit} object
+ */
+ function fillHeaders(headers, object) {
+ if (Array.isArray(object)) {
+ for (const header of object) {
+ if (header.length !== 2) {
+ throw new TypeError(
+ `Invalid header. Length must be 2, but is ${header.length}`,
+ );
}
+ 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.
- *
- * This varies slightly from spec behaviour in that when the key is `set-cookie`
- * the value returned will look like a concatenated value, when in fact, if the
- * headers were iterated over, each individual `set-cookie` value is a unique
- * entry in the headers list. */
- function dataGet(
- data,
- key,
- ) {
- const setCookieValues = [];
- for (const [dataKey, value] of data) {
- if (dataKey === key) {
- if (key === "set-cookie") {
- setCookieValues.push(value);
- } else {
- return value;
- }
- }
+ /**
+ * https://fetch.spec.whatwg.org/#concept-headers-append
+ * @param {Headers} headers
+ * @param {string} name
+ * @param {string} value
+ */
+ function appendHeader(headers, name, value) {
+ // 1.
+ value = normalizeHeaderValue(value);
+
+ // 2.
+ if (!HTTP_TOKEN_CODE_POINT_RE.test(name)) {
+ throw new TypeError("Header name is not valid.");
}
- if (setCookieValues.length) {
- return setCookieValues.join(", ");
+ if (
+ value.includes("\x00") || value.includes("\x0A") || value.includes("\x0D")
+ ) {
+ throw new TypeError("Header value is not valid.");
}
- return undefined;
- }
- /** Sets a value of a key in the headers list.
- *
- * The spec indicates that the value should be replaced if the key already
- * 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,
- * then the value is replaced. If the key of the cookie is not found, then
- * the value of the `set-cookie` is added to the list of headers.
- *
- * 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 dataSet(
- data,
- key,
- value,
- ) {
- for (let i = 0; i < data.length; i++) {
- const [dataKey] = data[i];
- if (dataKey === key) {
- // there could be multiple set-cookie headers, but all others are unique
- if (key === "set-cookie") {
- const [, dataValue] = data[i];
- const [dataCookieKey] = dataValue.split("=");
- const [cookieKey] = value.split("=");
- if (cookieKey === dataCookieKey) {
- data[i][1] = value;
- return;
- }
- } else {
- data[i][1] = value;
- return;
- }
- }
+ // 3.
+ if (headers[_guard] == "immutable") {
+ throw new TypeError("Headers are immutable.");
}
- data.push([key, value]);
- }
- function dataDelete(data, key) {
- let i = 0;
- while (i < data.length) {
- const [dataKey] = data[i];
- if (dataKey === key) {
- data.splice(i, 1);
- } else {
- i++;
+ // 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;
}
}
+ list.push([name, value]);
}
- function dataHas(data, key) {
- for (const [dataKey] of data) {
- if (dataKey === key) {
- return true;
- }
+ /**
+ * @param {HeaderList} list
+ * @param {string} name
+ */
+ function getHeader(list, name) {
+ const lowercaseName = byteLowerCase(name);
+ const entries = list.filter((entry) =>
+ byteLowerCase(entry[0]) === lowercaseName
+ ).map((entry) => entry[1]);
+ if (entries.length === 0) {
+ return null;
+ } else {
+ return entries.join("\x2C\x20");
}
- 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);
+ class Headers {
+ /** @type {HeaderList} */
+ [_headerList] = [];
+ /** @type {"immutable"| "request"| "request-no-cors"| "response" | "none"} */
+ [_guard];
+
+ get [_iterableHeaders]() {
+ const list = this[_headerList];
+
+ const headers = [];
+ const headerNamesSet = new Set();
+ 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 {
+ const value = getHeader(list, name);
+ if (value === null) throw new TypeError("Unreachable");
+ headers.push([name, value]);
}
}
+ return headers;
}
- [Symbol.for("Deno.customInspect")]() {
- let length = this[headersData].length;
- let output = "";
- for (const [key, value] of this[headersData]) {
- const prefix = length === this[headersData].length ? " " : "";
- const postfix = length === 1 ? " " : ", ";
- output = output + `${prefix}${key}: ${value}${postfix}`;
- length--;
+ /** @param {HeadersInit} [init] */
+ constructor(init = undefined) {
+ const prefix = "Failed to construct 'Event'";
+ if (init !== undefined) {
+ init = webidl.converters["HeadersInit"](init, {
+ prefix,
+ context: "Argument 1",
+ });
+ }
+
+ this[webidl.brand] = webidl.brand;
+ this[_guard] = "none";
+ if (init !== undefined) {
+ fillHeaders(this, init);
}
- return `Headers {${output}}`;
}
- // ref: https://fetch.spec.whatwg.org/#concept-headers-append
+ /**
+ * @param {string} name
+ * @param {string} value
+ */
append(name, value) {
- requiredArguments("Headers.append", arguments.length, 2);
- const [newname, newvalue] = normalizeParams(name, value);
- validateName(newname);
- validateValue(newvalue);
- dataAppend(this[headersData], newname, newvalue);
+ webidl.assertBranded(this, Headers);
+ const prefix = "Failed to execute 'append' on 'Headers'";
+ webidl.requiredArguments(arguments.length, 2, { prefix });
+ name = webidl.converters["ByteString"](name, {
+ prefix,
+ context: "Argument 1",
+ });
+ value = webidl.converters["ByteString"](value, {
+ prefix,
+ context: "Argument 2",
+ });
+ appendHeader(this, name, value);
}
+ /**
+ * @param {string} name
+ */
delete(name) {
- requiredArguments("Headers.delete", arguments.length, 1);
- const [newname] = normalizeParams(name);
- validateName(newname);
- dataDelete(this[headersData], newname);
+ const prefix = "Failed to execute 'delete' on 'Headers'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ name = webidl.converters["ByteString"](name, {
+ 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) {
- requiredArguments("Headers.get", arguments.length, 1);
- const [newname] = normalizeParams(name);
- validateName(newname);
- return dataGet(this[headersData], newname) ?? null;
+ const prefix = "Failed to execute 'get' on 'Headers'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ name = webidl.converters["ByteString"](name, {
+ 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) {
- requiredArguments("Headers.has", arguments.length, 1);
- const [newname] = normalizeParams(name);
- validateName(newname);
- return dataHas(this[headersData], newname);
+ const prefix = "Failed to execute 'has' on 'Headers'";
+ webidl.requiredArguments(arguments.length, 1, { prefix });
+ name = webidl.converters["ByteString"](name, {
+ 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) {
- requiredArguments("Headers.set", arguments.length, 2);
- const [newName, newValue] = normalizeParams(name, value);
- validateName(newName);
- validateValue(newValue);
- dataSet(this[headersData], newName, newValue);
+ webidl.assertBranded(this, Headers);
+ const prefix = "Failed to execute 'set' on 'Headers'";
+ webidl.requiredArguments(arguments.length, 2, { prefix });
+ name = webidl.converters["ByteString"](name, {
+ 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]() {
@@ -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 = {
Headers,
diff --git a/op_crates/fetch/lib.rs b/op_crates/fetch/lib.rs
index 9eeda059a..030f8a809 100644
--- a/op_crates/fetch/lib.rs
+++ b/op_crates/fetch/lib.rs
@@ -59,10 +59,6 @@ pub fn init(isolate: &mut JsRuntime) {
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",
include_str!("11_streams.js"),
),
diff --git a/op_crates/web/00_infra.js b/op_crates/web/00_infra.js
index 0590ffd03..ff9cb7cd4 100644
--- a/op_crates/web/00_infra.js
+++ b/op_crates/web/00_infra.js
@@ -8,6 +8,74 @@
"use strict";
((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
* @param {string} input
@@ -25,7 +93,43 @@
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 = {
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);
diff --git a/op_crates/web/01_mimesniff.js b/op_crates/web/01_mimesniff.js
index f58130132..534e39c31 100644
--- a/op_crates/web/01_mimesniff.js
+++ b/op_crates/web/01_mimesniff.js
@@ -8,72 +8,14 @@
"use strict";
((window) => {
- const { collectSequenceOfCodepoints } = window.__bootstrap.infra;
-
- /**
- * @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("");
- }
-
- 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",
- );
+ const {
+ collectSequenceOfCodepoints,
+ HTTP_WHITESPACE,
+ HTTP_WHITESPACE_PREFIX_RE,
+ HTTP_WHITESPACE_SUFFIX_RE,
+ HTTP_QUOTED_STRING_TOKEN_POINT_RE,
+ HTTP_TOKEN_CODE_POINT_RE,
+ } = window.__bootstrap.infra;
/**
* https://fetch.spec.whatwg.org/#collect-an-http-quoted-string
@@ -132,7 +74,15 @@
}
/**
+ * @typedef MimeType
+ * @property {string} type
+ * @property {string} subtype
+ * @property {Map<string,string>} parameters
+ */
+
+ /**
* @param {string} input
+ * @returns {MimeType | null}
*/
function parseMimeType(input) {
// 1.
diff --git a/op_crates/web/internal.d.ts b/op_crates/web/internal.d.ts
index 5681edc7b..a5b653218 100644
--- a/op_crates/web/internal.d.ts
+++ b/op_crates/web/internal.d.ts
@@ -17,15 +17,32 @@ declare namespace globalThis {
result: string;
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: {
- parseMimeType(input: string): {
+ declare namespace mimesniff {
+ declare interface MimeType {
type: string;
subtype: string;
parameters: Map<string, string>;
- } | null;
- };
+ }
+ declare function parseMimeType(input: string): MimeType | null;
+ }
declare var eventTarget: {
EventTarget: typeof EventTarget;
diff --git a/op_crates/webidl/00_webidl.js b/op_crates/webidl/00_webidl.js
index 508abe44d..63946c9a1 100644
--- a/op_crates/webidl/00_webidl.js
+++ b/op_crates/webidl/00_webidl.js
@@ -764,12 +764,16 @@
opts,
);
}
+ const keys = Reflect.ownKeys(V);
const result = {};
- for (const key of V) {
- const typedKey = keyConverter(key, opts);
- const value = V[key];
- const typedValue = valueConverter(value, opts);
- result[typedKey] = typedValue;
+ for (const key of keys) {
+ const desc = Object.getOwnPropertyDescriptor(V, key);
+ if (desc !== undefined && desc.enumerable === true) {
+ const typedKey = keyConverter(key, opts);
+ const value = V[key];
+ const typedValue = valueConverter(value, opts);
+ result[typedKey] = typedValue;
+ }
}
return result;
};
@@ -802,29 +806,81 @@
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) {
+ const iteratorPrototype = Object.create(globalIteratorPrototype, {
+ [Symbol.toStringTag]: { configurable: true, value: `${name} Iterator` },
+ });
+ define(iteratorPrototype, {
+ 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() {
+ entries() {
assertBranded(this, prototype);
- for (const entry of this[dataSymbol]) {
- yield [entry[keyKey], entry[valueKey]];
- }
+ return createDefaultIterator(this, "key+value");
},
[Symbol.iterator]() {
assertBranded(this, prototype);
- return this.entries();
+ return createDefaultIterator(this, "key+value");
},
- *keys() {
+ keys() {
assertBranded(this, prototype);
- for (const entry of this[dataSymbol]) {
- yield entry[keyKey];
- }
+ return createDefaultIterator(this, "key");
},
- *values() {
+ values() {
assertBranded(this, prototype);
- for (const entry of this[dataSymbol]) {
- yield entry[valueKey];
- }
+ return createDefaultIterator(this, "value");
},
forEach(idlCallback, thisArg) {
assertBranded(this, prototype);