[6a3a178] | 1 | 'use strict'
|
---|
| 2 | const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/
|
---|
| 3 | const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/
|
---|
| 4 |
|
---|
| 5 | const validateName = name => {
|
---|
| 6 | name = `${name}`
|
---|
| 7 | if (invalidTokenRegex.test(name) || name === '')
|
---|
| 8 | throw new TypeError(`${name} is not a legal HTTP header name`)
|
---|
| 9 | }
|
---|
| 10 |
|
---|
| 11 | const validateValue = value => {
|
---|
| 12 | value = `${value}`
|
---|
| 13 | if (invalidHeaderCharRegex.test(value))
|
---|
| 14 | throw new TypeError(`${value} is not a legal HTTP header value`)
|
---|
| 15 | }
|
---|
| 16 |
|
---|
| 17 | const find = (map, name) => {
|
---|
| 18 | name = name.toLowerCase()
|
---|
| 19 | for (const key in map) {
|
---|
| 20 | if (key.toLowerCase() === name)
|
---|
| 21 | return key
|
---|
| 22 | }
|
---|
| 23 | return undefined
|
---|
| 24 | }
|
---|
| 25 |
|
---|
| 26 | const MAP = Symbol('map')
|
---|
| 27 | class Headers {
|
---|
| 28 | constructor (init = undefined) {
|
---|
| 29 | this[MAP] = Object.create(null)
|
---|
| 30 | if (init instanceof Headers) {
|
---|
| 31 | const rawHeaders = init.raw()
|
---|
| 32 | const headerNames = Object.keys(rawHeaders)
|
---|
| 33 | for (const headerName of headerNames) {
|
---|
| 34 | for (const value of rawHeaders[headerName]) {
|
---|
| 35 | this.append(headerName, value)
|
---|
| 36 | }
|
---|
| 37 | }
|
---|
| 38 | return
|
---|
| 39 | }
|
---|
| 40 |
|
---|
| 41 | // no-op
|
---|
| 42 | if (init === undefined || init === null)
|
---|
| 43 | return
|
---|
| 44 |
|
---|
| 45 | if (typeof init === 'object') {
|
---|
| 46 | const method = init[Symbol.iterator]
|
---|
| 47 | if (method !== null && method !== undefined) {
|
---|
| 48 | if (typeof method !== 'function')
|
---|
| 49 | throw new TypeError('Header pairs must be iterable')
|
---|
| 50 |
|
---|
| 51 | // sequence<sequence<ByteString>>
|
---|
| 52 | // Note: per spec we have to first exhaust the lists then process them
|
---|
| 53 | const pairs = []
|
---|
| 54 | for (const pair of init) {
|
---|
| 55 | if (typeof pair !== 'object' ||
|
---|
| 56 | typeof pair[Symbol.iterator] !== 'function')
|
---|
| 57 | throw new TypeError('Each header pair must be iterable')
|
---|
| 58 | const arrPair = Array.from(pair)
|
---|
| 59 | if (arrPair.length !== 2)
|
---|
| 60 | throw new TypeError('Each header pair must be a name/value tuple')
|
---|
| 61 | pairs.push(arrPair)
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | for (const pair of pairs) {
|
---|
| 65 | this.append(pair[0], pair[1])
|
---|
| 66 | }
|
---|
| 67 | } else {
|
---|
| 68 | // record<ByteString, ByteString>
|
---|
| 69 | for (const key of Object.keys(init)) {
|
---|
| 70 | this.append(key, init[key])
|
---|
| 71 | }
|
---|
| 72 | }
|
---|
| 73 | } else
|
---|
| 74 | throw new TypeError('Provided initializer must be an object')
|
---|
| 75 | }
|
---|
| 76 |
|
---|
| 77 | get (name) {
|
---|
| 78 | name = `${name}`
|
---|
| 79 | validateName(name)
|
---|
| 80 | const key = find(this[MAP], name)
|
---|
| 81 | if (key === undefined)
|
---|
| 82 | return null
|
---|
| 83 |
|
---|
| 84 | return this[MAP][key].join(', ')
|
---|
| 85 | }
|
---|
| 86 |
|
---|
| 87 | forEach (callback, thisArg = undefined) {
|
---|
| 88 | let pairs = getHeaders(this)
|
---|
| 89 | for (let i = 0; i < pairs.length; i++) {
|
---|
| 90 | const [name, value] = pairs[i]
|
---|
| 91 | callback.call(thisArg, value, name, this)
|
---|
| 92 | // refresh in case the callback added more headers
|
---|
| 93 | pairs = getHeaders(this)
|
---|
| 94 | }
|
---|
| 95 | }
|
---|
| 96 |
|
---|
| 97 | set (name, value) {
|
---|
| 98 | name = `${name}`
|
---|
| 99 | value = `${value}`
|
---|
| 100 | validateName(name)
|
---|
| 101 | validateValue(value)
|
---|
| 102 | const key = find(this[MAP], name)
|
---|
| 103 | this[MAP][key !== undefined ? key : name] = [value]
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | append (name, value) {
|
---|
| 107 | name = `${name}`
|
---|
| 108 | value = `${value}`
|
---|
| 109 | validateName(name)
|
---|
| 110 | validateValue(value)
|
---|
| 111 | const key = find(this[MAP], name)
|
---|
| 112 | if (key !== undefined)
|
---|
| 113 | this[MAP][key].push(value)
|
---|
| 114 | else
|
---|
| 115 | this[MAP][name] = [value]
|
---|
| 116 | }
|
---|
| 117 |
|
---|
| 118 | has (name) {
|
---|
| 119 | name = `${name}`
|
---|
| 120 | validateName(name)
|
---|
| 121 | return find(this[MAP], name) !== undefined
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | delete (name) {
|
---|
| 125 | name = `${name}`
|
---|
| 126 | validateName(name)
|
---|
| 127 | const key = find(this[MAP], name)
|
---|
| 128 | if (key !== undefined)
|
---|
| 129 | delete this[MAP][key]
|
---|
| 130 | }
|
---|
| 131 |
|
---|
| 132 | raw () {
|
---|
| 133 | return this[MAP]
|
---|
| 134 | }
|
---|
| 135 |
|
---|
| 136 | keys () {
|
---|
| 137 | return new HeadersIterator(this, 'key')
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | values () {
|
---|
| 141 | return new HeadersIterator(this, 'value')
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | [Symbol.iterator]() {
|
---|
| 145 | return new HeadersIterator(this, 'key+value')
|
---|
| 146 | }
|
---|
| 147 |
|
---|
| 148 | entries () {
|
---|
| 149 | return new HeadersIterator(this, 'key+value')
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | get [Symbol.toStringTag] () {
|
---|
| 153 | return 'Headers'
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | static exportNodeCompatibleHeaders (headers) {
|
---|
| 157 | const obj = Object.assign(Object.create(null), headers[MAP])
|
---|
| 158 |
|
---|
| 159 | // http.request() only supports string as Host header. This hack makes
|
---|
| 160 | // specifying custom Host header possible.
|
---|
| 161 | const hostHeaderKey = find(headers[MAP], 'Host')
|
---|
| 162 | if (hostHeaderKey !== undefined)
|
---|
| 163 | obj[hostHeaderKey] = obj[hostHeaderKey][0]
|
---|
| 164 |
|
---|
| 165 | return obj
|
---|
| 166 | }
|
---|
| 167 |
|
---|
| 168 | static createHeadersLenient (obj) {
|
---|
| 169 | const headers = new Headers()
|
---|
| 170 | for (const name of Object.keys(obj)) {
|
---|
| 171 | if (invalidTokenRegex.test(name))
|
---|
| 172 | continue
|
---|
| 173 |
|
---|
| 174 | if (Array.isArray(obj[name])) {
|
---|
| 175 | for (const val of obj[name]) {
|
---|
| 176 | if (invalidHeaderCharRegex.test(val))
|
---|
| 177 | continue
|
---|
| 178 |
|
---|
| 179 | if (headers[MAP][name] === undefined)
|
---|
| 180 | headers[MAP][name] = [val]
|
---|
| 181 | else
|
---|
| 182 | headers[MAP][name].push(val)
|
---|
| 183 | }
|
---|
| 184 | } else if (!invalidHeaderCharRegex.test(obj[name]))
|
---|
| 185 | headers[MAP][name] = [obj[name]]
|
---|
| 186 | }
|
---|
| 187 | return headers
|
---|
| 188 | }
|
---|
| 189 | }
|
---|
| 190 |
|
---|
| 191 | Object.defineProperties(Headers.prototype, {
|
---|
| 192 | get: { enumerable: true },
|
---|
| 193 | forEach: { enumerable: true },
|
---|
| 194 | set: { enumerable: true },
|
---|
| 195 | append: { enumerable: true },
|
---|
| 196 | has: { enumerable: true },
|
---|
| 197 | delete: { enumerable: true },
|
---|
| 198 | keys: { enumerable: true },
|
---|
| 199 | values: { enumerable: true },
|
---|
| 200 | entries: { enumerable: true },
|
---|
| 201 | })
|
---|
| 202 |
|
---|
| 203 | const getHeaders = (headers, kind = 'key+value') =>
|
---|
| 204 | Object.keys(headers[MAP]).sort().map(
|
---|
| 205 | kind === 'key' ? k => k.toLowerCase()
|
---|
| 206 | : kind === 'value' ? k => headers[MAP][k].join(', ')
|
---|
| 207 | : k => [k.toLowerCase(), headers[MAP][k].join(', ')]
|
---|
| 208 | )
|
---|
| 209 |
|
---|
| 210 | const INTERNAL = Symbol('internal')
|
---|
| 211 |
|
---|
| 212 | class HeadersIterator {
|
---|
| 213 | constructor (target, kind) {
|
---|
| 214 | this[INTERNAL] = {
|
---|
| 215 | target,
|
---|
| 216 | kind,
|
---|
| 217 | index: 0,
|
---|
| 218 | }
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 | get [Symbol.toStringTag] () {
|
---|
| 222 | return 'HeadersIterator'
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | next () {
|
---|
| 226 | /* istanbul ignore if: should be impossible */
|
---|
| 227 | if (!this || Object.getPrototypeOf(this) !== HeadersIterator.prototype)
|
---|
| 228 | throw new TypeError('Value of `this` is not a HeadersIterator')
|
---|
| 229 |
|
---|
| 230 | const { target, kind, index } = this[INTERNAL]
|
---|
| 231 | const values = getHeaders(target, kind)
|
---|
| 232 | const len = values.length
|
---|
| 233 | if (index >= len) {
|
---|
| 234 | return {
|
---|
| 235 | value: undefined,
|
---|
| 236 | done: true,
|
---|
| 237 | }
|
---|
| 238 | }
|
---|
| 239 |
|
---|
| 240 | this[INTERNAL].index++
|
---|
| 241 |
|
---|
| 242 | return { value: values[index], done: false }
|
---|
| 243 | }
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | // manually extend because 'extends' requires a ctor
|
---|
| 247 | Object.setPrototypeOf(HeadersIterator.prototype,
|
---|
| 248 | Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())))
|
---|
| 249 |
|
---|
| 250 | module.exports = Headers
|
---|