diff --git a/lib/web/fetch/data-url.js b/lib/web/fetch/data-url.js index 2996feca92a..5d2b310aa4a 100644 --- a/lib/web/fetch/data-url.js +++ b/lib/web/fetch/data-url.js @@ -136,8 +136,8 @@ function URLSerializer (url, excludeFragment = false) { return serialized } -// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points /** + * @see https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points * @param {(char: string) => boolean} condition * @param {string} input * @param {{ position: number }} position diff --git a/lib/web/fetch/headers.js b/lib/web/fetch/headers.js index 44b1f52bb3f..e3bc2eb26ca 100644 --- a/lib/web/fetch/headers.js +++ b/lib/web/fetch/headers.js @@ -4,10 +4,13 @@ const { kConstruct } = require('../../core/symbols') const { kEnumerableProperty } = require('../../core/util') +const { forbiddenMethods } = require('./constants') const { + gettingDecodingSplitting, iteratorMixin, isValidHeaderName, - isValidHeaderValue + isValidHeaderValue, + isomorphicEncode } = require('./util') const { webidl } = require('./webidl') const assert = require('node:assert') @@ -79,6 +82,75 @@ function fill (headers, object) { } } +/** + * @see https://fetch.spec.whatwg.org/#forbidden-request-header + * @param {string} name + * @param {string} value + * @returns {boolean} Returns true if the header is forbidden. + */ +function isForbiddenRequestHeader (name, value) { + // 1. If name is a byte-case-insensitive match of one of: + if ( + [ + 'accept-charset', + 'accept-encoding', + 'access-control-request-headers', + 'access-control-request-method', + 'connection', + 'content-length', + 'cookie', + 'cookie2', + 'date', + 'dnt', + 'expect', + 'host', + 'keep-alive', + 'origin', + 'referer', + 'set-cookie', + 'te', + 'trailer', + 'transfer-encoding', + 'upgrade', + 'via' + ].includes(name.toLowerCase()) + ) { + // then return true. + return true + } + + // 2. If name when byte-lowercased starts with `proxy-` or `sec-`, + // then return true. + if ( + name.toLowerCase().startsWith('proxy-') || + name.toLowerCase().startsWith('sec-') + ) { + return true + } + + // 3. If name is a byte-case-insensitive match for one of + if ( + ['x-http-method', 'x-http-method-override', 'x-method-override'].includes( + name.toLowerCase() + ) + ) { + // 1. Let parsedValues be the result of getting, decoding, and + // splitting value. + const parsedValues = gettingDecodingSplitting(value) + + // 2. For each method of parsedValues: + for (const method of parsedValues) { + // if the isomorphic encoding of method is a forbidden method, + // then return true. + if (forbiddenMethods.includes(isomorphicEncode(method).toUpperCase())) { + return true + } + } + } + + return false +} + /** * @see https://fetch.spec.whatwg.org/#concept-headers-append * @param {Headers} headers @@ -89,8 +161,9 @@ function appendHeader (headers, name, value) { // 1. Normalize value. value = headerValueNormalize(value) - // 2. If name is not a header name or value is not a - // header value, then throw a TypeError. + // 2. If validating (name, value) for headers returns false, then return. + // 1. If name is not a header name or value is not a header value, + // then throw a TypeError. if (!isValidHeaderName(name)) { throw webidl.errors.invalidArgument({ prefix: 'Headers.append', @@ -105,18 +178,23 @@ function appendHeader (headers, name, value) { }) } - // 3. If headers’s guard is "immutable", then throw a TypeError. - // 4. Otherwise, if headers’s guard is "request" and name is a - // forbidden header name, return. - // 5. Otherwise, if headers’s guard is "request-no-cors": - // TODO - // Note: undici does not implement forbidden header names + // 2. If headers’s guard is "immutable", then throw a TypeError. if (getHeadersGuard(headers) === 'immutable') { throw new TypeError('immutable') } - // 6. Otherwise, if headers’s guard is "response" and name is a - // forbidden response-header name, return. + // 3. If headers’s guard is "request" and (name, value) is a forbidden + // request-header, then return. + if ( + getHeadersGuard(headers) === 'request' && + isForbiddenRequestHeader(name, value) + ) { + return + } + + // 4. If headers’s guard is "response" and name is a forbidden + // response-header name, then return. + // TODO // 7. Append (name, value) to headers’s header list. return getHeadersList(headers).append(name, value, false) diff --git a/lib/web/fetch/util.js b/lib/web/fetch/util.js index daa2483c943..c47b99b711c 100644 --- a/lib/web/fetch/util.js +++ b/lib/web/fetch/util.js @@ -1448,6 +1448,7 @@ function extractMimeType (headers) { /** * @see https://fetch.spec.whatwg.org/#header-value-get-decode-and-split * @param {string|null} value + * @returns {string[]} Returns the parsed values. */ function gettingDecodingSplitting (value) { // 1. Let input be the result of isomorphic decoding value. @@ -1622,6 +1623,7 @@ module.exports = { parseMetadata, createInflate, extractMimeType, + gettingDecodingSplitting, getDecodeSplit, utf8DecodeBytes, environmentSettingsObject diff --git a/test/wpt/status/fetch.status.json b/test/wpt/status/fetch.status.json index 081b693493c..187db016378 100644 --- a/test/wpt/status/fetch.status.json +++ b/test/wpt/status/fetch.status.json @@ -52,10 +52,6 @@ "origin-when-cross-origin policy on a same-origin URL" ] }, - "request-forbidden-headers.any.js": { - "note": "undici doesn't filter headers", - "skip": true - }, "request-headers.any.js": { "fail": [ "Fetch with Chicken",