@@ -52,6 +52,8 @@ const { utcDate } = internalHttp;
5252
5353const kIsCorked = Symbol ( 'isCorked' ) ;
5454
55+ const hasOwnProperty = Function . call . bind ( Object . prototype . hasOwnProperty ) ;
56+
5557var RE_CONN_CLOSE = / (?: ^ | \W ) c l o s e (?: $ | \W ) / i;
5658var RE_TE_CHUNKED = common . chunkExpression ;
5759
@@ -116,7 +118,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headers', {
116118if ( val == null ) {
117119this [ outHeadersKey ] = null ;
118120} else if ( typeof val === 'object' ) {
119- const headers = this [ outHeadersKey ] = { } ;
121+ const headers = this [ outHeadersKey ] = Object . create ( null ) ;
120122const keys = Object . keys ( val ) ;
121123for ( var i = 0 ; i < keys . length ; ++ i ) {
122124const name = keys [ i ] ;
@@ -129,7 +131,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headers', {
129131Object . defineProperty ( OutgoingMessage . prototype , '_headerNames' , {
130132get : function ( ) {
131133const headers = this [ outHeadersKey ] ;
132- if ( headers ) {
134+ if ( headers !== null ) {
133135const out = Object . create ( null ) ;
134136const keys = Object . keys ( headers ) ;
135137for ( var i = 0 ; i < keys . length ; ++ i ) {
@@ -138,9 +140,8 @@ Object.defineProperty(OutgoingMessage.prototype, '_headerNames', {
138140out [ key ] = val ;
139141}
140142return out ;
141- } else {
142- return headers ;
143143}
144+ return null ;
144145} ,
145146set : function ( val ) {
146147if ( typeof val === 'object' && val !== null ) {
@@ -164,14 +165,14 @@ OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
164165}
165166
166167var headersMap = this [ outHeadersKey ] ;
167- if ( ! headersMap ) return { } ;
168-
169- var headers = { } ;
170- var keys = Object . keys ( headersMap ) ;
168+ const headers = { } ;
171169
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+ }
175176}
176177return headers ;
177178} ;
@@ -285,72 +286,40 @@ OutgoingMessage.prototype._storeHeader = _storeHeader;
285286function _storeHeader ( firstLine , headers ) {
286287// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
287288// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
288- var state = {
289+ const state = {
289290connection : false ,
290291contLen : false ,
291292te : false ,
292293date : false ,
293294expect : false ,
294295trailer : false ,
295- upgrade : false ,
296296header : firstLine
297297} ;
298298
299- var field ;
300299var key ;
301- var value ;
302- var i ;
303- var j ;
304300if ( headers === this [ outHeadersKey ] ) {
305301for ( 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 ) ;
319304}
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 ) ;
332309}
333310} 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 ) ;
346314}
347- storeHeader ( this , state , field , value , true ) ;
348315}
349316}
350317
318+ let { header } = state ;
319+
351320// Date header
352321if ( this . sendDate && ! state . date ) {
353- state . header += 'Date: ' + utcDate ( ) + CRLF ;
322+ header += 'Date: ' + utcDate ( ) + CRLF ;
354323}
355324
356325// Force the connection to close when the response is a 204 No Content or
@@ -364,9 +333,9 @@ function _storeHeader(firstLine, headers) {
364333// It was pointed out that this might confuse reverse proxies to the point
365334// of creating security liabilities, so suppress the zero chunk and force
366335// 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,' +
370339' closing connection.' ) ;
371340this . chunkedEncoding = false ;
372341this . shouldKeepAlive = false ;
@@ -377,13 +346,13 @@ function _storeHeader(firstLine, headers) {
377346this . _last = true ;
378347this . shouldKeepAlive = false ;
379348} else if ( ! state . connection ) {
380- var shouldSendKeepAlive = this . shouldKeepAlive &&
349+ const shouldSendKeepAlive = this . shouldKeepAlive &&
381350( state . contLen || this . useChunkedEncodingByDefault || this . agent ) ;
382351if ( shouldSendKeepAlive ) {
383- state . header += 'Connection: keep-alive\r\n' ;
352+ header += 'Connection: keep-alive\r\n' ;
384353} else {
385354this . _last = true ;
386- state . header += 'Connection: close\r\n' ;
355+ header += 'Connection: close\r\n' ;
387356}
388357}
389358
@@ -396,9 +365,9 @@ function _storeHeader(firstLine, headers) {
396365} else if ( ! state . trailer &&
397366! this . _removedContLen &&
398367typeof this . _contentLength === 'number' ) {
399- state . header += 'Content-Length: ' + this . _contentLength + CRLF ;
368+ header += 'Content-Length: ' + this . _contentLength + CRLF ;
400369} else if ( ! this . _removedTE ) {
401- state . header += 'Transfer-Encoding: chunked\r\n' ;
370+ header += 'Transfer-Encoding: chunked\r\n' ;
402371this . chunkedEncoding = true ;
403372} else {
404373// We should only be able to get here if both Content-Length and
@@ -416,18 +385,31 @@ function _storeHeader(firstLine, headers) {
416385throw new ERR_HTTP_TRAILER_INVALID ( ) ;
417386}
418387
419- this . _header = state . header + CRLF ;
388+ this . _header = header + CRLF ;
420389this . _headerSent = false ;
421390
422391// wait until the first body chunk, or close(), is sent to flush,
423392// UNLESS we're sending Expect: 100-continue.
424393if ( state . expect ) this . _send ( '' ) ;
425394}
426395
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 ( '; ' ) ;
430406}
407+ storeHeader ( self , state , key , value , validate ) ;
408+ }
409+
410+ function storeHeader ( self , state , key , value , validate ) {
411+ if ( validate )
412+ validateHeaderValue ( key , value ) ;
431413state . header += key + ': ' + escapeHeaderValue ( value ) + CRLF ;
432414matchHeader ( self , state , key , value ) ;
433415}
@@ -439,39 +421,47 @@ function matchHeader(self, state, field, value) {
439421switch ( field ) {
440422case 'connection' :
441423state . connection = true ;
424+ self . _removedConnection = false ;
442425if ( RE_CONN_CLOSE . test ( value ) )
443426self . _last = true ;
444427else
445428self . shouldKeepAlive = true ;
446429break ;
447430case 'transfer-encoding' :
448431state . te = true ;
432+ self . _removedTE = false ;
449433if ( RE_TE_CHUNKED . test ( value ) ) self . chunkedEncoding = true ;
450434break ;
451435case 'content-length' :
452436state . contLen = true ;
437+ self . _removedContLen = false ;
453438break ;
454439case 'date' :
455440case 'expect' :
456441case 'trailer' :
457- case 'upgrade' :
458442state [ field ] = true ;
459443break ;
460444}
461445}
462446
463- function validateHeader ( name , value ) {
464- let err ;
447+ function validateHeaderName ( name ) {
465448if ( 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 ) {
468458err = new ERR_HTTP_INVALID_HEADER_VALUE ( value , name ) ;
469459} else if ( checkInvalidHeaderChar ( value ) ) {
470460debug ( 'Header "%s" contains invalid characters' , name ) ;
471461err = new ERR_INVALID_CHAR ( 'header content' , name ) ;
472462}
473463if ( err !== undefined ) {
474- Error . captureStackTrace ( err , validateHeader ) ;
464+ Error . captureStackTrace ( err , validateHeaderValue ) ;
475465throw err ;
476466}
477467}
@@ -480,25 +470,14 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
480470if ( this . _header ) {
481471throw new ERR_HTTP_HEADERS_SENT ( 'set' ) ;
482472}
483- validateHeader ( name , value ) ;
473+ validateHeaderName ( name ) ;
474+ validateHeaderValue ( name , value ) ;
484475
485- if ( ! this [ outHeadersKey ] )
486- this [ outHeadersKey ] = { } ;
476+ let headers = this [ outHeadersKey ] ;
477+ if ( headers === null )
478+ this [ outHeadersKey ] = headers = Object . create ( null ) ;
487479
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 ] ;
502481} ;
503482
504483
@@ -507,18 +486,18 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
507486throw new ERR_INVALID_ARG_TYPE ( 'name' , 'string' , name ) ;
508487}
509488
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 )
514491return ;
515- return entry [ 1 ] ;
492+
493+ const entry = headers [ name . toLowerCase ( ) ] ;
494+ return entry && entry [ 1 ] ;
516495} ;
517496
518497
519498// Returns an array of the names of the current outgoing headers.
520499OutgoingMessage . prototype . getHeaderNames = function getHeaderNames ( ) {
521- return ( this [ outHeadersKey ] ? Object . keys ( this [ outHeadersKey ] ) : [ ] ) ;
500+ return this [ outHeadersKey ] !== null ? Object . keys ( this [ outHeadersKey ] ) : [ ] ;
522501} ;
523502
524503
@@ -543,7 +522,8 @@ OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
543522throw new ERR_INVALID_ARG_TYPE ( 'name' , 'string' , name ) ;
544523}
545524
546- return ! ! ( this [ outHeadersKey ] && this [ outHeadersKey ] [ name . toLowerCase ( ) ] ) ;
525+ return this [ outHeadersKey ] !== null &&
526+ ! ! this [ outHeadersKey ] [ name . toLowerCase ( ) ] ;
547527} ;
548528
549529
@@ -573,7 +553,7 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
573553break ;
574554}
575555
576- if ( this [ outHeadersKey ] ) {
556+ if ( this [ outHeadersKey ] !== null ) {
577557delete this [ outHeadersKey ] [ key ] ;
578558}
579559} ;
0 commit comments