Skip to content

Commit 73d9445

Browse files
committed
http: try to avoid lowercasing incoming headers
PR-URL: #10558 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
1 parent c77ed32 commit 73d9445

File tree

1 file changed

+161
-45
lines changed

1 file changed

+161
-45
lines changed

lib/_http_incoming.js

+161-45
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,146 @@ function _addHeaderLines(headers, n) {
119119
}
120120

121121

122+
// This function is used to help avoid the lowercasing of a field name if it
123+
// matches a 'traditional cased' version of a field name. It then returns the
124+
// lowercased name to both avoid calling toLowerCase() a second time and to
125+
// indicate whether the field was a 'no duplicates' field. If a field is not a
126+
// 'no duplicates' field, a `0` byte is prepended as a flag. The one exception
127+
// to this is the Set-Cookie header which is indicated by a `1` byte flag, since
128+
// it is an 'array' field and thus is treated differently in _addHeaderLines().
129+
// TODO: perhaps http_parser could be returning both raw and lowercased versions
130+
// of known header names to avoid us having to call toLowerCase() for those
131+
// headers.
132+
/* eslint-disable max-len */
133+
// 'array' header list is taken from:
134+
// https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp
135+
/* eslint-enable max-len */
136+
function matchKnownFields(field) {
137+
var low = false;
138+
while (true) {
139+
switch (field) {
140+
case 'Content-Type':
141+
case 'content-type':
142+
return 'content-type';
143+
case 'Content-Length':
144+
case 'content-length':
145+
return 'content-length';
146+
case 'User-Agent':
147+
case 'user-agent':
148+
return 'user-agent';
149+
case 'Referer':
150+
case 'referer':
151+
return 'referer';
152+
case 'Host':
153+
case 'host':
154+
return 'host';
155+
case 'Authorization':
156+
case 'authorization':
157+
return 'authorization';
158+
case 'Proxy-Authorization':
159+
case 'proxy-authorization':
160+
return 'proxy-authorization';
161+
case 'If-Modified-Since':
162+
case 'if-modified-since':
163+
return 'if-modified-since';
164+
case 'If-Unmodified-Since':
165+
case 'if-unmodified-since':
166+
return 'if-unmodified-since';
167+
case 'From':
168+
case 'from':
169+
return 'from';
170+
case 'Location':
171+
case 'location':
172+
return 'location';
173+
case 'Max-Forwards':
174+
case 'max-forwards':
175+
return 'max-forwards';
176+
case 'Retry-After':
177+
case 'retry-after':
178+
return 'retry-after';
179+
case 'ETag':
180+
case 'etag':
181+
return 'etag';
182+
case 'Last-Modified':
183+
case 'last-modified':
184+
return 'last-modified';
185+
case 'Server':
186+
case 'server':
187+
return 'server';
188+
case 'Age':
189+
case 'age':
190+
return 'age';
191+
case 'Expires':
192+
case 'expires':
193+
return 'expires';
194+
case 'Set-Cookie':
195+
case 'set-cookie':
196+
return '\u0001';
197+
// The fields below are not used in _addHeaderLine(), but they are common
198+
// headers where we can avoid toLowerCase() if the mixed or lower case
199+
// versions match the first time through.
200+
case 'Transfer-Encoding':
201+
case 'transfer-encoding':
202+
return '\u0000transfer-encoding';
203+
case 'Date':
204+
case 'date':
205+
return '\u0000date';
206+
case 'Connection':
207+
case 'connection':
208+
return '\u0000connection';
209+
case 'Cache-Control':
210+
case 'cache-control':
211+
return '\u0000cache-control';
212+
case 'Vary':
213+
case 'vary':
214+
return '\u0000vary';
215+
case 'Content-Encoding':
216+
case 'content-encoding':
217+
return '\u0000content-encoding';
218+
case 'Cookie':
219+
case 'cookie':
220+
return '\u0000cookie';
221+
case 'Origin':
222+
case 'origin':
223+
return '\u0000origin';
224+
case 'Upgrade':
225+
case 'upgrade':
226+
return '\u0000upgrade';
227+
case 'Expect':
228+
case 'expect':
229+
return '\u0000expect';
230+
case 'If-Match':
231+
case 'if-match':
232+
return '\u0000if-match';
233+
case 'If-None-Match':
234+
case 'if-none-match':
235+
return '\u0000if-none-match';
236+
case 'Accept':
237+
case 'accept':
238+
return '\u0000accept';
239+
case 'Accept-Encoding':
240+
case 'accept-encoding':
241+
return '\u0000accept-encoding';
242+
case 'Accept-Language':
243+
case 'accept-language':
244+
return '\u0000accept-language';
245+
case 'X-Forwarded-For':
246+
case 'x-forwarded-for':
247+
return '\u0000x-forwarded-for';
248+
case 'X-Forwarded-Host':
249+
case 'x-forwarded-host':
250+
return '\u0000x-forwarded-host';
251+
case 'X-Forwarded-Proto':
252+
case 'x-forwarded-proto':
253+
return '\u0000x-forwarded-proto';
254+
default:
255+
if (low)
256+
return '\u0000' + field;
257+
field = field.toLowerCase();
258+
low = true;
259+
}
260+
}
261+
}
122262
// Add the given (field, value) pair to the message
123263
//
124264
// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
@@ -128,51 +268,27 @@ function _addHeaderLines(headers, n) {
128268
// always joined.
129269
IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
130270
function _addHeaderLine(field, value, dest) {
131-
field = field.toLowerCase();
132-
switch (field) {
133-
// Array headers:
134-
case 'set-cookie':
135-
if (dest[field] !== undefined) {
136-
dest[field].push(value);
137-
} else {
138-
dest[field] = [value];
139-
}
140-
break;
141-
142-
/* eslint-disable max-len */
143-
// list is taken from:
144-
// https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp
145-
/* eslint-enable max-len */
146-
case 'content-type':
147-
case 'content-length':
148-
case 'user-agent':
149-
case 'referer':
150-
case 'host':
151-
case 'authorization':
152-
case 'proxy-authorization':
153-
case 'if-modified-since':
154-
case 'if-unmodified-since':
155-
case 'from':
156-
case 'location':
157-
case 'max-forwards':
158-
case 'retry-after':
159-
case 'etag':
160-
case 'last-modified':
161-
case 'server':
162-
case 'age':
163-
case 'expires':
164-
// drop duplicates
165-
if (dest[field] === undefined)
166-
dest[field] = value;
167-
break;
168-
169-
default:
170-
// make comma-separated list
171-
if (typeof dest[field] === 'string') {
172-
dest[field] += ', ' + value;
173-
} else {
174-
dest[field] = value;
175-
}
271+
field = matchKnownFields(field);
272+
var flag = field.charCodeAt(0);
273+
if (flag === 0) {
274+
field = field.slice(1);
275+
// Make comma-separated list
276+
if (typeof dest[field] === 'string') {
277+
dest[field] += ', ' + value;
278+
} else {
279+
dest[field] = value;
280+
}
281+
} else if (flag === 1) {
282+
// Array header -- only Set-Cookie at the moment
283+
if (dest['set-cookie'] !== undefined) {
284+
dest['set-cookie'].push(value);
285+
} else {
286+
dest['set-cookie'] = [value];
287+
}
288+
} else {
289+
// Drop duplicates
290+
if (dest[field] === undefined)
291+
dest[field] = value;
176292
}
177293
}
178294

0 commit comments

Comments
 (0)