@@ -52,6 +52,8 @@ const { utcDate } = internalHttp;
52
52
53
53
const kIsCorked = Symbol ( 'isCorked' ) ;
54
54
55
+ const hasOwnProperty = Function . call . bind ( Object . prototype . hasOwnProperty ) ;
56
+
55
57
var RE_CONN_CLOSE = / (?: ^ | \W ) c l o s e (?: $ | \W ) / i;
56
58
var RE_TE_CHUNKED = common . chunkExpression ;
57
59
@@ -116,7 +118,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headers', {
116
118
if ( val == null ) {
117
119
this [ outHeadersKey ] = null ;
118
120
} else if ( typeof val === 'object' ) {
119
- const headers = this [ outHeadersKey ] = { } ;
121
+ const headers = this [ outHeadersKey ] = Object . create ( null ) ;
120
122
const keys = Object . keys ( val ) ;
121
123
for ( var i = 0 ; i < keys . length ; ++ i ) {
122
124
const name = keys [ i ] ;
@@ -129,7 +131,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headers', {
129
131
Object . defineProperty ( OutgoingMessage . prototype , '_headerNames' , {
130
132
get : function ( ) {
131
133
const headers = this [ outHeadersKey ] ;
132
- if ( headers ) {
134
+ if ( headers !== null ) {
133
135
const out = Object . create ( null ) ;
134
136
const keys = Object . keys ( headers ) ;
135
137
for ( var i = 0 ; i < keys . length ; ++ i ) {
@@ -138,9 +140,8 @@ Object.defineProperty(OutgoingMessage.prototype, '_headerNames', {
138
140
out [ key ] = val ;
139
141
}
140
142
return out ;
141
- } else {
142
- return headers ;
143
143
}
144
+ return null ;
144
145
} ,
145
146
set : function ( val ) {
146
147
if ( typeof val === 'object' && val !== null ) {
@@ -164,14 +165,14 @@ OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
164
165
}
165
166
166
167
var headersMap = this [ outHeadersKey ] ;
167
- if ( ! headersMap ) return { } ;
168
-
169
- var headers = { } ;
170
- var keys = Object . keys ( headersMap ) ;
168
+ const headers = { } ;
171
169
172
- for ( var i = 0 , l = keys . length ; i < l ; i ++ ) {
173
- var key = keys [ i ] ;
174
- headers [ headersMap [ key ] [ 0 ] ] = headersMap [ key ] [ 1 ] ;
170
+ if ( headersMap !== null ) {
171
+ const keys = Object . keys ( headersMap ) ;
172
+ for ( var i = 0 , l = keys . length ; i < l ; i ++ ) {
173
+ const key = keys [ i ] ;
174
+ headers [ headersMap [ key ] [ 0 ] ] = headersMap [ key ] [ 1 ] ;
175
+ }
175
176
}
176
177
return headers ;
177
178
} ;
@@ -285,72 +286,40 @@ OutgoingMessage.prototype._storeHeader = _storeHeader;
285
286
function _storeHeader ( firstLine , headers ) {
286
287
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
287
288
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
288
- var state = {
289
+ const state = {
289
290
connection : false ,
290
291
contLen : false ,
291
292
te : false ,
292
293
date : false ,
293
294
expect : false ,
294
295
trailer : false ,
295
- upgrade : false ,
296
296
header : firstLine
297
297
} ;
298
298
299
- var field ;
300
299
var key ;
301
- var value ;
302
- var i ;
303
- var j ;
304
300
if ( headers === this [ outHeadersKey ] ) {
305
301
for ( key in headers ) {
306
- var entry = headers [ key ] ;
307
- field = entry [ 0 ] ;
308
- value = entry [ 1 ] ;
309
-
310
- if ( value instanceof Array ) {
311
- if ( value . length < 2 || ! isCookieField ( field ) ) {
312
- for ( j = 0 ; j < value . length ; j ++ )
313
- storeHeader ( this , state , field , value [ j ] , false ) ;
314
- continue ;
315
- }
316
- value = value . join ( '; ' ) ;
317
- }
318
- storeHeader ( this , state , field , value , false ) ;
302
+ const entry = headers [ key ] ;
303
+ processHeader ( this , state , entry [ 0 ] , entry [ 1 ] , false ) ;
319
304
}
320
- } else if ( headers instanceof Array ) {
321
- for ( i = 0 ; i < headers . length ; i ++ ) {
322
- field = headers [ i ] [ 0 ] ;
323
- value = headers [ i ] [ 1 ] ;
324
-
325
- if ( value instanceof Array ) {
326
- for ( j = 0 ; j < value . length ; j ++ ) {
327
- storeHeader ( this , state , field , value [ j ] , true ) ;
328
- }
329
- } else {
330
- storeHeader ( this , state , field , value , true ) ;
331
- }
305
+ } else if ( Array . isArray ( headers ) ) {
306
+ for ( var i = 0 ; i < headers . length ; i ++ ) {
307
+ const entry = headers [ i ] ;
308
+ processHeader ( this , state , entry [ 0 ] , entry [ 1 ] , true ) ;
332
309
}
333
310
} else if ( headers ) {
334
- var keys = Object . keys ( headers ) ;
335
- for ( i = 0 ; i < keys . length ; i ++ ) {
336
- field = keys [ i ] ;
337
- value = headers [ field ] ;
338
-
339
- if ( value instanceof Array ) {
340
- if ( value . length < 2 || ! isCookieField ( field ) ) {
341
- for ( j = 0 ; j < value . length ; j ++ )
342
- storeHeader ( this , state , field , value [ j ] , true ) ;
343
- continue ;
344
- }
345
- value = value . join ( '; ' ) ;
311
+ for ( key in headers ) {
312
+ if ( hasOwnProperty ( headers , key ) ) {
313
+ processHeader ( this , state , key , headers [ key ] , true ) ;
346
314
}
347
- storeHeader ( this , state , field , value , true ) ;
348
315
}
349
316
}
350
317
318
+ let { header } = state ;
319
+
351
320
// Date header
352
321
if ( this . sendDate && ! state . date ) {
353
- state . header += 'Date: ' + utcDate ( ) + CRLF ;
322
+ header += 'Date: ' + utcDate ( ) + CRLF ;
354
323
}
355
324
356
325
// Force the connection to close when the response is a 204 No Content or
@@ -364,9 +333,9 @@ function _storeHeader(firstLine, headers) {
364
333
// It was pointed out that this might confuse reverse proxies to the point
365
334
// of creating security liabilities, so suppress the zero chunk and force
366
335
// the connection to close.
367
- var statusCode = this . statusCode ;
368
- if ( ( statusCode === 204 || statusCode === 304 ) && this . chunkedEncoding ) {
369
- debug ( statusCode + ' response should not use chunked encoding,' +
336
+ if ( this . chunkedEncoding && ( this . statusCode === 204 ||
337
+ this . statusCode === 304 ) ) {
338
+ debug ( this . statusCode + ' response should not use chunked encoding,' +
370
339
' closing connection.' ) ;
371
340
this . chunkedEncoding = false ;
372
341
this . shouldKeepAlive = false ;
@@ -377,13 +346,13 @@ function _storeHeader(firstLine, headers) {
377
346
this . _last = true ;
378
347
this . shouldKeepAlive = false ;
379
348
} else if ( ! state . connection ) {
380
- var shouldSendKeepAlive = this . shouldKeepAlive &&
349
+ const shouldSendKeepAlive = this . shouldKeepAlive &&
381
350
( state . contLen || this . useChunkedEncodingByDefault || this . agent ) ;
382
351
if ( shouldSendKeepAlive ) {
383
- state . header += 'Connection: keep-alive\r\n' ;
352
+ header += 'Connection: keep-alive\r\n' ;
384
353
} else {
385
354
this . _last = true ;
386
- state . header += 'Connection: close\r\n' ;
355
+ header += 'Connection: close\r\n' ;
387
356
}
388
357
}
389
358
@@ -396,9 +365,9 @@ function _storeHeader(firstLine, headers) {
396
365
} else if ( ! state . trailer &&
397
366
! this . _removedContLen &&
398
367
typeof this . _contentLength === 'number' ) {
399
- state . header += 'Content-Length: ' + this . _contentLength + CRLF ;
368
+ header += 'Content-Length: ' + this . _contentLength + CRLF ;
400
369
} else if ( ! this . _removedTE ) {
401
- state . header += 'Transfer-Encoding: chunked\r\n' ;
370
+ header += 'Transfer-Encoding: chunked\r\n' ;
402
371
this . chunkedEncoding = true ;
403
372
} else {
404
373
// We should only be able to get here if both Content-Length and
@@ -416,18 +385,31 @@ function _storeHeader(firstLine, headers) {
416
385
throw new ERR_HTTP_TRAILER_INVALID ( ) ;
417
386
}
418
387
419
- this . _header = state . header + CRLF ;
388
+ this . _header = header + CRLF ;
420
389
this . _headerSent = false ;
421
390
422
391
// wait until the first body chunk, or close(), is sent to flush,
423
392
// UNLESS we're sending Expect: 100-continue.
424
393
if ( state . expect ) this . _send ( '' ) ;
425
394
}
426
395
427
- function storeHeader ( self , state , key , value , validate ) {
428
- if ( validate ) {
429
- validateHeader ( key , value ) ;
396
+ function processHeader ( self , state , key , value , validate ) {
397
+ if ( validate )
398
+ validateHeaderName ( key ) ;
399
+ if ( Array . isArray ( value ) ) {
400
+ if ( value . length < 2 || ! isCookieField ( key ) ) {
401
+ for ( var i = 0 ; i < value . length ; i ++ )
402
+ storeHeader ( self , state , key , value [ i ] , validate ) ;
403
+ return ;
404
+ }
405
+ value = value . join ( '; ' ) ;
430
406
}
407
+ storeHeader ( self , state , key , value , validate ) ;
408
+ }
409
+
410
+ function storeHeader ( self , state , key , value , validate ) {
411
+ if ( validate )
412
+ validateHeaderValue ( key , value ) ;
431
413
state . header += key + ': ' + escapeHeaderValue ( value ) + CRLF ;
432
414
matchHeader ( self , state , key , value ) ;
433
415
}
@@ -439,39 +421,47 @@ function matchHeader(self, state, field, value) {
439
421
switch ( field ) {
440
422
case 'connection' :
441
423
state . connection = true ;
424
+ self . _removedConnection = false ;
442
425
if ( RE_CONN_CLOSE . test ( value ) )
443
426
self . _last = true ;
444
427
else
445
428
self . shouldKeepAlive = true ;
446
429
break ;
447
430
case 'transfer-encoding' :
448
431
state . te = true ;
432
+ self . _removedTE = false ;
449
433
if ( RE_TE_CHUNKED . test ( value ) ) self . chunkedEncoding = true ;
450
434
break ;
451
435
case 'content-length' :
452
436
state . contLen = true ;
437
+ self . _removedContLen = false ;
453
438
break ;
454
439
case 'date' :
455
440
case 'expect' :
456
441
case 'trailer' :
457
- case 'upgrade' :
458
442
state [ field ] = true ;
459
443
break ;
460
444
}
461
445
}
462
446
463
- function validateHeader ( name , value ) {
464
- let err ;
447
+ function validateHeaderName ( name ) {
465
448
if ( typeof name !== 'string' || ! name || ! checkIsHttpToken ( name ) ) {
466
- err = new ERR_INVALID_HTTP_TOKEN ( 'Header name' , name ) ;
467
- } else if ( value === undefined ) {
449
+ const err = new ERR_INVALID_HTTP_TOKEN ( 'Header name' , name ) ;
450
+ Error . captureStackTrace ( err , validateHeaderName ) ;
451
+ throw err ;
452
+ }
453
+ }
454
+
455
+ function validateHeaderValue ( name , value ) {
456
+ let err ;
457
+ if ( value === undefined ) {
468
458
err = new ERR_HTTP_INVALID_HEADER_VALUE ( value , name ) ;
469
459
} else if ( checkInvalidHeaderChar ( value ) ) {
470
460
debug ( 'Header "%s" contains invalid characters' , name ) ;
471
461
err = new ERR_INVALID_CHAR ( 'header content' , name ) ;
472
462
}
473
463
if ( err !== undefined ) {
474
- Error . captureStackTrace ( err , validateHeader ) ;
464
+ Error . captureStackTrace ( err , validateHeaderValue ) ;
475
465
throw err ;
476
466
}
477
467
}
@@ -480,25 +470,14 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
480
470
if ( this . _header ) {
481
471
throw new ERR_HTTP_HEADERS_SENT ( 'set' ) ;
482
472
}
483
- validateHeader ( name , value ) ;
473
+ validateHeaderName ( name ) ;
474
+ validateHeaderValue ( name , value ) ;
484
475
485
- if ( ! this [ outHeadersKey ] )
486
- this [ outHeadersKey ] = { } ;
476
+ let headers = this [ outHeadersKey ] ;
477
+ if ( headers === null )
478
+ this [ outHeadersKey ] = headers = Object . create ( null ) ;
487
479
488
- const key = name . toLowerCase ( ) ;
489
- this [ outHeadersKey ] [ key ] = [ name , value ] ;
490
-
491
- switch ( key ) {
492
- case 'connection' :
493
- this . _removedConnection = false ;
494
- break ;
495
- case 'content-length' :
496
- this . _removedContLen = false ;
497
- break ;
498
- case 'transfer-encoding' :
499
- this . _removedTE = false ;
500
- break ;
501
- }
480
+ headers [ name . toLowerCase ( ) ] = [ name , value ] ;
502
481
} ;
503
482
504
483
@@ -507,18 +486,18 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
507
486
throw new ERR_INVALID_ARG_TYPE ( 'name' , 'string' , name ) ;
508
487
}
509
488
510
- if ( ! this [ outHeadersKey ] ) return ;
511
-
512
- var entry = this [ outHeadersKey ] [ name . toLowerCase ( ) ] ;
513
- if ( ! entry )
489
+ const headers = this [ outHeadersKey ] ;
490
+ if ( headers === null )
514
491
return ;
515
- return entry [ 1 ] ;
492
+
493
+ const entry = headers [ name . toLowerCase ( ) ] ;
494
+ return entry && entry [ 1 ] ;
516
495
} ;
517
496
518
497
519
498
// Returns an array of the names of the current outgoing headers.
520
499
OutgoingMessage . prototype . getHeaderNames = function getHeaderNames ( ) {
521
- return ( this [ outHeadersKey ] ? Object . keys ( this [ outHeadersKey ] ) : [ ] ) ;
500
+ return this [ outHeadersKey ] !== null ? Object . keys ( this [ outHeadersKey ] ) : [ ] ;
522
501
} ;
523
502
524
503
@@ -543,7 +522,8 @@ OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
543
522
throw new ERR_INVALID_ARG_TYPE ( 'name' , 'string' , name ) ;
544
523
}
545
524
546
- return ! ! ( this [ outHeadersKey ] && this [ outHeadersKey ] [ name . toLowerCase ( ) ] ) ;
525
+ return this [ outHeadersKey ] !== null &&
526
+ ! ! this [ outHeadersKey ] [ name . toLowerCase ( ) ] ;
547
527
} ;
548
528
549
529
@@ -573,7 +553,7 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
573
553
break ;
574
554
}
575
555
576
- if ( this [ outHeadersKey ] ) {
556
+ if ( this [ outHeadersKey ] !== null ) {
577
557
delete this [ outHeadersKey ] [ key ] ;
578
558
}
579
559
} ;
0 commit comments