@ -11,18 +11,8 @@ const checkIsHttpToken = common._checkIsHttpToken;
const checkInvalidHeaderChar = common . _ checkInvalidHeaderChar ;
const checkInvalidHeaderChar = common . _ checkInvalidHeaderChar ;
const CRLF = common . CRLF ;
const CRLF = common . CRLF ;
const trfrEncChunkExpression = common . chunkExpression ;
const debug = common . debug ;
const debug = common . debug ;
const upgradeExpression = /^Upgrade$/i ;
const transferEncodingExpression = /^Transfer-Encoding$/i ;
const contentLengthExpression = /^Content-Length$/i ;
const dateExpression = /^Date$/i ;
const expectExpression = /^Expect$/i ;
const trailerExpression = /^Trailer$/i ;
const connectionExpression = /^Connection$/i ;
const connCloseExpression = /(^|\W)close(\W|$)/i ;
const connUpgradeExpression = /(^|\W)upgrade(\W|$)/i ;
const automaticHeaders = {
const automaticHeaders = {
connection : true ,
connection : true ,
@ -31,6 +21,10 @@ const automaticHeaders = {
date : true
date : true
} ;
} ;
var RE_FIELDS = new RegExp ( '^(?:Connection|Transfer-Encoding|Content-Length|' +
'Date|Expect|Trailer|Upgrade)$' , 'i' ) ;
var RE_CONN_VALUES = /(?:^|\W)close|upgrade(?:$|\W)/ig ;
var RE_TE_CHUNKED = common . chunkExpression ;
var dateCache ;
var dateCache ;
function utcDate ( ) {
function utcDate ( ) {
@ -83,7 +77,6 @@ function OutgoingMessage() {
this . connection = null ;
this . connection = null ;
this . _ header = null ;
this . _ header = null ;
this . _ headers = null ;
this . _ headers = null ;
this . _ headerNames = { } ;
this . _ onPendingData = null ;
this . _ onPendingData = null ;
}
}
@ -198,57 +191,72 @@ function _storeHeader(firstLine, headers) {
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
var state = {
var state = {
sentConnectionHeader : false ,
connection : false ,
sentConne cti onUpgrade: false ,
con nUpgrade : false ,
sentContentLengthHeader : false ,
contLen : false ,
sen tTransf erEncodingHeader : false ,
te : false ,
sentDateHeader : false ,
date : false ,
s entE xpect: false ,
expect : false ,
sen tT railer: false ,
trailer : false ,
sentU pgrade: false ,
u pgrade: false ,
messageH eader: firstLine
h eader: firstLine
} ;
} ;
var i ;
var j ;
var field ;
var field ;
var key ;
var value ;
var value ;
if ( headers instanceof Array ) {
var i ;
for ( i = 0 ; i < headers . length ; ++ i ) {
var j ;
if ( headers === this . _ headers ) {
for ( key in headers ) {
var entry = headers [ key ] ;
field = entry [ 0 ] ;
value = entry [ 1 ] ;
if ( value instanceof Array ) {
for ( j = 0 ; j < value . length ; j ++ ) {
storeHeader ( this , state , field , value [ j ] , false ) ;
}
} else {
storeHeader ( this , state , field , value , false ) ;
}
}
} else if ( headers instanceof Array ) {
for ( i = 0 ; i < headers . length ; i ++ ) {
field = headers [ i ] [ 0 ] ;
field = headers [ i ] [ 0 ] ;
value = headers [ i ] [ 1 ] ;
value = headers [ i ] [ 1 ] ;
if ( value instanceof Array ) {
if ( value instanceof Array ) {
for ( j = 0 ; j < value . length ; j ++ ) {
for ( j = 0 ; j < value . length ; j ++ ) {
storeHeader ( this , state , field , value [ j ] ) ;
storeHeader ( this , state , field , value [ j ] , true ) ;
}
}
} else {
} else {
storeHeader ( this , state , field , value ) ;
storeHeader ( this , state , field , value , true ) ;
}
}
}
}
} else if ( headers ) {
} else if ( headers ) {
var keys = Object . keys ( headers ) ;
var keys = Object . keys ( headers ) ;
for ( i = 0 ; i < keys . length ; ++ i ) {
for ( i = 0 ; i < keys . length ; i ++ ) {
field = keys [ i ] ;
field = keys [ i ] ;
value = headers [ field ] ;
value = headers [ field ] ;
if ( value instanceof Array ) {
if ( value instanceof Array ) {
for ( j = 0 ; j < value . length ; j ++ ) {
for ( j = 0 ; j < value . length ; j ++ ) {
storeHeader ( this , state , field , value [ j ] ) ;
storeHeader ( this , state , field , value [ j ] , true ) ;
}
}
} else {
} else {
storeHeader ( this , state , field , value ) ;
storeHeader ( this , state , field , value , true ) ;
}
}
}
}
}
}
// Are we upgrading the connection?
// Are we upgrading the connection?
if ( state . sentConne cti onUpgrade && state . sentU pgrade)
if ( state . con nUpgrade && state . u pgrade)
this . upgrading = true ;
this . upgrading = true ;
// Date header
// Date header
if ( this . sendDate && ! state . sentDateHeader ) {
if ( this . sendDate && ! state . date ) {
state . messageH eader += 'Date: ' + utcDate ( ) + CRLF ;
state . h eader += 'Date: ' + utcDate ( ) + CRLF ;
}
}
// Force the connection to close when the response is a 204 No Content or
// Force the connection to close when the response is a 204 No Content or
@ -274,33 +282,30 @@ function _storeHeader(firstLine, headers) {
if ( this . _ removedHeader . connection ) {
if ( this . _ removedHeader . connection ) {
this . _ last = true ;
this . _ last = true ;
this . shouldKeepAlive = false ;
this . shouldKeepAlive = false ;
} else if ( ! state . sentConnectionHeader ) {
} else if ( ! state . connection ) {
var shouldSendKeepAlive = this . shouldKeepAlive &&
var shouldSendKeepAlive = this . shouldKeepAlive &&
( state . sentContentLengthHeader ||
( state . contLen || this . useChunkedEncodingByDefault || this . agent ) ;
this . useChunkedEncodingByDefault ||
this . agent ) ;
if ( shouldSendKeepAlive ) {
if ( shouldSendKeepAlive ) {
state . messageH eader += 'Connection: keep-alive\r\n' ;
state . header += 'Connection: keep-alive\r\n' ;
} else {
} else {
this . _ last = true ;
this . _ last = true ;
state . messageH eader += 'Connection: close\r\n' ;
state . h eader += 'Connection: close\r\n' ;
}
}
}
}
if ( ! state . sentContentLengthHeader && ! state . sen tTransf erEncodingHeader ) {
if ( ! state . contLen && ! state . te ) {
if ( ! this . _ hasBody ) {
if ( ! this . _ hasBody ) {
// Make sure we don't end the 0\r\n\r\n at the end of the message.
// Make sure we don't end the 0\r\n\r\n at the end of the message.
this . chunkedEncoding = false ;
this . chunkedEncoding = false ;
} else if ( ! this . useChunkedEncodingByDefault ) {
} else if ( ! this . useChunkedEncodingByDefault ) {
this . _ last = true ;
this . _ last = true ;
} else {
} else {
if ( ! state . sentTrailer &&
! this . _ removedHeader [ 'content-length' ] &&
! this . _ removedHeader [ 'content-length' ] &&
if ( ! state . trailer &&
typeof this . _ contentLength === 'number' ) {
typeof this . _ contentLength === 'number' ) {
state . messageHeader += 'Content-Length: ' + this . _ contentLength +
'\r\n' ;
} else if ( ! this . _ removedHeader [ 'transfer-encoding' ] ) {
} else if ( ! this . _ removedHeader [ 'transfer-encoding' ] ) {
state . messageHeader += 'Transfer-Encoding: chunked\r\n' ;
state . header += 'Content-Length: ' + this . _ contentLength + CRLF ;
state . header += 'Transfer-Encoding: chunked\r\n' ;
this . chunkedEncoding = true ;
this . chunkedEncoding = true ;
} else {
} else {
// We should only be able to get here if both Content-Length and
// We should only be able to get here if both Content-Length and
@ -311,70 +316,94 @@ function _storeHeader(firstLine, headers) {
}
}
}
}
this . _ header = state . messageH eader + CRLF ;
this . _ header = state . h eader + CRLF ;
this . _ headerSent = false ;
this . _ headerSent = false ;
// wait until the first body chunk, or close(), is sent to flush,
// wait until the first body chunk, or close(), is sent to flush,
// UNLESS we're sending Expect: 100-continue.
// UNLESS we're sending Expect: 100-continue.
if ( state . s entE xpect) this . _ send ( '' ) ;
if ( state . expect ) this . _ send ( '' ) ;
}
}
function storeHeader ( self , state , field , value ) {
function storeHeader ( self , state , field , value , validate ) {
if ( ! checkIsHttpToken ( field ) ) {
if ( validate ) {
throw new TypeError (
if ( ! checkIsHttpToken ( field ) ) {
'Header name must be a valid HTTP Token ["' + field + '"]' ) ;
throw new TypeError (
}
'Header name must be a valid HTTP Token ["' + field + '"]' ) ;
if ( checkInvalidHeaderChar ( value ) ) {
}
debug ( 'Header "%s" contains invalid characters' , field ) ;
if ( value === undefined ) {
throw new TypeError ( 'The header content contains invalid characters' ) ;
throw new Error ( 'Header "%s" value must not be undefined' , field ) ;
}
} else if ( checkInvalidHeaderChar ( value ) ) {
state . messageHeader += field + ': ' + escapeHeaderValue ( value ) + CRLF ;
debug ( 'Header "%s" contains invalid characters' , field ) ;
throw new TypeError ( 'The header content contains invalid characters' ) ;
if ( connectionExpression . test ( field ) ) {
state . sentConnectionHeader = true ;
if ( connCloseExpression . test ( value ) ) {
self . _ last = true ;
} else {
self . shouldKeepAlive = true ;
}
}
if ( connUpgradeExpression . test ( value ) )
state . sentConnectionUpgrade = true ;
} else if ( transferEncodingExpression . test ( field ) ) {
state . sentTransferEncodingHeader = true ;
if ( trfrEncChunkExpression . test ( value ) ) self . chunkedEncoding = true ;
} else if ( contentLengthExpression . test ( field ) ) {
state . sentContentLengthHeader = true ;
} else if ( dateExpression . test ( field ) ) {
state . sentDateHeader = true ;
} else if ( expectExpression . test ( field ) ) {
state . sentExpect = true ;
} else if ( trailerExpression . test ( field ) ) {
state . sentTrailer = true ;
} else if ( upgradeExpression . test ( field ) ) {
state . sentUpgrade = true ;
}
}
state . header += field + ': ' + escapeHeaderValue ( value ) + CRLF ;
matchHeader ( self , state , field , value ) ;
}
}
function matchConnValue ( self , state , value ) {
var sawClose = false ;
var m = RE_CONN_VALUES . exec ( value ) ;
while ( m ) {
if ( m [ 0 ] . length === 5 )
sawClose = true ;
else
state . connUpgrade = true ;
m = RE_CONN_VALUES . exec ( value ) ;
}
if ( sawClose )
self . _ last = true ;
else
self . shouldKeepAlive = true ;
}
OutgoingMessage . prototype . setHeader = function setHeader ( name , value ) {
function matchHeader ( self , state , field , value ) {
var m = RE_FIELDS . exec ( field ) ;
if ( ! m )
return ;
var len = m [ 0 ] . length ;
if ( len === 10 ) {
state . connection = true ;
matchConnValue ( self , state , value ) ;
} else if ( len === 17 ) {
state . te = true ;
if ( RE_TE_CHUNKED . test ( value ) ) self . chunkedEncoding = true ;
} else if ( len === 14 ) {
state . contLen = true ;
} else if ( len === 4 ) {
state . date = true ;
} else if ( len === 6 ) {
state . expect = true ;
} else if ( len === 7 ) {
var ch = m [ 0 ] . charCodeAt ( 0 ) ;
if ( ch === 85 || ch === 117 )
state . upgrade = true ;
else
state . trailer = true ;
}
}
function validateHeader ( msg , name , value ) {
if ( ! checkIsHttpToken ( name ) )
if ( ! checkIsHttpToken ( name ) )
throw new TypeError (
throw new TypeError (
'Header name must be a valid HTTP Token ["' + name + '"]' ) ;
'Header name must be a valid HTTP Token ["' + name + '"]' ) ;
if ( value === undefined )
if ( value === undefined )
throw new Error ( '"value" required in setHeader("' + name + '", value)' ) ;
throw new Error ( '"value" required in setHeader("' + name + '", value)' ) ;
if ( this . _ header )
if ( msg . _ header )
throw new Error ( 'Can\'t set headers after they are sent.' ) ;
throw new Error ( 'Can\'t set headers after they are sent.' ) ;
if ( checkInvalidHeaderChar ( value ) ) {
if ( checkInvalidHeaderChar ( value ) ) {
debug ( 'Header "%s" contains invalid characters' , name ) ;
debug ( 'Header "%s" contains invalid characters' , name ) ;
throw new TypeError ( 'The header content contains invalid characters' ) ;
throw new TypeError ( 'The header content contains invalid characters' ) ;
}
}
if ( this . _ headers === null )
}
OutgoingMessage . prototype . setHeader = function setHeader ( name , value ) {
validateHeader ( this , name , value ) ;
if ( ! this . _ headers )
this . _ headers = { } ;
this . _ headers = { } ;
var key = name . toLowerCase ( ) ;
const key = name . toLowerCase ( ) ;
this . _ headers [ key ] = value ;
this . _ headers [ key ] = [ name , value ] ;
this . _ headerNames [ key ] = name ;
if ( automaticHeaders [ key ] )
if ( automaticHeaders [ key ] )
this . _ removedHeader [ key ] = false ;
this . _ removedHeader [ key ] = false ;
@ -388,7 +417,10 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
if ( ! this . _ headers ) return ;
if ( ! this . _ headers ) return ;
return this . _ headers [ name . toLowerCase ( ) ] ;
var entry = this . _ headers [ name . toLowerCase ( ) ] ;
if ( ! entry )
return ;
return entry [ 1 ] ;
} ;
} ;
@ -410,30 +442,10 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
if ( this . _ headers ) {
if ( this . _ headers ) {
delete this . _ headers [ key ] ;
delete this . _ headers [ key ] ;
delete this . _ headerNames [ key ] ;
}
}
} ;
} ;
OutgoingMessage . prototype . _ renderHeaders = function _ renderHeaders ( ) {
if ( this . _ header ) {
throw new Error ( 'Can\'t render headers after they are sent to the client' ) ;
}
var headersMap = this . _ headers ;
if ( ! headersMap ) return { } ;
var headers = { } ;
var keys = Object . keys ( headersMap ) ;
var headerNames = this . _ headerNames ;
for ( var i = 0 , l = keys . length ; i < l ; i ++ ) {
var key = keys [ i ] ;
headers [ headerNames [ key ] ] = headersMap [ key ] ;
}
return headers ;
} ;
OutgoingMessage . prototype . _ implicitHeader = function _ implicitHeader ( ) {
OutgoingMessage . prototype . _ implicitHeader = function _ implicitHeader ( ) {
throw new Error ( '_implicitHeader() method is not implemented' ) ;
throw new Error ( '_implicitHeader() method is not implemented' ) ;
} ;
} ;
@ -492,6 +504,7 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) {
this . connection . cork ( ) ;
this . connection . cork ( ) ;
process . nextTick ( connectionCorkNT , this . connection ) ;
process . nextTick ( connectionCorkNT , this . connection ) ;
}
}
this . _ send ( len . toString ( 16 ) , 'latin1' , null ) ;
this . _ send ( len . toString ( 16 ) , 'latin1' , null ) ;
this . _ send ( crlf_buf , null , null ) ;
this . _ send ( crlf_buf , null , null ) ;
this . _ send ( chunk , encoding , null ) ;
this . _ send ( chunk , encoding , null ) ;