1'use strict' 2 3/** 4 * headers.js 5 * 6 * Headers class offers convenient helpers 7 */ 8 9const common = require('./common.js') 10const checkInvalidHeaderChar = common.checkInvalidHeaderChar 11const checkIsHttpToken = common.checkIsHttpToken 12 13function sanitizeName (name) { 14 name += '' 15 if (!checkIsHttpToken(name)) { 16 throw new TypeError(`${name} is not a legal HTTP header name`) 17 } 18 return name.toLowerCase() 19} 20 21function sanitizeValue (value) { 22 value += '' 23 if (checkInvalidHeaderChar(value)) { 24 throw new TypeError(`${value} is not a legal HTTP header value`) 25 } 26 return value 27} 28 29const MAP = Symbol('map') 30class Headers { 31 /** 32 * Headers class 33 * 34 * @param Object headers Response headers 35 * @return Void 36 */ 37 constructor (init) { 38 this[MAP] = Object.create(null) 39 40 if (init instanceof Headers) { 41 const rawHeaders = init.raw() 42 const headerNames = Object.keys(rawHeaders) 43 44 for (const headerName of headerNames) { 45 for (const value of rawHeaders[headerName]) { 46 this.append(headerName, value) 47 } 48 } 49 50 return 51 } 52 53 // We don't worry about converting prop to ByteString here as append() 54 // will handle it. 55 if (init == null) { 56 // no op 57 } else if (typeof init === 'object') { 58 const method = init[Symbol.iterator] 59 if (method != null) { 60 if (typeof method !== 'function') { 61 throw new TypeError('Header pairs must be iterable') 62 } 63 64 // sequence<sequence<ByteString>> 65 // Note: per spec we have to first exhaust the lists then process them 66 const pairs = [] 67 for (const pair of init) { 68 if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { 69 throw new TypeError('Each header pair must be iterable') 70 } 71 pairs.push(Array.from(pair)) 72 } 73 74 for (const pair of pairs) { 75 if (pair.length !== 2) { 76 throw new TypeError('Each header pair must be a name/value tuple') 77 } 78 this.append(pair[0], pair[1]) 79 } 80 } else { 81 // record<ByteString, ByteString> 82 for (const key of Object.keys(init)) { 83 const value = init[key] 84 this.append(key, value) 85 } 86 } 87 } else { 88 throw new TypeError('Provided initializer must be an object') 89 } 90 91 Object.defineProperty(this, Symbol.toStringTag, { 92 value: 'Headers', 93 writable: false, 94 enumerable: false, 95 configurable: true 96 }) 97 } 98 99 /** 100 * Return first header value given name 101 * 102 * @param String name Header name 103 * @return Mixed 104 */ 105 get (name) { 106 const list = this[MAP][sanitizeName(name)] 107 if (!list) { 108 return null 109 } 110 111 return list.join(', ') 112 } 113 114 /** 115 * Iterate over all headers 116 * 117 * @param Function callback Executed for each item with parameters (value, name, thisArg) 118 * @param Boolean thisArg `this` context for callback function 119 * @return Void 120 */ 121 forEach (callback, thisArg) { 122 let pairs = getHeaderPairs(this) 123 let i = 0 124 while (i < pairs.length) { 125 const name = pairs[i][0] 126 const value = pairs[i][1] 127 callback.call(thisArg, value, name, this) 128 pairs = getHeaderPairs(this) 129 i++ 130 } 131 } 132 133 /** 134 * Overwrite header values given name 135 * 136 * @param String name Header name 137 * @param String value Header value 138 * @return Void 139 */ 140 set (name, value) { 141 this[MAP][sanitizeName(name)] = [sanitizeValue(value)] 142 } 143 144 /** 145 * Append a value onto existing header 146 * 147 * @param String name Header name 148 * @param String value Header value 149 * @return Void 150 */ 151 append (name, value) { 152 if (!this.has(name)) { 153 this.set(name, value) 154 return 155 } 156 157 this[MAP][sanitizeName(name)].push(sanitizeValue(value)) 158 } 159 160 /** 161 * Check for header name existence 162 * 163 * @param String name Header name 164 * @return Boolean 165 */ 166 has (name) { 167 return !!this[MAP][sanitizeName(name)] 168 } 169 170 /** 171 * Delete all header values given name 172 * 173 * @param String name Header name 174 * @return Void 175 */ 176 delete (name) { 177 delete this[MAP][sanitizeName(name)] 178 }; 179 180 /** 181 * Return raw headers (non-spec api) 182 * 183 * @return Object 184 */ 185 raw () { 186 return this[MAP] 187 } 188 189 /** 190 * Get an iterator on keys. 191 * 192 * @return Iterator 193 */ 194 keys () { 195 return createHeadersIterator(this, 'key') 196 } 197 198 /** 199 * Get an iterator on values. 200 * 201 * @return Iterator 202 */ 203 values () { 204 return createHeadersIterator(this, 'value') 205 } 206 207 /** 208 * Get an iterator on entries. 209 * 210 * This is the default iterator of the Headers object. 211 * 212 * @return Iterator 213 */ 214 [Symbol.iterator] () { 215 return createHeadersIterator(this, 'key+value') 216 } 217} 218Headers.prototype.entries = Headers.prototype[Symbol.iterator] 219 220Object.defineProperty(Headers.prototype, Symbol.toStringTag, { 221 value: 'HeadersPrototype', 222 writable: false, 223 enumerable: false, 224 configurable: true 225}) 226 227function getHeaderPairs (headers, kind) { 228 const keys = Object.keys(headers[MAP]).sort() 229 return keys.map( 230 kind === 'key' 231 ? k => [k] 232 : k => [k, headers.get(k)] 233 ) 234} 235 236const INTERNAL = Symbol('internal') 237 238function createHeadersIterator (target, kind) { 239 const iterator = Object.create(HeadersIteratorPrototype) 240 iterator[INTERNAL] = { 241 target, 242 kind, 243 index: 0 244 } 245 return iterator 246} 247 248const HeadersIteratorPrototype = Object.setPrototypeOf({ 249 next () { 250 // istanbul ignore if 251 if (!this || 252 Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { 253 throw new TypeError('Value of `this` is not a HeadersIterator') 254 } 255 256 const target = this[INTERNAL].target 257 const kind = this[INTERNAL].kind 258 const index = this[INTERNAL].index 259 const values = getHeaderPairs(target, kind) 260 const len = values.length 261 if (index >= len) { 262 return { 263 value: undefined, 264 done: true 265 } 266 } 267 268 const pair = values[index] 269 this[INTERNAL].index = index + 1 270 271 let result 272 if (kind === 'key') { 273 result = pair[0] 274 } else if (kind === 'value') { 275 result = pair[1] 276 } else { 277 result = pair 278 } 279 280 return { 281 value: result, 282 done: false 283 } 284 } 285}, Object.getPrototypeOf( 286 Object.getPrototypeOf([][Symbol.iterator]()) 287)) 288 289Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { 290 value: 'HeadersIterator', 291 writable: false, 292 enumerable: false, 293 configurable: true 294}) 295 296module.exports = Headers 297