@@ -21,9 +21,12 @@ const {
2121 validateQuicClientSessionOptions,
2222 validateQuicSocketOptions,
2323} = require ( 'internal/quic/util' ) ;
24+ const { validateNumber } = require ( 'internal/validators' ) ;
2425const util = require ( 'util' ) ;
2526const assert = require ( 'internal/assert' ) ;
2627const EventEmitter = require ( 'events' ) ;
28+ const fs = require ( 'fs' ) ;
29+ const fsPromisesInternal = require ( 'internal/fs/promises' ) ;
2730const { Duplex } = require ( 'stream' ) ;
2831const {
2932 createSecureContext : _createSecureContext
@@ -32,7 +35,7 @@ const {
3235 translatePeerCertificate
3336} = require ( '_tls_common' ) ;
3437const {
35- defaultTriggerAsyncIdScope, // eslint-disable-line no-unused-vars
38+ defaultTriggerAsyncIdScope,
3639 symbols : {
3740 async_id_symbol,
3841 owner_symbol,
@@ -52,14 +55,15 @@ const {
5255
5356const {
5457 ShutdownWrap,
55- kReadBytesOrError, // eslint-disable-line no-unused-vars
56- streamBaseState // eslint-disable-line no-unused-vars
58+ kReadBytesOrError,
59+ streamBaseState
5760} = internalBinding ( 'stream_wrap' ) ;
5861
5962const {
6063 codes : {
6164 ERR_INVALID_ARG_TYPE ,
6265 ERR_INVALID_ARG_VALUE ,
66+ ERR_INVALID_OPT_VALUE ,
6367 ERR_INVALID_CALLBACK ,
6468 ERR_OUT_OF_RANGE ,
6569 ERR_QUIC_ERROR ,
@@ -78,6 +82,10 @@ const {
7882 exceptionWithHostPort
7983} = require ( 'internal/errors' ) ;
8084
85+ const { FileHandle } = internalBinding ( 'fs' ) ;
86+ const { StreamPipe } = internalBinding ( 'stream_pipe' ) ;
87+ const { UV_EOF } = internalBinding ( 'uv' ) ;
88+
8189const {
8290 QuicSocket : QuicSocketHandle ,
8391 initSecureContext,
@@ -2253,6 +2261,87 @@ class QuicStream extends Duplex {
22532261 streamOnResume . call ( this ) ;
22542262 }
22552263
2264+ sendFile ( path , options = { } ) {
2265+ fs . open ( path , 'r' , QuicStream . #onFileOpened. bind ( this , options ) ) ;
2266+ }
2267+
2268+ static #onFileOpened = function ( options , err , fd ) {
2269+ const onError = options . onError ;
2270+ if ( err ) {
2271+ if ( onError ) {
2272+ this . close ( ) ;
2273+ onError ( err ) ;
2274+ } else {
2275+ this . destroy ( err ) ;
2276+ }
2277+ return ;
2278+ }
2279+
2280+ if ( this . destroyed || this . closed ) {
2281+ fs . close ( fd , ( err ) => { if ( err ) throw err ; } ) ;
2282+ return ;
2283+ }
2284+
2285+ this . sendFD ( fd , options , true ) ;
2286+ }
2287+
2288+ sendFD ( fd , { offset = - 1 , length = - 1 } = { } , ownsFd = false ) {
2289+ if ( this . destroyed || this . #closed)
2290+ return ;
2291+
2292+ if ( typeof offset !== 'number' )
2293+ throw new ERR_INVALID_OPT_VALUE ( 'options.offset' , offset ) ;
2294+ if ( typeof length !== 'number' )
2295+ throw new ERR_INVALID_OPT_VALUE ( 'options.length' , length ) ;
2296+
2297+ if ( fd instanceof fsPromisesInternal . FileHandle )
2298+ fd = fd . fd ;
2299+ else if ( typeof fd !== 'number' )
2300+ throw new ERR_INVALID_ARG_TYPE ( 'fd' , [ 'number' , 'FileHandle' ] , fd ) ;
2301+
2302+ this [ kUpdateTimer ] ( ) ;
2303+ this . ownsFd = ownsFd ;
2304+
2305+ // Close the writable side of the stream, but only as far as the writable
2306+ // stream implementation is concerned.
2307+ this . _final = null ;
2308+ this . end ( ) ;
2309+
2310+ defaultTriggerAsyncIdScope ( this [ async_id_symbol ] ,
2311+ QuicStream . #startFilePipe,
2312+ this , fd , offset , length ) ;
2313+ }
2314+
2315+ static #startFilePipe = ( stream , fd , offset , length ) => {
2316+ const handle = new FileHandle ( fd , offset , length ) ;
2317+ handle . onread = QuicStream . #onPipedFileHandleRead;
2318+ handle . stream = stream ;
2319+
2320+ const pipe = new StreamPipe ( handle , stream [ kHandle ] ) ;
2321+ pipe . onunpipe = QuicStream . #onFileUnpipe;
2322+ pipe . start ( ) ;
2323+
2324+ // Exact length of the file doesn't matter here, since the
2325+ // stream is closing anyway - just use 1 to signify that
2326+ // a write does exist
2327+ stream [ kTrackWriteState ] ( stream , 1 ) ;
2328+ }
2329+
2330+ static #onFileUnpipe = function ( ) { // Called on the StreamPipe instance.
2331+ const stream = this . sink [ owner_symbol ] ;
2332+ if ( stream . ownsFd )
2333+ this . source . close ( ) . catch ( stream . destroy . bind ( stream ) ) ;
2334+ else
2335+ this . source . releaseFD ( ) ;
2336+ }
2337+
2338+ static #onPipedFileHandleRead = function ( ) {
2339+ const err = streamBaseState [ kReadBytesOrError ] ;
2340+ if ( err < 0 && err !== UV_EOF ) {
2341+ this . stream . destroy ( errnoException ( err , 'sendFD' ) ) ;
2342+ }
2343+ }
2344+
22562345 get resetReceived ( ) {
22572346 return ( this . #resetCode !== undefined ) ?
22582347 { code : this . #resetCode | 0 , finalSize : this . #resetFinalSize | 0 } :
0 commit comments