diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1192f551..b9ddc412c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,12 +2,12 @@ name: CI on: push: - branches: [ master ] + branches: [master] pull_request: paths: - - '**.js' - - 'package.json' - - '.github/workflows/ci.yml' + - "**.js" + - "package.json" + - ".github/workflows/ci.yml" jobs: test: @@ -41,7 +41,6 @@ jobs: # upload coverage only once - name: Coveralls uses: coverallsapp/github-action@master - if: matrix.node == '14.x' && matrix.os == 'ubuntu-latest' + if: matrix.node == '12.x' && matrix.os == 'ubuntu-latest' with: github-token: ${{ secrets.GITHUB_TOKEN }} - diff --git a/src/headers.js b/src/headers.js index da1934fd8..5091152ec 100644 --- a/src/headers.js +++ b/src/headers.js @@ -5,23 +5,27 @@ */ import {types} from 'util'; +import http from 'http'; -const invalidTokenRegex = /[^`\-\w!#$%&'*+.|~]/; -const invalidHeaderCharRegex = /[^\t\u0020-\u007E\u0080-\u00FF]/; - -function validateName(name) { - name = String(name); - if (invalidTokenRegex.test(name) || name === '') { - throw new TypeError(`'${name}' is not a legal HTTP header name`); - } -} +const validateHeaderName = typeof http.validateHeaderName === 'function' ? + http.validateHeaderName : + name => { + if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) { + const err = new TypeError(`Header name must be a valid HTTP token [${name}]`); + Object.defineProperty(err, 'code', {value: 'ERR_INVALID_HTTP_TOKEN'}); + throw err; + } + }; -function validateValue(value) { - value = String(value); - if (invalidHeaderCharRegex.test(value)) { - throw new TypeError(`'${value}' is not a legal HTTP header value`); - } -} +const validateHeaderValue = typeof http.validateHeaderValue === 'function' ? + http.validateHeaderValue : + (name, value) => { + if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) { + const err = new TypeError(`Invalid character in header content ["${name}"]`); + Object.defineProperty(err, 'code', {value: 'ERR_INVALID_CHAR'}); + throw err; + } + }; /** * @typedef {Headers | Record | Iterable | Iterable[]} HeadersInit @@ -91,9 +95,9 @@ export default class Headers extends URLSearchParams { result = result.length > 0 ? result.map(([name, value]) => { - validateName(name); - validateValue(value); - return [String(name).toLowerCase(), value]; + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return [String(name).toLowerCase(), String(value)]; }) : undefined; @@ -107,12 +111,12 @@ export default class Headers extends URLSearchParams { case 'append': case 'set': return (name, value) => { - validateName(name); - validateValue(value); + validateHeaderName(name); + validateHeaderValue(name, String(value)); return URLSearchParams.prototype[p].call( receiver, String(name).toLowerCase(), - value + String(value) ); }; @@ -120,7 +124,7 @@ export default class Headers extends URLSearchParams { case 'has': case 'getAll': return name => { - validateName(name); + validateHeaderName(name); return URLSearchParams.prototype[p].call( receiver, String(name).toLowerCase() @@ -142,7 +146,7 @@ export default class Headers extends URLSearchParams { } get [Symbol.toStringTag]() { - return 'Headers'; + return this.constructor.name; } toString() { @@ -247,7 +251,15 @@ export function fromRawHeaders(headers = []) { return result; }, []) - .filter(([name, value]) => !(invalidTokenRegex.test(name) || invalidHeaderCharRegex.test(value))) + .filter(([name, value]) => { + try { + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return true; + } catch { + return false; + } + }) ); }