@@ -1541,7 +1541,7 @@ function processHeaders(headers) {
15411541 return headers ;
15421542}
15431543
1544- function processRespondWithFD ( fd , headers ) {
1544+ function processRespondWithFD ( fd , headers , offset = 0 , length = - 1 ) {
15451545 const session = this [ kSession ] ;
15461546 const state = this [ kState ] ;
15471547 state . headersSent = true ;
@@ -1551,7 +1551,7 @@ function processRespondWithFD(fd, headers) {
15511551
15521552 const handle = session [ kHandle ] ;
15531553 const ret =
1554- handle . submitFile ( this [ kID ] , fd , headers ) ;
1554+ handle . submitFile ( this [ kID ] , fd , headers , offset , length ) ;
15551555 let err ;
15561556 switch ( ret ) {
15571557 case NGHTTP2_ERR_NOMEM :
@@ -1575,26 +1575,71 @@ function doSendFD(session, options, fd, headers, err, stat) {
15751575 process . nextTick ( ( ) => this . emit ( 'error' , err ) ) ;
15761576 return ;
15771577 }
1578+
1579+ const statOptions = {
1580+ offset : options . offset !== undefined ? options . offset : 0 ,
1581+ length : options . length !== undefined ? options . length : - 1
1582+ } ;
1583+
1584+ if ( typeof options . statCheck === 'function' &&
1585+ options . statCheck . call ( this , stat , headers , statOptions ) === false ) {
1586+ return ;
1587+ }
1588+
1589+ const headersList = mapToHeaders ( headers ,
1590+ assertValidPseudoHeaderResponse ) ;
1591+ if ( ! Array . isArray ( headersList ) ) {
1592+ process . nextTick ( ( ) => this . emit ( 'error' , headersList ) ) ;
1593+ }
1594+
1595+ processRespondWithFD . call ( this , fd , headersList ,
1596+ statOptions . offset ,
1597+ statOptions . length ) ;
1598+ }
1599+
1600+ function doSendFileFD ( session , options , fd , headers , err , stat ) {
1601+ if ( this . destroyed || session . destroyed ) {
1602+ abort ( this ) ;
1603+ return ;
1604+ }
1605+ if ( err ) {
1606+ process . nextTick ( ( ) => this . emit ( 'error' , err ) ) ;
1607+ return ;
1608+ }
15781609 if ( ! stat . isFile ( ) ) {
15791610 err = new errors . Error ( 'ERR_HTTP2_SEND_FILE' ) ;
15801611 process . nextTick ( ( ) => this . emit ( 'error' , err ) ) ;
15811612 return ;
15821613 }
15831614
1615+ const statOptions = {
1616+ offset : options . offset !== undefined ? options . offset : 0 ,
1617+ length : options . length !== undefined ? options . length : - 1
1618+ } ;
1619+
15841620 // Set the content-length by default
1585- headers [ HTTP2_HEADER_CONTENT_LENGTH ] = stat . size ;
15861621 if ( typeof options . statCheck === 'function' &&
15871622 options . statCheck . call ( this , stat , headers ) === false ) {
15881623 return ;
15891624 }
15901625
1626+ statOptions . length =
1627+ statOptions . length < 0 ? stat . size - ( + statOptions . offset ) :
1628+ Math . min ( stat . size - ( + statOptions . offset ) ,
1629+ statOptions . length ) ;
1630+
1631+ if ( headers [ HTTP2_HEADER_CONTENT_LENGTH ] === undefined )
1632+ headers [ HTTP2_HEADER_CONTENT_LENGTH ] = statOptions . length ;
1633+
15911634 const headersList = mapToHeaders ( headers ,
15921635 assertValidPseudoHeaderResponse ) ;
15931636 if ( ! Array . isArray ( headersList ) ) {
1594- throw headersList ;
1637+ process . nextTick ( ( ) => this . emit ( 'error' , headersList ) ) ;
15951638 }
15961639
1597- processRespondWithFD . call ( this , fd , headersList ) ;
1640+ processRespondWithFD . call ( this , fd , headersList ,
1641+ options . offset ,
1642+ options . length ) ;
15981643}
15991644
16001645function afterOpen ( session , options , headers , err , fd ) {
@@ -1609,7 +1654,7 @@ function afterOpen(session, options, headers, err, fd) {
16091654 }
16101655 state . fd = fd ;
16111656
1612- fs . fstat ( fd , doSendFD . bind ( this , session , options , fd , headers ) ) ;
1657+ fs . fstat ( fd , doSendFileFD . bind ( this , session , options , fd , headers ) ) ;
16131658}
16141659
16151660
@@ -1786,12 +1831,12 @@ class ServerHttp2Stream extends Http2Stream {
17861831 }
17871832
17881833 // Initiate a response using an open FD. Note that there are fewer
1789- // protections with this approach. For one, the fd is not validated.
1790- // In respondWithFile, the file is checked to make sure it is a
1834+ // protections with this approach. For one, the fd is not validated by
1835+ // default. In respondWithFile, the file is checked to make sure it is a
17911836 // regular file, here the fd is passed directly. If the underlying
17921837 // mechanism is not able to read from the fd, then the stream will be
17931838 // reset with an error code.
1794- respondWithFD ( fd , headers ) {
1839+ respondWithFD ( fd , headers , options ) {
17951840 const session = this [ kSession ] ;
17961841 if ( this . destroyed )
17971842 throw new errors . Error ( 'ERR_HTTP2_INVALID_STREAM' ) ;
@@ -1803,6 +1848,26 @@ class ServerHttp2Stream extends Http2Stream {
18031848 if ( state . headersSent )
18041849 throw new errors . Error ( 'ERR_HTTP2_HEADERS_SENT' ) ;
18051850
1851+ assertIsObject ( options , 'options' ) ;
1852+ options = Object . assign ( Object . create ( null ) , options ) ;
1853+
1854+ if ( options . offset !== undefined && typeof options . offset !== 'number' )
1855+ throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' ,
1856+ 'offset' ,
1857+ options . offset ) ;
1858+
1859+ if ( options . length !== undefined && typeof options . length !== 'number' )
1860+ throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' ,
1861+ 'length' ,
1862+ options . length ) ;
1863+
1864+ if ( options . statCheck !== undefined &&
1865+ typeof options . statCheck !== 'function' ) {
1866+ throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' ,
1867+ 'statCheck' ,
1868+ options . statCheck ) ;
1869+ }
1870+
18061871 if ( typeof fd !== 'number' )
18071872 throw new errors . TypeError ( 'ERR_INVALID_ARG_TYPE' ,
18081873 'fd' , 'number' ) ;
@@ -1816,13 +1881,20 @@ class ServerHttp2Stream extends Http2Stream {
18161881 throw new errors . Error ( 'ERR_HTTP2_PAYLOAD_FORBIDDEN' , statusCode ) ;
18171882 }
18181883
1884+ if ( options . statCheck !== undefined ) {
1885+ fs . fstat ( fd , doSendFD . bind ( this , session , options , fd , headers ) ) ;
1886+ return ;
1887+ }
1888+
18191889 const headersList = mapToHeaders ( headers ,
18201890 assertValidPseudoHeaderResponse ) ;
18211891 if ( ! Array . isArray ( headersList ) ) {
1822- throw headersList ;
1892+ process . nextTick ( ( ) => this . emit ( 'error' , headersList ) ) ;
18231893 }
18241894
1825- processRespondWithFD . call ( this , fd , headersList ) ;
1895+ processRespondWithFD . call ( this , fd , headersList ,
1896+ options . offset ,
1897+ options . length ) ;
18261898 }
18271899
18281900 // Initiate a file response on this Http2Stream. The path is passed to
@@ -1847,6 +1919,16 @@ class ServerHttp2Stream extends Http2Stream {
18471919 assertIsObject ( options , 'options' ) ;
18481920 options = Object . assign ( Object . create ( null ) , options ) ;
18491921
1922+ if ( options . offset !== undefined && typeof options . offset !== 'number' )
1923+ throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' ,
1924+ 'offset' ,
1925+ options . offset ) ;
1926+
1927+ if ( options . length !== undefined && typeof options . length !== 'number' )
1928+ throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' ,
1929+ 'length' ,
1930+ options . length ) ;
1931+
18501932 if ( options . statCheck !== undefined &&
18511933 typeof options . statCheck !== 'function' ) {
18521934 throw new errors . TypeError ( 'ERR_INVALID_OPT_VALUE' ,
0 commit comments