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