Skip to content

Commit 1592ca1

Browse files
committed
Use caseless Headers handling
This is unfortunately impossible to test, since the Node.js HTTP library lower-cases all incoming headers. However, this matters for outgoing HTTP requests. See the linked issues from the linked Fetch Standard pull request. See: whatwg/fetch#476
1 parent a8f6d79 commit 1592ca1

File tree

1 file changed

+61
-35
lines changed

1 file changed

+61
-35
lines changed

src/headers.js

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,36 @@
88
const invalidTokenRegex = /[^\^_`a-zA-Z\-0-9!#$%&'*+.|~]/;
99
const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
1010

11-
function sanitizeName(name) {
12-
name += '';
11+
function validateName(name) {
12+
name = `${name}`;
1313
if (invalidTokenRegex.test(name)) {
1414
throw new TypeError(`${name} is not a legal HTTP header name`);
1515
}
16-
return name.toLowerCase();
1716
}
1817

19-
function sanitizeValue(value) {
20-
value += '';
18+
function validateValue(value) {
19+
value = `${value}`;
2120
if (invalidHeaderCharRegex.test(value)) {
2221
throw new TypeError(`${value} is not a legal HTTP header value`);
2322
}
24-
return value;
23+
}
24+
25+
/**
26+
* Find the key in the map object given a header name.
27+
*
28+
* Returns undefined if not found.
29+
*
30+
* @param String name Header name
31+
* @return String|Undefined
32+
*/
33+
function find(map, name) {
34+
name = name.toLowerCase();
35+
for (const key in map) {
36+
if (key.toLowerCase() === name) {
37+
return key;
38+
}
39+
}
40+
return undefined;
2541
}
2642

2743
const MAP = Symbol('map');
@@ -88,18 +104,20 @@ export default class Headers {
88104
}
89105

90106
/**
91-
* Return first header value given name
107+
* Return combined header value given name
92108
*
93109
* @param String name Header name
94110
* @return Mixed
95111
*/
96112
get(name) {
97-
const list = this[MAP][sanitizeName(name)];
98-
if (!list) {
113+
name = `${name}`;
114+
validateName(name);
115+
const key = find(this[MAP], name);
116+
if (key === undefined) {
99117
return null;
100118
}
101119

102-
return list.join(', ');
120+
return this[MAP][key].join(', ');
103121
}
104122

105123
/**
@@ -110,12 +128,12 @@ export default class Headers {
110128
* @return Void
111129
*/
112130
forEach(callback, thisArg = undefined) {
113-
let pairs = getHeaderPairs(this);
131+
let pairs = getHeaders(this);
114132
let i = 0;
115133
while (i < pairs.length) {
116134
const [name, value] = pairs[i];
117135
callback.call(thisArg, value, name, this);
118-
pairs = getHeaderPairs(this);
136+
pairs = getHeaders(this);
119137
i++;
120138
}
121139
}
@@ -128,7 +146,12 @@ export default class Headers {
128146
* @return Void
129147
*/
130148
set(name, value) {
131-
this[MAP][sanitizeName(name)] = [sanitizeValue(value)];
149+
name = `${name}`;
150+
value = `${value}`;
151+
validateName(name);
152+
validateValue(value);
153+
const key = find(this[MAP], name);
154+
this[MAP][key !== undefined ? key : name] = [value];
132155
}
133156

134157
/**
@@ -139,12 +162,16 @@ export default class Headers {
139162
* @return Void
140163
*/
141164
append(name, value) {
142-
if (!this.has(name)) {
143-
this.set(name, value);
144-
return;
165+
name = `${name}`;
166+
value = `${value}`;
167+
validateName(name);
168+
validateValue(value);
169+
const key = find(this[MAP], name);
170+
if (key !== undefined) {
171+
this[MAP][key].push(value);
172+
} else {
173+
this[MAP][name] = [value];
145174
}
146-
147-
this[MAP][sanitizeName(name)].push(sanitizeValue(value));
148175
}
149176

150177
/**
@@ -154,7 +181,9 @@ export default class Headers {
154181
* @return Boolean
155182
*/
156183
has(name) {
157-
return !!this[MAP][sanitizeName(name)];
184+
name = `${name}`;
185+
validateName(name);
186+
return find(this[MAP], name) !== undefined;
158187
}
159188

160189
/**
@@ -164,7 +193,12 @@ export default class Headers {
164193
* @return Void
165194
*/
166195
delete(name) {
167-
delete this[MAP][sanitizeName(name)];
196+
name = `${name}`;
197+
validateName(name);
198+
const key = find(this[MAP], name);
199+
if (key !== undefined) {
200+
delete this[MAP][key];
201+
}
168202
};
169203

170204
/**
@@ -226,12 +260,14 @@ Object.defineProperties(Headers.prototype, {
226260
entries: { enumerable: true }
227261
});
228262

229-
function getHeaderPairs(headers, kind) {
263+
function getHeaders(headers, kind = 'key+value') {
230264
const keys = Object.keys(headers[MAP]).sort();
231265
return keys.map(
232266
kind === 'key' ?
233-
k => [k] :
234-
k => [k, headers.get(k)]
267+
k => k.toLowerCase() :
268+
kind === 'value' ?
269+
k => headers[MAP][k].join(', ') :
270+
k => [k.toLowerCase(), headers[MAP][k].join(', ')]
235271
);
236272
}
237273

@@ -260,7 +296,7 @@ const HeadersIteratorPrototype = Object.setPrototypeOf({
260296
kind,
261297
index
262298
} = this[INTERNAL];
263-
const values = getHeaderPairs(target, kind);
299+
const values = getHeaders(target, kind);
264300
const len = values.length;
265301
if (index >= len) {
266302
return {
@@ -269,20 +305,10 @@ const HeadersIteratorPrototype = Object.setPrototypeOf({
269305
};
270306
}
271307

272-
const pair = values[index];
273308
this[INTERNAL].index = index + 1;
274309

275-
let result;
276-
if (kind === 'key') {
277-
result = pair[0];
278-
} else if (kind === 'value') {
279-
result = pair[1];
280-
} else {
281-
result = pair;
282-
}
283-
284310
return {
285-
value: result,
311+
value: values[index],
286312
done: false
287313
};
288314
}

0 commit comments

Comments
 (0)