Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fetch: implement forbidden request header #3695

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/web/fetch/data-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 89 additions & 11 deletions lib/web/fetch/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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
Expand All @@ -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',
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions lib/web/fetch/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -1622,6 +1623,7 @@ module.exports = {
parseMetadata,
createInflate,
extractMimeType,
gettingDecodingSplitting,
getDecodeSplit,
utf8DecodeBytes,
environmentSettingsObject
Expand Down
4 changes: 0 additions & 4 deletions test/wpt/status/fetch.status.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading