123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- 'use strict'
- const invalidTokenRegex = /[^^_`a-zA-Z\-0-9!#$%&'*+.|~]/
- const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/
-
- const validateName = name => {
- name = `${name}`
- if (invalidTokenRegex.test(name) || name === '') {
- throw new TypeError(`${name} is not a legal HTTP header name`)
- }
- }
-
- const validateValue = value => {
- value = `${value}`
- if (invalidHeaderCharRegex.test(value)) {
- throw new TypeError(`${value} is not a legal HTTP header value`)
- }
- }
-
- const find = (map, name) => {
- name = name.toLowerCase()
- for (const key in map) {
- if (key.toLowerCase() === name) {
- return key
- }
- }
- return undefined
- }
-
- const MAP = Symbol('map')
- class Headers {
- constructor (init = undefined) {
- this[MAP] = Object.create(null)
- if (init instanceof Headers) {
- const rawHeaders = init.raw()
- const headerNames = Object.keys(rawHeaders)
- for (const headerName of headerNames) {
- for (const value of rawHeaders[headerName]) {
- this.append(headerName, value)
- }
- }
- return
- }
-
- // no-op
- if (init === undefined || init === null) {
- return
- }
-
- if (typeof init === 'object') {
- const method = init[Symbol.iterator]
- if (method !== null && method !== undefined) {
- if (typeof method !== 'function') {
- throw new TypeError('Header pairs must be iterable')
- }
-
- // sequence<sequence<ByteString>>
- // Note: per spec we have to first exhaust the lists then process them
- const pairs = []
- for (const pair of init) {
- if (typeof pair !== 'object' ||
- typeof pair[Symbol.iterator] !== 'function') {
- throw new TypeError('Each header pair must be iterable')
- }
- const arrPair = Array.from(pair)
- if (arrPair.length !== 2) {
- throw new TypeError('Each header pair must be a name/value tuple')
- }
- pairs.push(arrPair)
- }
-
- for (const pair of pairs) {
- this.append(pair[0], pair[1])
- }
- } else {
- // record<ByteString, ByteString>
- for (const key of Object.keys(init)) {
- this.append(key, init[key])
- }
- }
- } else {
- throw new TypeError('Provided initializer must be an object')
- }
- }
-
- get (name) {
- name = `${name}`
- validateName(name)
- const key = find(this[MAP], name)
- if (key === undefined) {
- return null
- }
-
- return this[MAP][key].join(', ')
- }
-
- forEach (callback, thisArg = undefined) {
- let pairs = getHeaders(this)
- for (let i = 0; i < pairs.length; i++) {
- const [name, value] = pairs[i]
- callback.call(thisArg, value, name, this)
- // refresh in case the callback added more headers
- pairs = getHeaders(this)
- }
- }
-
- set (name, value) {
- name = `${name}`
- value = `${value}`
- validateName(name)
- validateValue(value)
- const key = find(this[MAP], name)
- this[MAP][key !== undefined ? key : name] = [value]
- }
-
- append (name, value) {
- name = `${name}`
- value = `${value}`
- validateName(name)
- validateValue(value)
- const key = find(this[MAP], name)
- if (key !== undefined) {
- this[MAP][key].push(value)
- } else {
- this[MAP][name] = [value]
- }
- }
-
- has (name) {
- name = `${name}`
- validateName(name)
- return find(this[MAP], name) !== undefined
- }
-
- delete (name) {
- name = `${name}`
- validateName(name)
- const key = find(this[MAP], name)
- if (key !== undefined) {
- delete this[MAP][key]
- }
- }
-
- raw () {
- return this[MAP]
- }
-
- keys () {
- return new HeadersIterator(this, 'key')
- }
-
- values () {
- return new HeadersIterator(this, 'value')
- }
-
- [Symbol.iterator] () {
- return new HeadersIterator(this, 'key+value')
- }
-
- entries () {
- return new HeadersIterator(this, 'key+value')
- }
-
- get [Symbol.toStringTag] () {
- return 'Headers'
- }
-
- static exportNodeCompatibleHeaders (headers) {
- const obj = Object.assign(Object.create(null), headers[MAP])
-
- // http.request() only supports string as Host header. This hack makes
- // specifying custom Host header possible.
- const hostHeaderKey = find(headers[MAP], 'Host')
- if (hostHeaderKey !== undefined) {
- obj[hostHeaderKey] = obj[hostHeaderKey][0]
- }
-
- return obj
- }
-
- static createHeadersLenient (obj) {
- const headers = new Headers()
- for (const name of Object.keys(obj)) {
- if (invalidTokenRegex.test(name)) {
- continue
- }
-
- if (Array.isArray(obj[name])) {
- for (const val of obj[name]) {
- if (invalidHeaderCharRegex.test(val)) {
- continue
- }
-
- if (headers[MAP][name] === undefined) {
- headers[MAP][name] = [val]
- } else {
- headers[MAP][name].push(val)
- }
- }
- } else if (!invalidHeaderCharRegex.test(obj[name])) {
- headers[MAP][name] = [obj[name]]
- }
- }
- return headers
- }
- }
-
- Object.defineProperties(Headers.prototype, {
- get: { enumerable: true },
- forEach: { enumerable: true },
- set: { enumerable: true },
- append: { enumerable: true },
- has: { enumerable: true },
- delete: { enumerable: true },
- keys: { enumerable: true },
- values: { enumerable: true },
- entries: { enumerable: true },
- })
-
- const getHeaders = (headers, kind = 'key+value') =>
- Object.keys(headers[MAP]).sort().map(
- kind === 'key' ? k => k.toLowerCase()
- : kind === 'value' ? k => headers[MAP][k].join(', ')
- : k => [k.toLowerCase(), headers[MAP][k].join(', ')]
- )
-
- const INTERNAL = Symbol('internal')
-
- class HeadersIterator {
- constructor (target, kind) {
- this[INTERNAL] = {
- target,
- kind,
- index: 0,
- }
- }
-
- get [Symbol.toStringTag] () {
- return 'HeadersIterator'
- }
-
- next () {
- /* istanbul ignore if: should be impossible */
- if (!this || Object.getPrototypeOf(this) !== HeadersIterator.prototype) {
- throw new TypeError('Value of `this` is not a HeadersIterator')
- }
-
- const { target, kind, index } = this[INTERNAL]
- const values = getHeaders(target, kind)
- const len = values.length
- if (index >= len) {
- return {
- value: undefined,
- done: true,
- }
- }
-
- this[INTERNAL].index++
-
- return { value: values[index], done: false }
- }
- }
-
- // manually extend because 'extends' requires a ctor
- Object.setPrototypeOf(HeadersIterator.prototype,
- Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())))
-
- module.exports = Headers
|