From 502e14b277d84bf768339f01f164c5eef96a2265 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Wed, 9 Jun 2021 19:31:16 -0600 Subject: [PATCH 01/34] add headers impl --- lib/internal/fetch/headers.js | 207 ++++++++++++++++++++++++++++++++++ node.gyp | 1 + 2 files changed, 208 insertions(+) create mode 100644 lib/internal/fetch/headers.js diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js new file mode 100644 index 00000000000000..4ac1740d16ac61 --- /dev/null +++ b/lib/internal/fetch/headers.js @@ -0,0 +1,207 @@ +'use strict'; + +const { + codes: { ERR_INVALID_ARG_VALUE }, +} = require('internal/errors'); + +const { + ObjectEntries, + ArrayIsArray, + StringPrototypeToLocaleLowerCase, + StringPrototypeReplace, + Symbol, + SymbolIterator, + Array, +} = primordials; + +const { validateObject } = require('internal/validators'); + +const { isBoxedPrimitive } = require('internal/util/types'); + +const { validateHeaderName, validateHeaderValue } = require('_http_outgoing'); + +const { Buffer } = require('buffer'); + +const kHeadersList = Symbol('headers list'); + +/** + * This algorithm is based off of + * https://www.tbray.org/ongoing/When/200x/2003/03/22/Binary + * It only operates on the even indexes of the array (the header names) by only + * iterating at most half the length of the input array. The search also + * assumes all entries are strings and uses String.prototype.localeCompare for + * comparison. + */ +function binarySearch(arr, val) { + let low = 0; + let high = arr.length / 2; + + while (high > low) { + const mid = (high + low) >>> 1; + + if (val.localeCompare(arr[mid * 2]) > 0) { + low = mid + 1; + } else { + high = mid; + } + } + + return low * 2; +} + +function normalizeAndValidateHeaderName(name) { + const normalizedHeaderName = StringPrototypeToLocaleLowerCase(name); + validateHeaderName(normalizedHeaderName); + return normalizedHeaderName; +} + +function normalizeAndValidateHeaderValue(name, value) { + // https://fetch.spec.whatwg.org/#concept-header-value-normalize + const normalizedHeaderValue = StringPrototypeReplace( + value, + /^[\n\t\r\x20]+|[\n\t\r\x20]+$/g, + '' + ); + validateHeaderValue(name, normalizedHeaderValue); + return normalizedHeaderValue; +} + +function fill(headers, object) { + if (kHeadersList in object) { + // Object is instance of Headers + headers[kHeadersList] = new Array(...object[kHeadersList]); + } else if (ArrayIsArray(object)) { + // Support both 1D and 2D arrays of header entries + if (ArrayIsArray(object[0])) { + // Array of arrays + for (const header of object) { + if (header.length !== 2) { + throw new ERR_INVALID_ARG_VALUE('init', header, 'is not of length 2'); + } + headers.append(header[0], header[1]); + } + } else if (typeof object[0] === 'string' || Buffer.isBuffer(object[0])) { + if (object.length % 2 !== 0) { + throw new ERR_INVALID_ARG_VALUE('init', object, 'is not even length'); + } + for (let i = 0; i < object.length; i += 2) { + headers.append( + object[i].toString('utf-8'), + object[i + 1].toString('utf-8') + ); + } + } else { + throw new ERR_INVALID_ARG_VALUE( + 'init', + object, + 'is not a valid array entry' + ); + } + } else if (!isBoxedPrimitive(object)) { + for (const { 0: name, 1: value } of ObjectEntries(object)) { + headers.append(name, value); + } + } +} + +class Headers { + constructor(init) { + this[kHeadersList] = []; + + if (init && validateObject(init, 'init', { allowArray: true })) { + fill(this, init); + } + } + + append(name, value) { + const normalizedName = normalizeAndValidateHeaderName(name); + const normalizedValue = normalizeAndValidateHeaderValue(name, value); + + const index = binarySearch(this[kHeadersList], normalizedName); + + if (this[kHeadersList][index] === normalizedName) { + this[kHeadersList][index + 1] += `, ${normalizedValue}`; + } else { + this[kHeadersList].splice(index, 0, normalizedName, normalizedValue); + } + } + + delete(name) { + const normalizedName = normalizeAndValidateHeaderName(name); + + const index = binarySearch(this[kHeadersList], normalizedName); + + if (this[kHeadersList][index] === normalizedName) { + this[kHeadersList].splice(index, 2); + } + } + + get(name) { + const normalizedName = normalizeAndValidateHeaderName(name); + + const index = binarySearch(this[kHeadersList], normalizedName); + + if (this[kHeadersList][index] === normalizedName) { + return this[kHeadersList][index + 1]; + } + + return null; + } + + has(name) { + const normalizedName = normalizeAndValidateHeaderName(name); + + const index = binarySearch(this[kHeadersList], normalizedName); + + return this[kHeadersList][index] === normalizedName; + } + + set(name, value) { + const normalizedName = normalizeAndValidateHeaderName(name); + const normalizedValue = normalizeAndValidateHeaderValue(name, value); + + const index = binarySearch(this[kHeadersList], normalizedName); + if (this[kHeadersList][index] === normalizedName) { + this[kHeadersList][index + 1] = normalizedValue; + } else { + this[kHeadersList].splice(index, 2, normalizedName, normalizedValue); + } + } + + *keys() { + for (const header of this) { + yield header[0]; + } + } + + *values() { + for (const header of this) { + yield header[1]; + } + } + + *entries() { + yield* this; + } + + forEach(callback, thisArg) { + for (let index = 0; index < this[kHeadersList].length; index += 2) { + callback.call( + thisArg, + this[kHeadersList][index + 1], + this[kHeadersList][index], + this + ); + } + } + + *[SymbolIterator]() { + for (let index = 0; index < this[kHeadersList].length; index += 2) { + yield [this[kHeadersList][index], this[kHeadersList][index + 1]]; + } + } +} + +module.exports = { + Headers, +}; diff --git a/node.gyp b/node.gyp index 30327b38a59df5..35a4904c58d72d 100644 --- a/node.gyp +++ b/node.gyp @@ -151,6 +151,7 @@ 'lib/internal/errors.js', 'lib/internal/error_serdes.js', 'lib/internal/event_target.js', + 'lib/internal/fetch/headers.js', 'lib/internal/fixed_queue.js', 'lib/internal/freelist.js', 'lib/internal/freeze_intrinsics.js', From b0f9a75fab9c0330099404a511410015037a597f Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Wed, 9 Jun 2021 23:21:40 -0600 Subject: [PATCH 02/34] update headers and begin porting tests --- lib/internal/fetch/headers.js | 9 ++- test/parallel/test-headers.js | 126 ++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 test/parallel/test-headers.js diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 4ac1740d16ac61..4e45a9dedab61e 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -21,6 +21,7 @@ const { isBoxedPrimitive } = require('internal/util/types'); const { validateHeaderName, validateHeaderValue } = require('_http_outgoing'); const { Buffer } = require('buffer'); +const console = require('console'); const kHeadersList = Symbol('headers list'); @@ -81,8 +82,9 @@ function fill(headers, object) { headers.append(header[0], header[1]); } } else if (typeof object[0] === 'string' || Buffer.isBuffer(object[0])) { + // flat array of strings or Buffers if (object.length % 2 !== 0) { - throw new ERR_INVALID_ARG_VALUE('init', object, 'is not even length'); + throw new ERR_INVALID_ARG_VALUE('init', object, 'is not even in length'); } for (let i = 0; i < object.length; i += 2) { headers.append( @@ -91,6 +93,7 @@ function fill(headers, object) { ); } } else { + // all other array based entries throw new ERR_INVALID_ARG_VALUE( 'init', object, @@ -98,6 +101,7 @@ function fill(headers, object) { ); } } else if (!isBoxedPrimitive(object)) { + // object of key/value entries for (const { 0: name, 1: value } of ObjectEntries(object)) { headers.append(name, value); } @@ -108,7 +112,7 @@ class Headers { constructor(init) { this[kHeadersList] = []; - if (init && validateObject(init, 'init', { allowArray: true })) { + if (init && validateObject(init, 'init', { allowArray: true }) === undefined) { fill(this, init); } } @@ -204,4 +208,5 @@ class Headers { module.exports = { Headers, + kHeadersList }; diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js new file mode 100644 index 00000000000000..2f9f2bbec2df2e --- /dev/null +++ b/test/parallel/test-headers.js @@ -0,0 +1,126 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// Flags: --expose-internals + +const { Headers, kHeadersList } = require('internal/fetch/headers'); + +{ + // init is undefined + assert.deepStrictEqual(new Headers()[kHeadersList], []); + // Init is flat array with one entry + assert.deepStrictEqual( + new Headers(['test-name', 'test-value'])[kHeadersList], + ['test-name', 'test-value'] + ); + // Init is flat array with multiple entries + assert.deepStrictEqual( + new Headers(['test-name-1', 'test-value-1', 'test-name-2', 'test-value-2'])[ + kHeadersList + ], + ['test-name-1', 'test-value-1', 'test-name-2', 'test-value-2'] + ); + // Init is multidimensional array with one entry + assert.deepStrictEqual( + new Headers([['test-name-1', 'test-value-1']])[kHeadersList], + ['test-name-1', 'test-value-1'] + ); + // Init is multidimensional array with multiple entries + assert.deepStrictEqual( + new Headers([ + ['test-name-1', 'test-value-1'], + ['test-name-2', 'test-value-2'], + ])[kHeadersList], + ['test-name-1', 'test-value-1', 'test-name-2', 'test-value-2'] + ); + // Throws when init length is odd + assert.throws(() => { + new Headers(['test-name-1', 'test-value', 'test-name-2']); + }, "Error: The argument 'init' is not even in length. Received [ 'test-name-1', 'test-value', 'test-name-2' ]"); + // Throws when multidimensional init entry length is not 2 + assert.throws(() => { + new Headers([['test-name-1', 'test-value-1'], ['test-name-2']]); + }, "Error: The argument 'init' is not of length 2. Received [ 'test-name-2' ]"); + // Throws when init is not valid array input + assert.throws(() => { + new Headers([0, 1]); + }, "Error: The argument 'init' is not a valid array entry. Received [ 0, 1 ]"); +} + +{ + // Init is object with single entry + const headers = new Headers({ + 'test-name-1': 'test-value-1', + }); + assert.strictEqual(headers[kHeadersList].length, 2); +} + +{ + // Init is object with multiple entries + const headers = new Headers({ + 'test-name-1': 'test-value-1', + 'test-name-2': 'test-value-2', + }); + assert.strictEqual(headers[kHeadersList].length, 4); +} + +{ + // Init fails silently when initialized with BoxedPrimitives + try { + new Headers(new Number()); + new Headers(new Boolean()); + new Headers(new String()); + } catch (error) { + common.mustNotCall(error); + } +} + +{ + // Init fails silently if function or primitive is passed + try { + new Headers(Function); + new Headers(function() {}); + new Headers(1); + new Headers('test'); + } catch (error) { + common.mustNotCall(error); + } +} + +{ + // headers append + const headers = new Headers(); + headers.append('test-name-1', 'test-value-1'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + ]); + headers.append('test-name-2', 'test-value-2'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + 'test-name-2', + 'test-value-2', + ]); + headers.append('test-name-1', 'test-value-3'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1, test-value-3', + 'test-name-2', + 'test-value-2', + ]); + + assert.throws(() => { + headers.append(); + }); + + assert.throws(() => { + headers.append('test-name'); + }); + + assert.throws(() => { + headers.append('invalid @ header ? name', 'test-value'); + }); +} From 499c1c61715db3e6dc3e5ff5cbd41fe3075942da Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 14 Jun 2021 18:30:23 -0600 Subject: [PATCH 03/34] add in progress test script --- lib/internal/fetch/headers.js | 2 +- test/parallel/test-headers.js | 60 ++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 4e45a9dedab61e..6575434882fc71 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -168,7 +168,7 @@ class Headers { if (this[kHeadersList][index] === normalizedName) { this[kHeadersList][index + 1] = normalizedValue; } else { - this[kHeadersList].splice(index, 2, normalizedName, normalizedValue); + this[kHeadersList].splice(index, 0, normalizedName, normalizedValue); } } diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index 2f9f2bbec2df2e..fe35ac062ecce8 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -90,7 +90,7 @@ const { Headers, kHeadersList } = require('internal/fetch/headers'); } { - // headers append + // append const headers = new Headers(); headers.append('test-name-1', 'test-value-1'); assert.deepStrictEqual(headers[kHeadersList], [ @@ -124,3 +124,61 @@ const { Headers, kHeadersList } = require('internal/fetch/headers'); headers.append('invalid @ header ? name', 'test-value'); }); } + +{ + // delete + const headers = new Headers(); + headers.append('test-name-1', 'test-value-1'); + headers.append('test-name-2', 'test-value-2'); + headers.append('test-name-3', 'test-value-3'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + 'test-name-2', + 'test-value-2', + 'test-name-3', + 'test-value-3' + ]); + assert.doesNotThrow(() => headers.delete('test-name-2')) + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + 'test-name-3', + 'test-value-3' + ]); + assert.doesNotThrow(() => headers.delete('does-not-exist')) + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + 'test-name-3', + 'test-value-3' + ]); + + assert.throws(() => { + headers.delete() + }) + + assert.throws(() => { + headers.delete('invalid @ header ?') + }) +} + +{ + // get + const headers = new Headers(); + headers.append('test-name-1', 'test-value-1'); + headers.append('test-name-2', 'test-value-2'); + headers.append('test-name-3', 'test-value-3'); + assert.deepStrictEqual(headers.get('test-name-1'), 'test-value-1'); + assert.deepStrictEqual(headers.get('does-not-exist'), null); + headers.append('test-name-2', 'test-value-4') + assert.deepStrictEqual(headers.get('test-name-2'), 'test-value-2, test-value-4') + + assert.throws(() => { + headers.get() + }) + + assert.throws(() => { + headers.get('invalid @ header ?') + }) +} \ No newline at end of file From 357ba5dc1bc0cbbdeee5d4ea89f64e49b455a63f Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 11:27:11 -0600 Subject: [PATCH 04/34] complete test migration --- lib/internal/fetch/headers.js | 7 +- test/parallel/test-headers.js | 194 +++++++++++++++++++++++++++++++++- 2 files changed, 196 insertions(+), 5 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 6575434882fc71..3adb5f4ee7fae4 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -15,13 +15,11 @@ const { } = primordials; const { validateObject } = require('internal/validators'); - const { isBoxedPrimitive } = require('internal/util/types'); const { validateHeaderName, validateHeaderValue } = require('_http_outgoing'); const { Buffer } = require('buffer'); -const console = require('console'); const kHeadersList = Symbol('headers list'); @@ -35,7 +33,7 @@ const kHeadersList = Symbol('headers list'); */ function binarySearch(arr, val) { let low = 0; - let high = arr.length / 2; + let high = Math.floor(arr.length / 2); while (high > low) { const mid = (high + low) >>> 1; @@ -208,5 +206,6 @@ class Headers { module.exports = { Headers, - kHeadersList + kHeadersList, + binarySearch }; diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index fe35ac062ecce8..62ad140ebc7606 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -5,7 +5,7 @@ const assert = require('assert'); // Flags: --expose-internals -const { Headers, kHeadersList } = require('internal/fetch/headers'); +const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers'); { // init is undefined @@ -181,4 +181,196 @@ const { Headers, kHeadersList } = require('internal/fetch/headers'); assert.throws(() => { headers.get('invalid @ header ?') }) +} + +{ + // has + const headers = new Headers(); + headers.append('test-name-1', 'test-value-1'); + headers.append('test-name-2', 'test-value-2'); + headers.append('test-name-3', 'test-value-3'); + assert.strictEqual(headers.has('test-name-1'), true); + assert.strictEqual(headers.has('does-not-exist'), false); + + assert.throws(() => { + headers.has() + }) + + assert.throws(() => { + headers.has('invalid @ header ?') + }) +} + +{ + // set + const headers = new Headers(); + headers.set('test-name-1', 'test-value-1'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + ]); + headers.set('test-name-2', 'test-value-2'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-1', + 'test-name-2', + 'test-value-2', + ]); + headers.set('test-name-1', 'test-value-3'); + assert.deepStrictEqual(headers[kHeadersList], [ + 'test-name-1', + 'test-value-3', + 'test-name-2', + 'test-value-2', + ]); + + assert.throws(() => { + headers.set(); + }); + + assert.throws(() => { + headers.set('test-name'); + }); + + assert.throws(() => { + headers.set('invalid @ header ? name', 'test-value'); + }); +} + +{ + // for each + const init = [ + ['a', '1'], + ['b', '2'], + ['c', '3'], + ['abc', '4'], + ['b', '5'] + ] + const expected = [ + ['a', '1'], + ['abc', '4'], + ['b', '2, 5'], + ['c', '3'] + ] + + const headers = new Headers(init) + const that = {} + let i = 0 + headers.forEach(function (value, key, _headers) { + assert.deepStrictEqual(expected[i++], [key, value]) + assert.strictEqual(headers, _headers) + assert.strictEqual(this, that) + }, that) +} + +{ + // entries + const init = [ + ['a', '1'], + ['b', '2'], + ['c', '3'], + ['abc', '4'], + ['b', '5'] + ] + const expected = [ + ['a', '1'], + ['abc', '4'], + ['b', '2, 5'], + ['c', '3'] + ] + const headers = new Headers(init) + let i = 0 + for (const header of headers.entries()) { + assert.deepStrictEqual(header, expected[i++]) + } +} + +{ + // keys + const init = [ + ['a', '1'], + ['b', '2'], + ['c', '3'], + ['abc', '4'], + ['b', '5'] + ] + const expected = ['a', 'abc', 'b', 'c'] + const headers = new Headers(init) + let i = 0 + for (const key of headers.keys()) { + assert.deepStrictEqual(key, expected[i++]) + } +} + +{ + // values + const init = [ + ['a', '1'], + ['b', '2'], + ['c', '3'], + ['abc', '4'], + ['b', '5'] + ] + const expected = ['1', '4', '2, 5', '3'] + const headers = new Headers(init) + let i = 0 + for (const value of headers.values()) { + assert.deepStrictEqual(value, expected[i++]) + } +} + +{ + // for of + const init = [ + ['a', '1'], + ['b', '2'], + ['c', '3'], + ['abc', '4'], + ['b', '5'] + ] + const expected = [ + ['a', '1'], + ['abc', '4'], + ['b', '2, 5'], + ['c', '3'] + ] + let i = 0 + const headers = new Headers(init) + for (const header of headers) { + assert.deepStrictEqual(header, expected[i++]) + } +} + +{ + // 0 1 2 3 4 5 6 7 + const l1 = ['b', 1, 'c', 2, 'd', 3, 'f', 4] + // 0 1 2 3 4 5 6 7 8 9 + const l2 = ['b', 1, 'c', 2, 'd', 3, 'e', 4, 'g', 5] + // 0 1 2 3 4 5 6 7 + const l3 = ['a', 1, 'b', 2, 'bcd', 3, 'c', 4] + // 0 1 2 3 4 5 6 7 8 9 + const l4 = ['a', 1, 'b', 2, 'c', 3, 'cde', 4, 'f', 5] + + const tests = [ + { input: [l1, 'c'], expected: 2, message: 'find item in n=even array' }, + { input: [l1, 'f'], expected: 6, message: 'find item at end of n=even array' }, + { input: [l1, 'b'], expected: 0, message: 'find item at beg of n=even array' }, + { input: [l1, 'e'], expected: 6, message: 'find new item position in n=even array' }, + { input: [l1, 'g'], expected: 8, message: 'find new item position at end of n=even array' }, + { input: [l1, 'a'], expected: 0, message: 'find new item position at beg of n=even array' }, + { input: [l2, 'c'], expected: 2, message: 'find item in n=odd array' }, + { input: [l2, 'g'], expected: 8, message: 'find item at end of n=odd array' }, + { input: [l2, 'b'], expected: 0, message: 'find item at beg of n=odd array' }, + { input: [l2, 'f'], expected: 8, message: 'find new item position in n=odd array' }, + { input: [l2, 'h'], expected: 10, message: 'find new item position at end of n=odd array' }, + { input: [l2, 'a'], expected: 0, message: 'find new item position at beg of n=odd array' }, + { input: [l3, 'b'], expected: 2, message: 'find item with similarity in n=odd array' }, + { input: [l3, 'bcd'], expected: 4, message: 'find item with similarity in n=odd array' }, + { input: [l4, 'c'], expected: 4, message: 'find item with similarity in n=odd array' }, + { input: [l4, 'cde'], expected: 6, message: 'find item with similarity in n=odd array' } + ] + + tests.forEach(({ input: [list, target], expected, message }) => { + assert.deepStrictEqual(expected, binarySearch(list, target), message) + }) } \ No newline at end of file From b34a484e291413b842fac5ca11d3d01dc4bbe085 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 11:35:46 -0600 Subject: [PATCH 05/34] add docs --- doc/api/fetch.md | 225 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 doc/api/fetch.md diff --git a/doc/api/fetch.md b/doc/api/fetch.md new file mode 100644 index 00000000000000..962a34de799f6b --- /dev/null +++ b/doc/api/fetch.md @@ -0,0 +1,225 @@ +# Fetch + +## Class: `fetch.Headers` + +Represents a WHATWG Fetch Spec [Headers Class](https://fetch.spec.whatwg.org/#headers-class) + +### `new Headers([init])` + +* `init` {Headers | Iterable<[string, string]> | string[] | Record} Initial header list to be cloned into the new instance + +```js +new Headers() + +new Headers([ + ['name', 'value'] +]) + +new Headers([ 'name', 'value' ]) + +const headers = new Headers({ + 'name': 'value' +}) + +new Headers(headers) +``` + +### `headers.append(name, value)` + +* `name` {string} +* `value` {string} +* Returns: {void} + +Non-destructive operation for adding header entries. When called multiple times with the same _name_, the values will be collected in a list and returned together when retrieved using [Headers.get](#headersgetname). + +```js +const headers = new Headers() + +headers.append('undici', 'fetch') +headers.get('undici') // -> 'fetch' + +headers.append('foobar', 'fuzz') +headers.append('foobar', 'buzz') +headers.get('foobar') // -> 'fuzz, buzz' +``` + +### `headers.delete(name)` + +* `name` {string} + +Removes a header entry. This operation is destructive and cannot be restored. Does **not** throw an error if the given _name_ does not exist. Reminder that [Headers.get](#headersgetname) will return `null` if the _name_ does not exist. + +```js +const headers = new Headers() + +headers.append('undici', 'fetch') + +headers.get('undici') // -> 'fetch' + +headers.delete('undici') + +headers.get('undici') // -> null +``` + +### `headers.get(name)` + +* `name` {string} +* Returns: {string | null} + +Retrieves a header entry. If the entry _name_ has multiple values, they are returned as a string joined by `','` characters. If the _name_ does not exist, this method returns null. + +```js +const headers = new Headers() + +headers.append('undici', 'fetch') +headers.get('undici') // -> 'fetch' + +headers.append('foobar', 'fuzz') +headers.append('foobar', 'buzz') +headers.get('foobar') // -> 'fuzz, buzz' + +headers.get('nodejs') // -> null +``` + +### `headers.has(name)` + +* `name` {string} +* Returns: {boolean} + +Checks for the existence of a given entry _name_. + +```js +const headers = new Headers() + +headers.append('undici', 'fetch') +headers.has('undici') // -> true +``` + +### `headers.set(name, value)` + +* `name` {string} +* `value` {string} + +Destructive operation that will override any existing values for the given entry _name_. For a non-destructive alternative see [Headers.append](#headersappendname-value). + +```js +const headers = new Headers() + +headers.set('foobar', 'fuzz') +headers.get('foobar') // -> 'fuzz' + +headers.set('foobar', 'buzz') +headers.get('foobar') // -> 'buzz' +``` + +### `headers.values()` + +* Returns: {IteratableIterator} + +Yields a list of header values combined and sorted by their respective keys. + +```js +const headers = new Headers() + +headers.set('abc', '123') +headers.set('def', '456') +headers.set('ghi', '789') +headers.append('ghi', '012') + +for (const value of headers.values()) { + console.log(value) +} + +// -> '123' +// -> '456' +// -> '789, 012' +``` + +### `headers.keys()` + +Returns: {IteratableIterator} + +Yields a sorted list of header keys. + +```js +const headers = new Headers() + +headers.set('abc', '123') +headers.set('def', '456') +headers.set('ghi', '789') +headers.append('ghi', '012') + +for (const name of headers.keys()) { + console.log(name) +} + +// -> 'abc' +// -> 'def' +// -> 'ghi' +``` + +### `headers.forEach(callback, [thisArg])` + +* `callback` {(value: string, key: string, iterable: Headers) => void} +* `thisArg` {any} (optional) + +A Headers class can be iterated using `.forEach(callback, [thisArg])`. + +Optionally a `thisArg` can be passed which will be assigned to the `this` context of callback. + +The headers are returned in a sorted order, and values are combined on similar keys. + +```js +const headers = new Headers([['abc', '123']]) + +headers.forEach(function (value, key, headers) { + console.log(key, value) +}) +// -> 'abc', '123' +``` + +### `headers[Symbol.iterator]` + +* Returns: {Iterator<[string, string]>} + +A Headers class instance is iterable. It yields each of its entries as a pair where the first value is the entry _name_ and the second value is the header _value_. They are sorted by _name_ or otherwise referred to as the header key. + +```js +const headers = new Headers() + +headers.set('abc', '123') +headers.set('def', '456') +headers.set('ghi', '789') +headers.append('ghi', '012') + +for (const [name, value] of headers) { + console.log(name, value) +} + +// -> 'abc', '123' +// -> 'def', '456' +// -> 'ghi', '789, 012' +``` + +### `headers.entries()` + +* Returns: {IteratableIterator<[string, string]>} + +Yields a list of headers sorted and combined by key. + +```js +const headers = new Headers() + +headers.set('abc', '123') +headers.set('def', '456') +headers.set('ghi', '789') +headers.append('ghi', '012') + +for (const entry of headers.entries()) { + console.log(entry) +} + +// -> 'abc', '123' +// -> 'def', '456' +// -> 'ghi', '789, 012' +``` \ No newline at end of file From f8f20594b19b8964e2d1c2117f5c771a5a6167ea Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 11:37:38 -0600 Subject: [PATCH 06/34] fix ordering --- lib/internal/fetch/headers.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 3adb5f4ee7fae4..ffe447e543a4d0 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -5,13 +5,14 @@ const { } = require('internal/errors'); const { - ObjectEntries, + Array, ArrayIsArray, - StringPrototypeToLocaleLowerCase, + MathFloor, + ObjectEntries, StringPrototypeReplace, + StringPrototypeToLocaleLowerCase, Symbol, SymbolIterator, - Array, } = primordials; const { validateObject } = require('internal/validators'); @@ -33,7 +34,7 @@ const kHeadersList = Symbol('headers list'); */ function binarySearch(arr, val) { let low = 0; - let high = Math.floor(arr.length / 2); + let high = MathFloor(arr.length / 2); while (high > low) { const mid = (high + low) >>> 1; From b5857163f10a893b8dd4428fd9569aaa5c5d262f Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:26:49 -0600 Subject: [PATCH 07/34] lint fixes --- doc/api/fetch.md | 120 +++++++------- lib/internal/fetch/headers.js | 29 ++-- test/parallel/test-headers.js | 301 ++++++++++++++++++++++++---------- 3 files changed, 286 insertions(+), 164 deletions(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index 962a34de799f6b..9aa92e603bdc75 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -9,19 +9,19 @@ Represents a WHATWG Fetch Spec [Headers Class](https://fetch.spec.whatwg.org/#he * `init` {Headers | Iterable<[string, string]> | string[] | Record} Initial header list to be cloned into the new instance ```js -new Headers() +new Headers(); new Headers([ - ['name', 'value'] -]) + ['name', 'value'], +]); -new Headers([ 'name', 'value' ]) +new Headers([ 'name', 'value' ]); const headers = new Headers({ 'name': 'value' -}) +}); -new Headers(headers) +new Headers(headers); ``` ### `headers.append(name, value)` @@ -33,14 +33,14 @@ new Headers(headers) Non-destructive operation for adding header entries. When called multiple times with the same _name_, the values will be collected in a list and returned together when retrieved using [Headers.get](#headersgetname). ```js -const headers = new Headers() +const headers = new Headers(); -headers.append('undici', 'fetch') -headers.get('undici') // -> 'fetch' +headers.append('undici', 'fetch'); +headers.get('undici'); // -> 'fetch' -headers.append('foobar', 'fuzz') -headers.append('foobar', 'buzz') -headers.get('foobar') // -> 'fuzz, buzz' +headers.append('foobar', 'fuzz'); +headers.append('foobar', 'buzz'); +headers.get('foobar'); // -> 'fuzz, buzz' ``` ### `headers.delete(name)` @@ -50,15 +50,15 @@ headers.get('foobar') // -> 'fuzz, buzz' Removes a header entry. This operation is destructive and cannot be restored. Does **not** throw an error if the given _name_ does not exist. Reminder that [Headers.get](#headersgetname) will return `null` if the _name_ does not exist. ```js -const headers = new Headers() +const headers = new Headers(); -headers.append('undici', 'fetch') +headers.append('undici', 'fetch'); -headers.get('undici') // -> 'fetch' +headers.get('undici'); // -> 'fetch' -headers.delete('undici') +headers.delete('undici'); -headers.get('undici') // -> null +headers.get('undici'); // -> null ``` ### `headers.get(name)` @@ -69,16 +69,16 @@ headers.get('undici') // -> null Retrieves a header entry. If the entry _name_ has multiple values, they are returned as a string joined by `','` characters. If the _name_ does not exist, this method returns null. ```js -const headers = new Headers() +const headers = new Headers(); -headers.append('undici', 'fetch') -headers.get('undici') // -> 'fetch' +headers.append('undici', 'fetch'); +headers.get('undici'); // -> 'fetch' -headers.append('foobar', 'fuzz') -headers.append('foobar', 'buzz') -headers.get('foobar') // -> 'fuzz, buzz' +headers.append('foobar', 'fuzz'); +headers.append('foobar', 'buzz'); +headers.get('foobar'); // -> 'fuzz, buzz' -headers.get('nodejs') // -> null +headers.get('nodejs'); // -> null ``` ### `headers.has(name)` @@ -89,10 +89,10 @@ headers.get('nodejs') // -> null Checks for the existence of a given entry _name_. ```js -const headers = new Headers() +const headers = new Headers(); -headers.append('undici', 'fetch') -headers.has('undici') // -> true +headers.append('undici', 'fetch'); +headers.has('undici'); // -> true ``` ### `headers.set(name, value)` @@ -103,13 +103,13 @@ headers.has('undici') // -> true Destructive operation that will override any existing values for the given entry _name_. For a non-destructive alternative see [Headers.append](#headersappendname-value). ```js -const headers = new Headers() +const headers = new Headers(); -headers.set('foobar', 'fuzz') -headers.get('foobar') // -> 'fuzz' +headers.set('foobar', 'fuzz'); +headers.get('foobar'); // -> 'fuzz' -headers.set('foobar', 'buzz') -headers.get('foobar') // -> 'buzz' +headers.set('foobar', 'buzz'); +headers.get('foobar'); // -> 'buzz' ``` ### `headers.values()` @@ -119,15 +119,15 @@ headers.get('foobar') // -> 'buzz' Yields a list of header values combined and sorted by their respective keys. ```js -const headers = new Headers() +const headers = new Headers(); -headers.set('abc', '123') -headers.set('def', '456') -headers.set('ghi', '789') -headers.append('ghi', '012') +headers.set('abc', '123'); +headers.set('def', '456'); +headers.set('ghi', '789'); +headers.append('ghi', '012'); for (const value of headers.values()) { - console.log(value) + console.log(value); } // -> '123' @@ -142,15 +142,15 @@ Returns: {IteratableIterator} Yields a sorted list of header keys. ```js -const headers = new Headers() +const headers = new Headers(); -headers.set('abc', '123') -headers.set('def', '456') -headers.set('ghi', '789') -headers.append('ghi', '012') +headers.set('abc', '123'); +headers.set('def', '456'); +headers.set('ghi', '789'); +headers.append('ghi', '012'); for (const name of headers.keys()) { - console.log(name) + console.log(name); } // -> 'abc' @@ -170,11 +170,11 @@ Optionally a `thisArg` can be passed which will be assigned to the `this` contex The headers are returned in a sorted order, and values are combined on similar keys. ```js -const headers = new Headers([['abc', '123']]) +const headers = new Headers([['abc', '123']]); -headers.forEach(function (value, key, headers) { - console.log(key, value) -}) +headers.forEach(function(value, key, headers) { + console.log(key, value); +}); // -> 'abc', '123' ``` @@ -185,15 +185,15 @@ headers.forEach(function (value, key, headers) { A Headers class instance is iterable. It yields each of its entries as a pair where the first value is the entry _name_ and the second value is the header _value_. They are sorted by _name_ or otherwise referred to as the header key. ```js -const headers = new Headers() +const headers = new Headers(); -headers.set('abc', '123') -headers.set('def', '456') -headers.set('ghi', '789') -headers.append('ghi', '012') +headers.set('abc', '123'); +headers.set('def', '456'); +headers.set('ghi', '789'); +headers.append('ghi', '012'); for (const [name, value] of headers) { - console.log(name, value) + console.log(name, value); } // -> 'abc', '123' @@ -208,15 +208,15 @@ for (const [name, value] of headers) { Yields a list of headers sorted and combined by key. ```js -const headers = new Headers() +const headers = new Headers(); -headers.set('abc', '123') -headers.set('def', '456') -headers.set('ghi', '789') -headers.append('ghi', '012') +headers.set('abc', '123'); +headers.set('def', '456'); +headers.set('ghi', '789'); +headers.append('ghi', '012'); for (const entry of headers.entries()) { - console.log(entry) + console.log(entry); } // -> 'abc', '123' diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index ffe447e543a4d0..59729da93be4c0 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -1,7 +1,7 @@ 'use strict'; const { - codes: { ERR_INVALID_ARG_VALUE }, + codes: { ERR_INVALID_ARG_VALUE } } = require('internal/errors'); const { @@ -12,7 +12,7 @@ const { StringPrototypeReplace, StringPrototypeToLocaleLowerCase, Symbol, - SymbolIterator, + SymbolIterator } = primordials; const { validateObject } = require('internal/validators'); @@ -76,14 +76,16 @@ function fill(headers, object) { // Array of arrays for (const header of object) { if (header.length !== 2) { - throw new ERR_INVALID_ARG_VALUE('init', header, 'is not of length 2'); + throw new ERR_INVALID_ARG_VALUE('init', header, + 'is not of length 2'); } headers.append(header[0], header[1]); } } else if (typeof object[0] === 'string' || Buffer.isBuffer(object[0])) { - // flat array of strings or Buffers + // Flat array of strings or Buffers if (object.length % 2 !== 0) { - throw new ERR_INVALID_ARG_VALUE('init', object, 'is not even in length'); + throw new ERR_INVALID_ARG_VALUE('init', object, + 'is not even in length'); } for (let i = 0; i < object.length; i += 2) { headers.append( @@ -92,7 +94,7 @@ function fill(headers, object) { ); } } else { - // all other array based entries + // All other array based entries throw new ERR_INVALID_ARG_VALUE( 'init', object, @@ -100,7 +102,7 @@ function fill(headers, object) { ); } } else if (!isBoxedPrimitive(object)) { - // object of key/value entries + // Object of key/value entries for (const { 0: name, 1: value } of ObjectEntries(object)) { headers.append(name, value); } @@ -111,7 +113,8 @@ class Headers { constructor(init) { this[kHeadersList] = []; - if (init && validateObject(init, 'init', { allowArray: true }) === undefined) { + if (init && + validateObject(init, 'init', { allowArray: true }) === undefined) { fill(this, init); } } @@ -171,20 +174,20 @@ class Headers { } } - *keys() { + * keys() { for (const header of this) { yield header[0]; } } - *values() { + * values() { for (const header of this) { yield header[1]; } } - *entries() { - yield* this; + * entries() { + yield * this; } forEach(callback, thisArg) { @@ -198,7 +201,7 @@ class Headers { } } - *[SymbolIterator]() { + * [SymbolIterator]() { for (let index = 0; index < this[kHeadersList].length; index += 2) { yield [this[kHeadersList][index], this[kHeadersList][index + 1]]; } diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index 62ad140ebc7606..8aedf2886f67e8 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -5,7 +5,11 @@ const assert = require('assert'); // Flags: --expose-internals -const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers'); +const { + Headers, + kHeadersList, + binarySearch +} = require('internal/fetch/headers'); { // init is undefined @@ -38,21 +42,33 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' // Throws when init length is odd assert.throws(() => { new Headers(['test-name-1', 'test-value', 'test-name-2']); - }, "Error: The argument 'init' is not even in length. Received [ 'test-name-1', 'test-value', 'test-name-2' ]"); + }, { + name: 'TypeError', + message: "The argument 'init' is not even in length. " + + "Received [ 'test-name-1', 'test-value', 'test-name-2' ]" + }); // Throws when multidimensional init entry length is not 2 assert.throws(() => { new Headers([['test-name-1', 'test-value-1'], ['test-name-2']]); - }, "Error: The argument 'init' is not of length 2. Received [ 'test-name-2' ]"); + }, { + name: 'TypeError', + message: "The argument 'init' is not of length 2. " + + "Received [ 'test-name-2' ]" + }); // Throws when init is not valid array input assert.throws(() => { new Headers([0, 1]); - }, "Error: The argument 'init' is not a valid array entry. Received [ 0, 1 ]"); + }, { + name: 'TypeError', + message: "The argument 'init' is not a valid array entry. " + + 'Received [ 0, 1 ]' + }); } { // Init is object with single entry const headers = new Headers({ - 'test-name-1': 'test-value-1', + 'test-name-1': 'test-value-1' }); assert.strictEqual(headers[kHeadersList].length, 2); } @@ -61,7 +77,7 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' // Init is object with multiple entries const headers = new Headers({ 'test-name-1': 'test-value-1', - 'test-name-2': 'test-value-2', + 'test-name-2': 'test-value-2' }); assert.strictEqual(headers[kHeadersList].length, 4); } @@ -114,14 +130,24 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' assert.throws(() => { headers.append(); + }, { + name: 'TypeError', + message: 'String.prototype.toLocaleLowerCase called on null or undefined' }); assert.throws(() => { headers.append('test-name'); + }, { + name: 'TypeError', + message: 'String.prototype.replace called on null or undefined' }); assert.throws(() => { headers.append('invalid @ header ? name', 'test-value'); + }, { + name: 'TypeError', + message: + 'Header name must be a valid HTTP token ["invalid @ header ? name"]' }); } @@ -137,30 +163,36 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' 'test-name-2', 'test-value-2', 'test-name-3', - 'test-value-3' + 'test-value-3', ]); - assert.doesNotThrow(() => headers.delete('test-name-2')) + headers.delete('test-name-2'); assert.deepStrictEqual(headers[kHeadersList], [ 'test-name-1', 'test-value-1', 'test-name-3', - 'test-value-3' + 'test-value-3', ]); - assert.doesNotThrow(() => headers.delete('does-not-exist')) + headers.delete('does-not-exist'); assert.deepStrictEqual(headers[kHeadersList], [ 'test-name-1', 'test-value-1', 'test-name-3', - 'test-value-3' + 'test-value-3', ]); assert.throws(() => { - headers.delete() - }) - + headers.delete(); + }, { + name: 'TypeError', + message: 'String.prototype.toLocaleLowerCase called on null or undefined' + }); + assert.throws(() => { - headers.delete('invalid @ header ?') - }) + headers.delete('invalid @ header ?'); + }, { + name: 'TypeError', + message: 'Header name must be a valid HTTP token ["invalid @ header ?"]' + }); } { @@ -171,16 +203,23 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' headers.append('test-name-3', 'test-value-3'); assert.deepStrictEqual(headers.get('test-name-1'), 'test-value-1'); assert.deepStrictEqual(headers.get('does-not-exist'), null); - headers.append('test-name-2', 'test-value-4') - assert.deepStrictEqual(headers.get('test-name-2'), 'test-value-2, test-value-4') + headers.append('test-name-2', 'test-value-4'); + assert.deepStrictEqual(headers.get('test-name-2'), + 'test-value-2, test-value-4'); assert.throws(() => { - headers.get() - }) - + headers.get(); + }, { + name: 'TypeError', + message: 'String.prototype.toLocaleLowerCase called on null or undefined' + }); + assert.throws(() => { - headers.get('invalid @ header ?') - }) + headers.get('invalid @ header ?'); + }, { + name: 'TypeError', + message: 'Header name must be a valid HTTP token ["invalid @ header ?"]' + }); } { @@ -193,12 +232,18 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' assert.strictEqual(headers.has('does-not-exist'), false); assert.throws(() => { - headers.has() - }) - + headers.has(); + }, { + name: 'TypeError', + message: 'String.prototype.toLocaleLowerCase called on null or undefined' + }); + assert.throws(() => { - headers.has('invalid @ header ?') - }) + headers.has('invalid @ header ?'); + }, { + name: 'TypeError', + message: 'Header name must be a valid HTTP token ["invalid @ header ?"]' + }); } { @@ -226,14 +271,24 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' assert.throws(() => { headers.set(); + }, { + name: 'TypeError', + message: 'String.prototype.toLocaleLowerCase called on null or undefined' }); assert.throws(() => { headers.set('test-name'); + }, { + name: 'TypeError', + message: 'String.prototype.replace called on null or undefined' }); assert.throws(() => { headers.set('invalid @ header ? name', 'test-value'); + }, { + name: 'TypeError', + message: + 'Header name must be a valid HTTP token ["invalid @ header ? name"]' }); } @@ -244,23 +299,23 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' ['b', '2'], ['c', '3'], ['abc', '4'], - ['b', '5'] - ] + ['b', '5'], + ]; const expected = [ ['a', '1'], ['abc', '4'], ['b', '2, 5'], - ['c', '3'] - ] - - const headers = new Headers(init) - const that = {} - let i = 0 - headers.forEach(function (value, key, _headers) { - assert.deepStrictEqual(expected[i++], [key, value]) - assert.strictEqual(headers, _headers) - assert.strictEqual(this, that) - }, that) + ['c', '3'], + ]; + + const headers = new Headers(init); + const that = {}; + let i = 0; + headers.forEach(function(value, key, _headers) { + assert.deepStrictEqual(expected[i++], [key, value]); + assert.strictEqual(headers, _headers); + assert.strictEqual(this, that); + }, that); } { @@ -270,18 +325,18 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' ['b', '2'], ['c', '3'], ['abc', '4'], - ['b', '5'] - ] + ['b', '5'], + ]; const expected = [ ['a', '1'], ['abc', '4'], ['b', '2, 5'], - ['c', '3'] - ] - const headers = new Headers(init) - let i = 0 + ['c', '3'], + ]; + const headers = new Headers(init); + let i = 0; for (const header of headers.entries()) { - assert.deepStrictEqual(header, expected[i++]) + assert.deepStrictEqual(header, expected[i++]); } } @@ -292,13 +347,13 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' ['b', '2'], ['c', '3'], ['abc', '4'], - ['b', '5'] - ] - const expected = ['a', 'abc', 'b', 'c'] - const headers = new Headers(init) - let i = 0 + ['b', '5'], + ]; + const expected = ['a', 'abc', 'b', 'c']; + const headers = new Headers(init); + let i = 0; for (const key of headers.keys()) { - assert.deepStrictEqual(key, expected[i++]) + assert.deepStrictEqual(key, expected[i++]); } } @@ -309,13 +364,13 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' ['b', '2'], ['c', '3'], ['abc', '4'], - ['b', '5'] - ] - const expected = ['1', '4', '2, 5', '3'] - const headers = new Headers(init) - let i = 0 + ['b', '5'], + ]; + const expected = ['1', '4', '2, 5', '3']; + const headers = new Headers(init); + let i = 0; for (const value of headers.values()) { - assert.deepStrictEqual(value, expected[i++]) + assert.deepStrictEqual(value, expected[i++]); } } @@ -326,51 +381,115 @@ const { Headers, kHeadersList, binarySearch } = require('internal/fetch/headers' ['b', '2'], ['c', '3'], ['abc', '4'], - ['b', '5'] - ] + ['b', '5'], + ]; const expected = [ ['a', '1'], ['abc', '4'], ['b', '2, 5'], - ['c', '3'] - ] - let i = 0 - const headers = new Headers(init) + ['c', '3'], + ]; + let i = 0; + const headers = new Headers(init); for (const header of headers) { - assert.deepStrictEqual(header, expected[i++]) + assert.deepStrictEqual(header, expected[i++]); } } { // 0 1 2 3 4 5 6 7 - const l1 = ['b', 1, 'c', 2, 'd', 3, 'f', 4] + const l1 = ['b', 1, 'c', 2, 'd', 3, 'f', 4]; // 0 1 2 3 4 5 6 7 8 9 - const l2 = ['b', 1, 'c', 2, 'd', 3, 'e', 4, 'g', 5] + const l2 = ['b', 1, 'c', 2, 'd', 3, 'e', 4, 'g', 5]; // 0 1 2 3 4 5 6 7 - const l3 = ['a', 1, 'b', 2, 'bcd', 3, 'c', 4] + const l3 = ['a', 1, 'b', 2, 'bcd', 3, 'c', 4]; // 0 1 2 3 4 5 6 7 8 9 - const l4 = ['a', 1, 'b', 2, 'c', 3, 'cde', 4, 'f', 5] + const l4 = ['a', 1, 'b', 2, 'c', 3, 'cde', 4, 'f', 5]; const tests = [ - { input: [l1, 'c'], expected: 2, message: 'find item in n=even array' }, - { input: [l1, 'f'], expected: 6, message: 'find item at end of n=even array' }, - { input: [l1, 'b'], expected: 0, message: 'find item at beg of n=even array' }, - { input: [l1, 'e'], expected: 6, message: 'find new item position in n=even array' }, - { input: [l1, 'g'], expected: 8, message: 'find new item position at end of n=even array' }, - { input: [l1, 'a'], expected: 0, message: 'find new item position at beg of n=even array' }, - { input: [l2, 'c'], expected: 2, message: 'find item in n=odd array' }, - { input: [l2, 'g'], expected: 8, message: 'find item at end of n=odd array' }, - { input: [l2, 'b'], expected: 0, message: 'find item at beg of n=odd array' }, - { input: [l2, 'f'], expected: 8, message: 'find new item position in n=odd array' }, - { input: [l2, 'h'], expected: 10, message: 'find new item position at end of n=odd array' }, - { input: [l2, 'a'], expected: 0, message: 'find new item position at beg of n=odd array' }, - { input: [l3, 'b'], expected: 2, message: 'find item with similarity in n=odd array' }, - { input: [l3, 'bcd'], expected: 4, message: 'find item with similarity in n=odd array' }, - { input: [l4, 'c'], expected: 4, message: 'find item with similarity in n=odd array' }, - { input: [l4, 'cde'], expected: 6, message: 'find item with similarity in n=odd array' } - ] + { + input: [l1, 'c'], + expected: 2, + message: 'find item in n=even array' + }, + { + input: [l1, 'f'], + expected: 6, + message: 'find item at end of n=even array' + }, + { + input: [l1, 'b'], + expected: 0, + message: 'find item at beg of n=even array' + }, + { + input: [l1, 'e'], + expected: 6, + message: 'find new item position in n=even array' + }, + { + input: [l1, 'g'], + expected: 8, + message: 'find new item position at end of n=even array' + }, + { + input: [l1, 'a'], + expected: 0, + message: 'find new item position at beg of n=even array' + }, + { + input: [l2, 'c'], + expected: 2, + message: 'find item in n=odd array' + }, + { + input: [l2, 'g'], + expected: 8, + message: 'find item at end of n=odd array' + }, + { + input: [l2, 'b'], + expected: 0, + message: 'find item at beg of n=odd array' + }, + { + input: [l2, 'f'], + expected: 8, + message: 'find new item position in n=odd array' + }, + { + input: [l2, 'h'], + expected: 10, + message: 'find new item position at end of n=odd array' + }, + { + input: [l2, 'a'], + expected: 0, + message: 'find new item position at beg of n=odd array' + }, + { + input: [l3, 'b'], + expected: 2, + message: 'find item with similarity in n=odd array' + }, + { + input: [l3, 'bcd'], + expected: 4, + message: 'find item with similarity in n=odd array' + }, + { + input: [l4, 'c'], + expected: 4, + message: 'find item with similarity in n=odd array' + }, + { + input: [l4, 'cde'], + expected: 6, + message: 'find item with similarity in n=odd array' + }, + ]; tests.forEach(({ input: [list, target], expected, message }) => { - assert.deepStrictEqual(expected, binarySearch(list, target), message) - }) -} \ No newline at end of file + assert.deepStrictEqual(expected, binarySearch(list, target), message); + }); +} From 865d422cdefdb7fd47048b1ba55ac82489a0f96a Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:27:14 -0600 Subject: [PATCH 08/34] Update doc/api/fetch.md Co-authored-by: Antoine du Hamel --- doc/api/fetch.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index 9aa92e603bdc75..5a20a7d4906a9a 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -1,5 +1,8 @@ # Fetch + + +> Stability: 1 - Experimental ## Class: `fetch.Headers` Represents a WHATWG Fetch Spec [Headers Class](https://fetch.spec.whatwg.org/#headers-class) @@ -222,4 +225,4 @@ for (const entry of headers.entries()) { // -> 'abc', '123' // -> 'def', '456' // -> 'ghi', '789, 012' -``` \ No newline at end of file +``` From c96bf21be9a33719bcc8948f37689570043b81d3 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:27:27 -0600 Subject: [PATCH 09/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 59729da93be4c0..d3540da866fae8 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -39,7 +39,7 @@ function binarySearch(arr, val) { while (high > low) { const mid = (high + low) >>> 1; - if (val.localeCompare(arr[mid * 2]) > 0) { + if (StringPrototypeLocaleCompare(val, arr[mid * 2]) > 0) { low = mid + 1; } else { high = mid; From e7413b15e526488bcd0fee12e5f49ff004863ad4 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:27:54 -0600 Subject: [PATCH 10/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index d3540da866fae8..594ec79ace7a82 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -210,6 +210,6 @@ class Headers { module.exports = { Headers, + binarySearch, kHeadersList, - binarySearch }; From 4d96cf4ad70f33cd1c93430323686e50066b1a4c Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:28:07 -0600 Subject: [PATCH 11/34] Update test/parallel/test-headers.js Co-authored-by: Antoine du Hamel --- test/parallel/test-headers.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index 8aedf2886f67e8..eba37db2401780 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -95,14 +95,12 @@ const { { // Init fails silently if function or primitive is passed - try { - new Headers(Function); - new Headers(function() {}); - new Headers(1); - new Headers('test'); - } catch (error) { - common.mustNotCall(error); - } + [ + Function, + function() {}, + 1, + 'test', + ].forEach((arg) => new Headers(arg)); } { From 6b726a9f4e5b4a10e1ea9a47b3f3daba16f37c4c Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:28:20 -0600 Subject: [PATCH 12/34] Update test/parallel/test-headers.js Co-authored-by: Antoine du Hamel --- test/parallel/test-headers.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index eba37db2401780..a5e327d606ee9a 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -84,13 +84,7 @@ const { { // Init fails silently when initialized with BoxedPrimitives - try { - new Headers(new Number()); - new Headers(new Boolean()); - new Headers(new String()); - } catch (error) { - common.mustNotCall(error); - } + [new Number(), new Boolean(), new String()].forEach((arg) => new Headers(arg)); } { From c735d9e16a76e85e144658b635c6112d6d6cd218 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:30:30 -0600 Subject: [PATCH 13/34] use entries for iterator --- lib/internal/fetch/headers.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 594ec79ace7a82..433c437caeb5f3 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -187,7 +187,9 @@ class Headers { } * entries() { - yield * this; + for (let index = 0; index < this[kHeadersList].length; index += 2) { + yield [this[kHeadersList][index], this[kHeadersList][index + 1]]; + } } forEach(callback, thisArg) { @@ -200,14 +202,10 @@ class Headers { ); } } - - * [SymbolIterator]() { - for (let index = 0; index < this[kHeadersList].length; index += 2) { - yield [this[kHeadersList][index], this[kHeadersList][index + 1]]; - } - } } +Headers.prototype[SymbolIterator] = Headers.prototype.entries; + module.exports = { Headers, binarySearch, From 173ccef944b2d0d5ac16e2ca186e4f575c106927 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:42:18 -0600 Subject: [PATCH 14/34] lint md --- doc/api/fetch.md | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index 5a20a7d4906a9a..0593c17154a9ca 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -3,25 +3,26 @@ > Stability: 1 - Experimental + ## Class: `fetch.Headers` -Represents a WHATWG Fetch Spec [Headers Class](https://fetch.spec.whatwg.org/#headers-class) +Represents a WHATWG Fetch Spec +[Headers Class](https://fetch.spec.whatwg.org/#headers-class) ### `new Headers([init])` -* `init` {Headers | Iterable<[string, string]> | string[] | Record} Initial header list to be cloned into the new instance +* `init` {Headers | Iterable<[string, string]> | string[] | Record} Initial header list to be cloned into the new instance ```js new Headers(); -new Headers([ - ['name', 'value'], -]); +new Headers([['name', 'value']]); -new Headers([ 'name', 'value' ]); +new Headers(['name', 'value']); const headers = new Headers({ - 'name': 'value' + name: 'value', }); new Headers(headers); @@ -33,7 +34,9 @@ new Headers(headers); * `value` {string} * Returns: {void} -Non-destructive operation for adding header entries. When called multiple times with the same _name_, the values will be collected in a list and returned together when retrieved using [Headers.get](#headersgetname). +Non-destructive operation for adding header entries. When called multiple times +with the same _name_, the values will be collected in a list and returned +together when retrieved using [Headers.get](#headersgetname). ```js const headers = new Headers(); @@ -50,7 +53,9 @@ headers.get('foobar'); // -> 'fuzz, buzz' * `name` {string} -Removes a header entry. This operation is destructive and cannot be restored. Does **not** throw an error if the given _name_ does not exist. Reminder that [Headers.get](#headersgetname) will return `null` if the _name_ does not exist. +Removes a header entry. This operation is destructive and cannot be restored. +Does **not** throw an error if the given _name_ does not exist. Reminder that +[Headers.get](#headersgetname) will return `null` if the _name_ does not exist. ```js const headers = new Headers(); @@ -69,7 +74,9 @@ headers.get('undici'); // -> null * `name` {string} * Returns: {string | null} -Retrieves a header entry. If the entry _name_ has multiple values, they are returned as a string joined by `','` characters. If the _name_ does not exist, this method returns null. +Retrieves a header entry. If the entry _name_ has multiple values, they are +returned as a string joined by `','` characters. If the _name_ does not exist, +this method returns null. ```js const headers = new Headers(); @@ -103,7 +110,9 @@ headers.has('undici'); // -> true * `name` {string} * `value` {string} -Destructive operation that will override any existing values for the given entry _name_. For a non-destructive alternative see [Headers.append](#headersappendname-value). +Destructive operation that will override any existing values for the given entry +_name_. For a non-destructive alternative see +[Headers.append](#headersappendname-value). ```js const headers = new Headers(); @@ -168,9 +177,11 @@ for (const name of headers.keys()) { A Headers class can be iterated using `.forEach(callback, [thisArg])`. -Optionally a `thisArg` can be passed which will be assigned to the `this` context of callback. +Optionally a `thisArg` can be passed which will be assigned to the `this` +context of callback. -The headers are returned in a sorted order, and values are combined on similar keys. +The headers are returned in a sorted order, and values are combined on similar +keys. ```js const headers = new Headers([['abc', '123']]); @@ -185,7 +196,9 @@ headers.forEach(function(value, key, headers) { * Returns: {Iterator<[string, string]>} -A Headers class instance is iterable. It yields each of its entries as a pair where the first value is the entry _name_ and the second value is the header _value_. They are sorted by _name_ or otherwise referred to as the header key. +A Headers class instance is iterable. It yields each of its entries as a pair +where the first value is the entry _name_ and the second value is the header +_value_. They are sorted by _name_ or otherwise referred to as the header key. ```js const headers = new Headers(); From d856bd400730ec1a90f0c3ecbc17dbabe006a56c Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:44:14 -0600 Subject: [PATCH 15/34] fix lint again --- doc/api/fetch.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index 0593c17154a9ca..d6e6ef9ee087e5 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -11,7 +11,7 @@ Represents a WHATWG Fetch Spec ### `new Headers([init])` -* `init` {Headers | Iterable<[string, string]> | string[] | Record | string\[] | Record} Initial header list to be cloned into the new instance ```js @@ -194,7 +194,7 @@ headers.forEach(function(value, key, headers) { ### `headers[Symbol.iterator]` -* Returns: {Iterator<[string, string]>} +* Returns: {Iterator<\[string, string]>} A Headers class instance is iterable. It yields each of its entries as a pair where the first value is the entry _name_ and the second value is the header @@ -219,7 +219,7 @@ for (const [name, value] of headers) { ### `headers.entries()` -* Returns: {IteratableIterator<[string, string]>} +* Returns: {IteratableIterator<\[string, string]>} Yields a list of headers sorted and combined by key. From bed131ed9201a9dd2ddb6f946edbbd7696454400 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:45:09 -0600 Subject: [PATCH 16/34] add missing character --- doc/api/fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index d6e6ef9ee087e5..b8cb2188c4febc 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -7,7 +7,7 @@ ## Class: `fetch.Headers` Represents a WHATWG Fetch Spec -[Headers Class](https://fetch.spec.whatwg.org/#headers-class) +[Headers Class](https://fetch.spec.whatwg.org/#headers-class). ### `new Headers([init])` From a87342feed66fbb1a5198b3dda9fad3de5478383 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:51:51 -0600 Subject: [PATCH 17/34] Update doc/api/fetch.md Co-authored-by: Antoine du Hamel --- doc/api/fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index b8cb2188c4febc..f1e4348e0720fd 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -12,7 +12,7 @@ Represents a WHATWG Fetch Spec ### `new Headers([init])` * `init` {Headers | Iterable<\[string, string]> | string\[] | Record} Initial header list to be cloned into the new instance + string>} Initial header list to be cloned into the new instance. ```js new Headers(); From 71c1aa2f897492311daa5581f42c60d288136f32 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:51:58 -0600 Subject: [PATCH 18/34] Update doc/api/fetch.md Co-authored-by: Antoine du Hamel --- doc/api/fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index f1e4348e0720fd..1bf2bf8ae10a7f 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -76,7 +76,7 @@ headers.get('undici'); // -> null Retrieves a header entry. If the entry _name_ has multiple values, they are returned as a string joined by `','` characters. If the _name_ does not exist, -this method returns null. +this method returns `null`. ```js const headers = new Headers(); From d5e3df37744193f196931e475cd7b6b222f76632 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:54:41 -0600 Subject: [PATCH 19/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 433c437caeb5f3..f7cd9e4785bccc 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -57,9 +57,9 @@ function normalizeAndValidateHeaderName(name) { function normalizeAndValidateHeaderValue(name, value) { // https://fetch.spec.whatwg.org/#concept-header-value-normalize - const normalizedHeaderValue = StringPrototypeReplace( - value, + const normalizedHeaderValue = RegExpPrototypeSymbolReplace( /^[\n\t\r\x20]+|[\n\t\r\x20]+$/g, + value, '' ); validateHeaderValue(name, normalizedHeaderValue); From c8d156a981f6da20b64841cd6c7466f1c9ebd2c2 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:55:02 -0600 Subject: [PATCH 20/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index f7cd9e4785bccc..7e13afd6fdde8b 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -103,8 +103,9 @@ function fill(headers, object) { } } else if (!isBoxedPrimitive(object)) { // Object of key/value entries - for (const { 0: name, 1: value } of ObjectEntries(object)) { - headers.append(name, value); + const entries = ObjectEntries(object); + for (let i = 0; i < entries.length; i++) { + headers.append(entries[i][0], entries[i][1]); } } } From 8fdd64cb3ca2da48d5ac2d2a0a84da8ca1626064 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 12:55:27 -0600 Subject: [PATCH 21/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 7e13afd6fdde8b..f39d1c3222897f 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -176,14 +176,14 @@ class Headers { } * keys() { - for (const header of this) { - yield header[0]; + for (let index = 0; index < this[kHeadersList].length; index += 2) { + yield this[kHeadersList][index]; } } * values() { - for (const header of this) { - yield header[1]; + for (let index = 1; index < this[kHeadersList].length; index += 2) { + yield this[kHeadersList][index]; } } From d66e313f39f865add72b0790ec4672681f79c514 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 13:18:45 -0600 Subject: [PATCH 22/34] fix lint and tests --- lib/internal/fetch/headers.js | 15 +++++++++++++-- test/parallel/test-headers.js | 24 ++++++++++++++---------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index f39d1c3222897f..04c79974396019 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -1,7 +1,11 @@ 'use strict'; const { - codes: { ERR_INVALID_ARG_VALUE } + codes: { + ERR_INVALID_ARG_VALUE, + ERR_INVALID_HTTP_TOKEN, + ERR_HTTP_INVALID_HEADER_VALUE + } } = require('internal/errors'); const { @@ -9,7 +13,8 @@ const { ArrayIsArray, MathFloor, ObjectEntries, - StringPrototypeReplace, + RegExpPrototypeSymbolReplace, + StringPrototypeLocaleCompare, StringPrototypeToLocaleLowerCase, Symbol, SymbolIterator @@ -50,12 +55,18 @@ function binarySearch(arr, val) { } function normalizeAndValidateHeaderName(name) { + if (name === undefined) { + throw new ERR_INVALID_HTTP_TOKEN('Header name', name); + } const normalizedHeaderName = StringPrototypeToLocaleLowerCase(name); validateHeaderName(normalizedHeaderName); return normalizedHeaderName; } function normalizeAndValidateHeaderValue(name, value) { + if (value === undefined) { + throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name); + } // https://fetch.spec.whatwg.org/#concept-header-value-normalize const normalizedHeaderValue = RegExpPrototypeSymbolReplace( /^[\n\t\r\x20]+|[\n\t\r\x20]+$/g, diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index a5e327d606ee9a..e91b2e7ebc4043 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -1,6 +1,6 @@ 'use strict'; -const common = require('../common'); +require('../common'); const assert = require('assert'); // Flags: --expose-internals @@ -84,13 +84,17 @@ const { { // Init fails silently when initialized with BoxedPrimitives - [new Number(), new Boolean(), new String()].forEach((arg) => new Headers(arg)); + [ + new Number(), + new Boolean(), + new String(), + ].forEach((arg) => new Headers(arg)); } { // Init fails silently if function or primitive is passed [ - Function, + new Function(), function() {}, 1, 'test', @@ -124,14 +128,14 @@ const { headers.append(); }, { name: 'TypeError', - message: 'String.prototype.toLocaleLowerCase called on null or undefined' + message: 'Header name must be a valid HTTP token ["undefined"]' }); assert.throws(() => { headers.append('test-name'); }, { name: 'TypeError', - message: 'String.prototype.replace called on null or undefined' + message: 'Invalid value "undefined" for header "test-name"' }); assert.throws(() => { @@ -176,7 +180,7 @@ const { headers.delete(); }, { name: 'TypeError', - message: 'String.prototype.toLocaleLowerCase called on null or undefined' + message: 'Header name must be a valid HTTP token ["undefined"]' }); assert.throws(() => { @@ -203,7 +207,7 @@ const { headers.get(); }, { name: 'TypeError', - message: 'String.prototype.toLocaleLowerCase called on null or undefined' + message: 'Header name must be a valid HTTP token ["undefined"]' }); assert.throws(() => { @@ -227,7 +231,7 @@ const { headers.has(); }, { name: 'TypeError', - message: 'String.prototype.toLocaleLowerCase called on null or undefined' + message: 'Header name must be a valid HTTP token ["undefined"]' }); assert.throws(() => { @@ -265,14 +269,14 @@ const { headers.set(); }, { name: 'TypeError', - message: 'String.prototype.toLocaleLowerCase called on null or undefined' + message: 'Header name must be a valid HTTP token ["undefined"]' }); assert.throws(() => { headers.set('test-name'); }, { name: 'TypeError', - message: 'String.prototype.replace called on null or undefined' + message: 'Invalid value "undefined" for header "test-name"' }); assert.throws(() => { From 1d042f0b6fc13d2e2dd1b635ae740dd2ee085d8f Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 13:20:41 -0600 Subject: [PATCH 23/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 04c79974396019..6d66b83c236fca 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -80,7 +80,7 @@ function normalizeAndValidateHeaderValue(name, value) { function fill(headers, object) { if (kHeadersList in object) { // Object is instance of Headers - headers[kHeadersList] = new Array(...object[kHeadersList]); + headers[kHeadersList] = ArrayPrototypeSlice(object[kHeadersList]); } else if (ArrayIsArray(object)) { // Support both 1D and 2D arrays of header entries if (ArrayIsArray(object[0])) { From 0a58d93ec8252ca364799f66bd5a82ab4716a47f Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 21 Jun 2021 13:24:37 -0600 Subject: [PATCH 24/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 6d66b83c236fca..832708a96e810c 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -85,12 +85,12 @@ function fill(headers, object) { // Support both 1D and 2D arrays of header entries if (ArrayIsArray(object[0])) { // Array of arrays - for (const header of object) { - if (header.length !== 2) { + for (let i = 0; i < object.length; i++) { + if (object[i].length !== 2) { throw new ERR_INVALID_ARG_VALUE('init', header, 'is not of length 2'); } - headers.append(header[0], header[1]); + headers.append(object[i][0], object[i][1]); } } else if (typeof object[0] === 'string' || Buffer.isBuffer(object[0])) { // Flat array of strings or Buffers From a85b1c09fab187f0ec02fad463e49b8945367959 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 22 Jul 2021 13:27:52 -0600 Subject: [PATCH 25/34] Update lib/internal/fetch/headers.js Co-authored-by: James M Snell --- lib/internal/fetch/headers.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 832708a96e810c..07939600412e7e 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -122,13 +122,10 @@ function fill(headers, object) { } class Headers { - constructor(init) { + constructor(init = {}) { + validateObject(init, 'init', { allowArray: true }); this[kHeadersList] = []; - - if (init && - validateObject(init, 'init', { allowArray: true }) === undefined) { - fill(this, init); - } + fill(this, init); } append(name, value) { From 58da7013f6bd15b9beb1a3a487b217281d2e4097 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 22 Jul 2021 13:47:09 -0600 Subject: [PATCH 26/34] incorporate review and fix failing test --- lib/internal/fetch/headers.js | 73 +++++++++++++++++++++++++++++++---- test/parallel/test-headers.js | 1 + 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 07939600412e7e..00671d76ff8ee9 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -9,15 +9,16 @@ const { } = require('internal/errors'); const { - Array, ArrayIsArray, + ArrayPrototypeSplice, MathFloor, ObjectEntries, RegExpPrototypeSymbolReplace, StringPrototypeLocaleCompare, StringPrototypeToLocaleLowerCase, Symbol, - SymbolIterator + SymbolIterator, + ObjectDefineProperties } = primordials; const { validateObject } = require('internal/validators'); @@ -77,8 +78,12 @@ function normalizeAndValidateHeaderValue(name, value) { return normalizedHeaderValue; } +function isHeaders (object) { + return kHeadersList in object +} + function fill(headers, object) { - if (kHeadersList in object) { + if (isHeaders(object)) { // Object is instance of Headers headers[kHeadersList] = ArrayPrototypeSlice(object[kHeadersList]); } else if (ArrayIsArray(object)) { @@ -87,7 +92,7 @@ function fill(headers, object) { // Array of arrays for (let i = 0; i < object.length; i++) { if (object[i].length !== 2) { - throw new ERR_INVALID_ARG_VALUE('init', header, + throw new ERR_INVALID_ARG_VALUE('init', object[i], 'is not of length 2'); } headers.append(object[i][0], object[i][1]); @@ -123,12 +128,22 @@ function fill(headers, object) { class Headers { constructor(init = {}) { + // fail silently on primitives + const typeofInit = typeof init + if (typeofInit === 'number' + || typeofInit === 'string' + || typeofInit === 'boolean' + || typeofInit === 'function' ) return validateObject(init, 'init', { allowArray: true }); this[kHeadersList] = []; fill(this, init); } append(name, value) { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + const normalizedName = normalizeAndValidateHeaderName(name); const normalizedValue = normalizeAndValidateHeaderValue(name, value); @@ -137,21 +152,29 @@ class Headers { if (this[kHeadersList][index] === normalizedName) { this[kHeadersList][index + 1] += `, ${normalizedValue}`; } else { - this[kHeadersList].splice(index, 0, normalizedName, normalizedValue); + ArrayPrototypeSplice(this[kHeadersList], index, 0, normalizedName, normalizedValue); } } delete(name) { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + const normalizedName = normalizeAndValidateHeaderName(name); const index = binarySearch(this[kHeadersList], normalizedName); if (this[kHeadersList][index] === normalizedName) { - this[kHeadersList].splice(index, 2); + ArrayPrototypeSplice(this[kHeadersList], index, 2); } } get(name) { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + const normalizedName = normalizeAndValidateHeaderName(name); const index = binarySearch(this[kHeadersList], normalizedName); @@ -164,6 +187,10 @@ class Headers { } has(name) { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + const normalizedName = normalizeAndValidateHeaderName(name); const index = binarySearch(this[kHeadersList], normalizedName); @@ -172,6 +199,10 @@ class Headers { } set(name, value) { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + const normalizedName = normalizeAndValidateHeaderName(name); const normalizedValue = normalizeAndValidateHeaderValue(name, value); @@ -179,29 +210,45 @@ class Headers { if (this[kHeadersList][index] === normalizedName) { this[kHeadersList][index + 1] = normalizedValue; } else { - this[kHeadersList].splice(index, 0, normalizedName, normalizedValue); + ArrayPrototypeSplice(this[kHeadersList], index, 0, normalizedName, normalizedValue); } } * keys() { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + for (let index = 0; index < this[kHeadersList].length; index += 2) { yield this[kHeadersList][index]; } } * values() { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + for (let index = 1; index < this[kHeadersList].length; index += 2) { yield this[kHeadersList][index]; } } * entries() { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + for (let index = 0; index < this[kHeadersList].length; index += 2) { yield [this[kHeadersList][index], this[kHeadersList][index + 1]]; } } forEach(callback, thisArg) { + if (!isHeaders(this)) { + throw new ERR_INVALID_THIS('Headers'); + } + for (let index = 0; index < this[kHeadersList].length; index += 2) { callback.call( thisArg, @@ -215,6 +262,18 @@ class Headers { Headers.prototype[SymbolIterator] = Headers.prototype.entries; +ObjectDefineProperties(Headers.prototype, { + append: { enumerable: true }, + delete: { enumerable: true }, + get: { enumerable: true }, + has: { enumerable: true }, + set: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true }, + forEach: { enumerable: true }, +}); + module.exports = { Headers, binarySearch, diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index e91b2e7ebc4043..9eb3cc8157becb 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -98,6 +98,7 @@ const { function() {}, 1, 'test', + true ].forEach((arg) => new Headers(arg)); } From 92b95193080927e6323a4d2389bb5dc93d193347 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 22 Jul 2021 14:10:51 -0600 Subject: [PATCH 27/34] export api --- doc/api/index.md | 1 + lib/fetch.js | 15 +++++++++++++++ lib/internal/fetch/headers.js | 24 ++++++++++++++---------- test/parallel/test-headers.js | 2 +- 4 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 lib/fetch.js diff --git a/doc/api/index.md b/doc/api/index.md index f45aa17ecc451a..28fcbe80eb12f5 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -29,6 +29,7 @@ * [Domain](domain.md) * [Errors](errors.md) * [Events](events.md) +* [Fetch](fetch.md) * [File system](fs.md) * [Globals](globals.md) * [HTTP](http.md) diff --git a/lib/fetch.js b/lib/fetch.js new file mode 100644 index 00000000000000..9e35dfa736b1dd --- /dev/null +++ b/lib/fetch.js @@ -0,0 +1,15 @@ +'use strict'; + +const { + emitExperimentalWarning, +} = require('internal/util'); + +emitExperimentalWarning('fetch/headers'); + +const { + Headers +} = require('internal/fetch/headers'); + +module.exports = { + Headers +}; diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 00671d76ff8ee9..825fce450af632 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -4,12 +4,14 @@ const { codes: { ERR_INVALID_ARG_VALUE, ERR_INVALID_HTTP_TOKEN, + ERR_INVALID_THIS, ERR_HTTP_INVALID_HEADER_VALUE } } = require('internal/errors'); const { ArrayIsArray, + ArrayPrototypeSlice, ArrayPrototypeSplice, MathFloor, ObjectEntries, @@ -78,8 +80,8 @@ function normalizeAndValidateHeaderValue(name, value) { return normalizedHeaderValue; } -function isHeaders (object) { - return kHeadersList in object +function isHeaders(object) { + return kHeadersList in object; } function fill(headers, object) { @@ -128,12 +130,12 @@ function fill(headers, object) { class Headers { constructor(init = {}) { - // fail silently on primitives - const typeofInit = typeof init - if (typeofInit === 'number' - || typeofInit === 'string' - || typeofInit === 'boolean' - || typeofInit === 'function' ) return + // Fail silently on primitives + const typeofInit = typeof init; + if (typeofInit === 'number' || + typeofInit === 'string' || + typeofInit === 'boolean' || + typeofInit === 'function') return; validateObject(init, 'init', { allowArray: true }); this[kHeadersList] = []; fill(this, init); @@ -152,7 +154,8 @@ class Headers { if (this[kHeadersList][index] === normalizedName) { this[kHeadersList][index + 1] += `, ${normalizedValue}`; } else { - ArrayPrototypeSplice(this[kHeadersList], index, 0, normalizedName, normalizedValue); + ArrayPrototypeSplice( + this[kHeadersList], index, 0, normalizedName, normalizedValue); } } @@ -210,7 +213,8 @@ class Headers { if (this[kHeadersList][index] === normalizedName) { this[kHeadersList][index + 1] = normalizedValue; } else { - ArrayPrototypeSplice(this[kHeadersList], index, 0, normalizedName, normalizedValue); + ArrayPrototypeSplice( + this[kHeadersList], index, 0, normalizedName, normalizedValue); } } diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index 9eb3cc8157becb..ec948f1a8b85ca 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -98,7 +98,7 @@ const { function() {}, 1, 'test', - true + true, ].forEach((arg) => new Headers(arg)); } From 6f212c065cfd0af9053ad958299264e017943c47 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Thu, 22 Jul 2021 14:47:56 -0600 Subject: [PATCH 28/34] add inspect and docs --- doc/api/fetch.md | 18 ++++++++++-------- lib/internal/fetch/headers.js | 6 ++++++ tools/doc/type-parser.mjs | 2 ++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/doc/api/fetch.md b/doc/api/fetch.md index 1bf2bf8ae10a7f..b03c40972172e1 100644 --- a/doc/api/fetch.md +++ b/doc/api/fetch.md @@ -11,8 +11,9 @@ Represents a WHATWG Fetch Spec ### `new Headers([init])` -* `init` {Headers | Iterable<\[string, string]> | string\[] | Record} Initial header list to be cloned into the new instance. +* `init` {Headers | Iterable | string\[] | string\[]\[] | Object} Initial header list to be cloned into the new instance. + +Per spec, other JS primordials can be passed and will fail silently. ```js new Headers(); @@ -32,7 +33,6 @@ new Headers(headers); * `name` {string} * `value` {string} -* Returns: {void} Non-destructive operation for adding header entries. When called multiple times with the same _name_, the values will be collected in a list and returned @@ -126,7 +126,7 @@ headers.get('foobar'); // -> 'buzz' ### `headers.values()` -* Returns: {IteratableIterator} +* Returns: {Iterator} Yields a list of header values combined and sorted by their respective keys. @@ -149,7 +149,7 @@ for (const value of headers.values()) { ### `headers.keys()` -Returns: {IteratableIterator} +Returns: {Iterator} Yields a sorted list of header keys. @@ -172,11 +172,13 @@ for (const name of headers.keys()) { ### `headers.forEach(callback, [thisArg])` -* `callback` {(value: string, key: string, iterable: Headers) => void} +* `callback` {Function} * `thisArg` {any} (optional) A Headers class can be iterated using `.forEach(callback, [thisArg])`. +The callback function is passed three arguments, `value: string`, `key: string`, and `iterable: Headers`. + Optionally a `thisArg` can be passed which will be assigned to the `this` context of callback. @@ -194,7 +196,7 @@ headers.forEach(function(value, key, headers) { ### `headers[Symbol.iterator]` -* Returns: {Iterator<\[string, string]>} +* Returns: {Iterator} A Headers class instance is iterable. It yields each of its entries as a pair where the first value is the entry _name_ and the second value is the header @@ -219,7 +221,7 @@ for (const [name, value] of headers) { ### `headers.entries()` -* Returns: {IteratableIterator<\[string, string]>} +* Returns: {Iterator} Yields a list of headers sorted and combined by key. diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 825fce450af632..f94e5d2ce04520 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -30,6 +30,8 @@ const { validateHeaderName, validateHeaderValue } = require('_http_outgoing'); const { Buffer } = require('buffer'); +const { inspect } = require('internal/util/inspect'); + const kHeadersList = Symbol('headers list'); /** @@ -262,6 +264,10 @@ class Headers { ); } } + + [inspect.custom] () { + return this[kHeadersList] + } } Headers.prototype[SymbolIterator] = Headers.prototype.entries; diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index 481a35ca330c80..47cd9162731035 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -132,6 +132,8 @@ const customTypesMap = { 'Event': 'events.html#events_class_event', 'EventListener': 'events.html#events_event_listener', + 'Headers': 'fetch.html#fetch_class_headers', + 'FileHandle': 'fs.html#fs_class_filehandle', 'fs.Dir': 'fs.html#fs_class_fs_dir', 'fs.Dirent': 'fs.html#fs_class_fs_dirent', From 6f06698d590041dc1fb041d451e25cd3c5ca57b1 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 26 Jul 2021 10:04:16 -0600 Subject: [PATCH 29/34] Update lib/fetch.js Co-authored-by: Antoine du Hamel --- lib/fetch.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/fetch.js b/lib/fetch.js index 9e35dfa736b1dd..a2b6da750a5fc7 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -6,9 +6,7 @@ const { emitExperimentalWarning('fetch/headers'); -const { - Headers -} = require('internal/fetch/headers'); +const { Headers } = require('internal/fetch/headers'); module.exports = { Headers From ae223ca977bbce77fdf18a909b8de0ccb43a4f87 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 26 Jul 2021 10:04:42 -0600 Subject: [PATCH 30/34] Update lib/fetch.js Co-authored-by: Antoine du Hamel --- lib/fetch.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/fetch.js b/lib/fetch.js index a2b6da750a5fc7..77d8d1497f413c 100644 --- a/lib/fetch.js +++ b/lib/fetch.js @@ -8,6 +8,4 @@ emitExperimentalWarning('fetch/headers'); const { Headers } = require('internal/fetch/headers'); -module.exports = { - Headers -}; +module.exports = { Headers }; From 1ef66a03ffb4a15d44c14ec3fa64ad1f50f729a0 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 26 Jul 2021 10:05:05 -0600 Subject: [PATCH 31/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index f94e5d2ce04520..6ee794aa3706d6 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -5,8 +5,8 @@ const { ERR_INVALID_ARG_VALUE, ERR_INVALID_HTTP_TOKEN, ERR_INVALID_THIS, - ERR_HTTP_INVALID_HEADER_VALUE - } + ERR_HTTP_INVALID_HEADER_VALUE, + }, } = require('internal/errors'); const { From a9a7b4da77427eab794c7aa7f942fdead289c10b Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 26 Jul 2021 10:05:16 -0600 Subject: [PATCH 32/34] Update lib/internal/fetch/headers.js Co-authored-by: Antoine du Hamel --- lib/internal/fetch/headers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 6ee794aa3706d6..4f899fd4377b27 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -14,13 +14,13 @@ const { ArrayPrototypeSlice, ArrayPrototypeSplice, MathFloor, + ObjectDefineProperties, ObjectEntries, RegExpPrototypeSymbolReplace, StringPrototypeLocaleCompare, StringPrototypeToLocaleLowerCase, Symbol, SymbolIterator, - ObjectDefineProperties } = primordials; const { validateObject } = require('internal/validators'); From 3b902a1db23e41953b75ea7e4d64cf0719096de2 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 26 Jul 2021 10:59:39 -0600 Subject: [PATCH 33/34] incorporate review changes --- lib/internal/fetch/headers.js | 36 +++++++++----------- lib/internal/util.js | 6 +++- lib/internal/webstreams/compression.js | 6 ++-- lib/internal/webstreams/encoding.js | 7 ++-- lib/internal/webstreams/queuingstrategies.js | 2 +- lib/internal/webstreams/readablestream.js | 2 +- lib/internal/webstreams/transformstream.js | 2 +- lib/internal/webstreams/util.js | 4 --- lib/internal/webstreams/writablestream.js | 2 +- test/parallel/test-headers.js | 24 ++----------- 10 files changed, 33 insertions(+), 58 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 4f899fd4377b27..05f2d9153f6ff6 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -13,6 +13,7 @@ const { ArrayIsArray, ArrayPrototypeSlice, ArrayPrototypeSplice, + FunctionPrototypeCall, MathFloor, ObjectDefineProperties, ObjectEntries, @@ -25,13 +26,15 @@ const { const { validateObject } = require('internal/validators'); const { isBoxedPrimitive } = require('internal/util/types'); +const { + customInspectSymbol, + kEnumerableProperty, +} = require('internal/util') const { validateHeaderName, validateHeaderValue } = require('_http_outgoing'); const { Buffer } = require('buffer'); -const { inspect } = require('internal/util/inspect'); - const kHeadersList = Symbol('headers list'); /** @@ -132,12 +135,6 @@ function fill(headers, object) { class Headers { constructor(init = {}) { - // Fail silently on primitives - const typeofInit = typeof init; - if (typeofInit === 'number' || - typeofInit === 'string' || - typeofInit === 'boolean' || - typeofInit === 'function') return; validateObject(init, 'init', { allowArray: true }); this[kHeadersList] = []; fill(this, init); @@ -256,7 +253,8 @@ class Headers { } for (let index = 0; index < this[kHeadersList].length; index += 2) { - callback.call( + FunctionPrototypeCall( + callback, thisArg, this[kHeadersList][index + 1], this[kHeadersList][index], @@ -265,7 +263,7 @@ class Headers { } } - [inspect.custom] () { + [customInspectSymbol] () { return this[kHeadersList] } } @@ -273,15 +271,15 @@ class Headers { Headers.prototype[SymbolIterator] = Headers.prototype.entries; ObjectDefineProperties(Headers.prototype, { - append: { enumerable: true }, - delete: { enumerable: true }, - get: { enumerable: true }, - has: { enumerable: true }, - set: { enumerable: true }, - keys: { enumerable: true }, - values: { enumerable: true }, - entries: { enumerable: true }, - forEach: { enumerable: true }, + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + keys: kEnumerableProperty, + values: kEnumerableProperty, + entries: kEnumerableProperty, + forEach: kEnumerableProperty, }); module.exports = { diff --git a/lib/internal/util.js b/lib/internal/util.js index 101fbec67775b0..e37b404511de8f 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -449,6 +449,9 @@ const lazyDOMException = hideStackFrames((message, name) => { return new DOMException(message, name); }); +const kEnumerableProperty = ObjectCreate(null); +kEnumerableProperty.enumerable = true; + module.exports = { assertCrypto, cachedResult, @@ -483,5 +486,6 @@ module.exports = { // Used by the buffer module to capture an internal reference to the // default isEncoding implementation, just in case userland overrides it. kIsEncodingSymbol: Symbol('kIsEncodingSymbol'), - kVmBreakFirstLineSymbol: Symbol('kVmBreakFirstLineSymbol') + kVmBreakFirstLineSymbol: Symbol('kVmBreakFirstLineSymbol'), + kEnumerableProperty, }; diff --git a/lib/internal/webstreams/compression.js b/lib/internal/webstreams/compression.js index 692f64af005af9..2ba8e53fa28e73 100644 --- a/lib/internal/webstreams/compression.js +++ b/lib/internal/webstreams/compression.js @@ -16,13 +16,11 @@ const { newReadableWritablePairFromDuplex, } = require('internal/webstreams/adapters'); -const { - customInspect, - kEnumerableProperty, -} = require('internal/webstreams/util'); +const { customInspect } = require('internal/webstreams/util'); const { customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); let zlib; diff --git a/lib/internal/webstreams/encoding.js b/lib/internal/webstreams/encoding.js index 5af59bc9f4a502..809bdaa8f9a69d 100644 --- a/lib/internal/webstreams/encoding.js +++ b/lib/internal/webstreams/encoding.js @@ -14,10 +14,9 @@ const { TransformStream, } = require('internal/webstreams/transformstream'); -const { - customInspect, - kEnumerableProperty, -} = require('internal/webstreams/util'); +const { customInspect } = require('internal/webstreams/util'); + +const { kEnumerableProperty } = require('internal/util'); const { codes: { diff --git a/lib/internal/webstreams/queuingstrategies.js b/lib/internal/webstreams/queuingstrategies.js index 2db51b92a7f502..b739d87d8b73b0 100644 --- a/lib/internal/webstreams/queuingstrategies.js +++ b/lib/internal/webstreams/queuingstrategies.js @@ -14,6 +14,7 @@ const { const { customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); const { @@ -21,7 +22,6 @@ const { isBrandCheck, kType, kState, - kEnumerableProperty, } = require('internal/webstreams/util'); const { diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index 62da68ff28086f..46dcbb844fefb8 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -50,6 +50,7 @@ const { const { createDeferredPromise, customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); const { @@ -103,7 +104,6 @@ const { nonOpStart, kType, kState, - kEnumerableProperty, } = require('internal/webstreams/util'); const { diff --git a/lib/internal/webstreams/transformstream.js b/lib/internal/webstreams/transformstream.js index b4e690daa98c4a..cea127515d4f97 100644 --- a/lib/internal/webstreams/transformstream.js +++ b/lib/internal/webstreams/transformstream.js @@ -27,6 +27,7 @@ const { const { createDeferredPromise, customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); const { @@ -45,7 +46,6 @@ const { nonOpFlush, kType, kState, - kEnumerableProperty, } = require('internal/webstreams/util'); const { diff --git a/lib/internal/webstreams/util.js b/lib/internal/webstreams/util.js index 58f191cf07ff5a..305064ba05a490 100644 --- a/lib/internal/webstreams/util.js +++ b/lib/internal/webstreams/util.js @@ -207,9 +207,6 @@ function lazyTransfer() { return transfer; } -const kEnumerableProperty = ObjectCreate(null); -kEnumerableProperty.enumerable = true; - module.exports = { ArrayBufferViewGetBuffer, ArrayBufferViewGetByteLength, @@ -237,5 +234,4 @@ module.exports = { nonOpWrite, kType, kState, - kEnumerableProperty, }; diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index dba7560c549a9a..d8a3d1d6cb14cc 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -33,6 +33,7 @@ const { const { createDeferredPromise, customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); const { @@ -64,7 +65,6 @@ const { nonOpWrite, kType, kState, - kEnumerableProperty, } = require('internal/webstreams/util'); const { diff --git a/test/parallel/test-headers.js b/test/parallel/test-headers.js index ec948f1a8b85ca..0ebd34675fc95d 100644 --- a/test/parallel/test-headers.js +++ b/test/parallel/test-headers.js @@ -6,9 +6,9 @@ const assert = require('assert'); // Flags: --expose-internals const { - Headers, + binarySearch, kHeadersList, - binarySearch + Headers, } = require('internal/fetch/headers'); { @@ -82,26 +82,6 @@ const { assert.strictEqual(headers[kHeadersList].length, 4); } -{ - // Init fails silently when initialized with BoxedPrimitives - [ - new Number(), - new Boolean(), - new String(), - ].forEach((arg) => new Headers(arg)); -} - -{ - // Init fails silently if function or primitive is passed - [ - new Function(), - function() {}, - 1, - 'test', - true, - ].forEach((arg) => new Headers(arg)); -} - { // append const headers = new Headers(); From cd38842d394918e3bc8ac93dbc509d6c1c3fd054 Mon Sep 17 00:00:00 2001 From: Ethan Arrowood Date: Mon, 26 Jul 2021 11:05:41 -0600 Subject: [PATCH 34/34] lint fixes --- lib/internal/fetch/headers.js | 6 +++--- lib/internal/webstreams/encoding.js | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/internal/fetch/headers.js b/lib/internal/fetch/headers.js index 05f2d9153f6ff6..e6b0a8eb9515e1 100644 --- a/lib/internal/fetch/headers.js +++ b/lib/internal/fetch/headers.js @@ -29,7 +29,7 @@ const { isBoxedPrimitive } = require('internal/util/types'); const { customInspectSymbol, kEnumerableProperty, -} = require('internal/util') +} = require('internal/util'); const { validateHeaderName, validateHeaderValue } = require('_http_outgoing'); @@ -263,8 +263,8 @@ class Headers { } } - [customInspectSymbol] () { - return this[kHeadersList] + [customInspectSymbol]() { + return this[kHeadersList]; } } diff --git a/lib/internal/webstreams/encoding.js b/lib/internal/webstreams/encoding.js index 809bdaa8f9a69d..9cc76fae2ce10d 100644 --- a/lib/internal/webstreams/encoding.js +++ b/lib/internal/webstreams/encoding.js @@ -16,8 +16,6 @@ const { const { customInspect } = require('internal/webstreams/util'); -const { kEnumerableProperty } = require('internal/util'); - const { codes: { ERR_INVALID_THIS, @@ -25,7 +23,8 @@ const { } = require('internal/errors'); const { - customInspectSymbol: kInspect + customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); const kHandle = Symbol('kHandle');