1+ const onFinishedStream = require ( "on-finished" ) ;
2+
3+ const escapeHtml = require ( "./escapeHtml" ) ;
4+
15/** @typedef {import("../index.js").IncomingMessage } IncomingMessage */
26/** @typedef {import("../index.js").ServerResponse } ServerResponse */
37
@@ -88,6 +92,18 @@ function setHeaderForResponse(res, name, value) {
8892 res . setHeader ( name , value ) ;
8993}
9094
95+ /**
96+ * @template {ServerResponse} Response
97+ * @param {Response } res
98+ */
99+ function clearHeadersForResponse ( res ) {
100+ const headers = getHeaderNames ( res ) ;
101+
102+ for ( let i = 0 ; i < headers . length ; i ++ ) {
103+ res . removeHeader ( headers [ i ] ) ;
104+ }
105+ }
106+
91107/**
92108 * @template {ServerResponse} Response
93109 * @param {Response } res
@@ -108,6 +124,76 @@ function setStatusCode(res, code) {
108124 res . statusCode = code ;
109125}
110126
127+ /**
128+ * @param {import("fs").ReadStream } stream stream
129+ * @param {boolean } suppress do need suppress?
130+ * @returns {void }
131+ */
132+ function destroyStream ( stream , suppress ) {
133+ if ( typeof stream . destroy === "function" ) {
134+ stream . destroy ( ) ;
135+ }
136+
137+ if ( typeof stream . close === "function" ) {
138+ // Node.js core bug workaround
139+ stream . on (
140+ "open" ,
141+ /**
142+ * @this {import("fs").ReadStream}
143+ */
144+ function onOpenClose ( ) {
145+ // @ts -ignore
146+ if ( typeof this . fd === "number" ) {
147+ // actually close down the fd
148+ this . close ( ) ;
149+ }
150+ } ,
151+ ) ;
152+ }
153+
154+ if ( typeof stream . addListener === "function" && suppress ) {
155+ stream . removeAllListeners ( "error" ) ;
156+ stream . addListener ( "error" , ( ) => { } ) ;
157+ }
158+ }
159+
160+ /** @type {Record<number, string> } */
161+ const statuses = {
162+ 404 : "Not Found" ,
163+ 500 : "Internal Server Error" ,
164+ } ;
165+
166+ /**
167+ * @template {ServerResponse} Response
168+ * @param {Response } res response
169+ * @param {number } status status
170+ * @returns {void }
171+ */
172+ function sendError ( res , status ) {
173+ const msg = statuses [ status ] || String ( status ) ;
174+ const doc = `<!DOCTYPE html>
175+ <html lang="en">
176+ <head>
177+ <meta charset="utf-8">
178+ <title>Error</title>
179+ </head>
180+ <body>
181+ <pre>${ escapeHtml ( msg ) } </pre>
182+ </body>
183+ </html>` ;
184+
185+ // Clear existing headers
186+ clearHeadersForResponse ( res ) ;
187+ // Send basic response
188+ setStatusCode ( res , status ) ;
189+ setHeaderForResponse ( res , "Content-Type" , "text/html; charset=UTF-8" ) ;
190+ setHeaderForResponse ( res , "Content-Length" , Buffer . byteLength ( doc ) ) ;
191+ setHeaderForResponse ( res , "Content-Security-Policy" , "default-src 'none'" ) ;
192+ setHeaderForResponse ( res , "X-Content-Type-Options" , "nosniff" ) ;
193+
194+ res . end ( doc ) ;
195+ }
196+
111197/**
112198 * @template {IncomingMessage} Request
113199 * @template {ServerResponse} Response
@@ -125,13 +211,42 @@ function send(req, res, bufferOtStream, byteLength) {
125211
126212 if ( req . method === "HEAD" ) {
127213 res . end ( ) ;
128-
129214 return ;
130215 }
131216
132217 /** @type {import("fs").ReadStream } */
133218 ( bufferOtStream ) . pipe ( res ) ;
134219
220+ // Cleanup
221+ const cleanup = ( ) => {
222+ destroyStream (
223+ /** @type {import("fs").ReadStream } */ ( bufferOtStream ) ,
224+ true ,
225+ ) ;
226+ } ;
227+
228+ // Response finished, cleanup
229+ onFinishedStream ( res , cleanup ) ;
230+
231+ // error handling
232+ /** @type {import("fs").ReadStream } */
233+ ( bufferOtStream ) . on ( "error" , ( error ) => {
234+ // clean up stream early
235+ cleanup ( ) ;
236+
237+ // Handle Error
238+ switch ( /** @type {NodeJS.ErrnoException } */ ( error ) . code ) {
239+ case "ENAMETOOLONG" :
240+ case "ENOENT" :
241+ case "ENOTDIR" :
242+ sendError ( res , 404 ) ;
243+ break ;
244+ default :
245+ sendError ( res , 500 ) ;
246+ break ;
247+ }
248+ } ) ;
249+
135250 return ;
136251 }
137252
@@ -141,7 +256,6 @@ function send(req, res, bufferOtStream, byteLength) {
141256 ) {
142257 /** @type {Response & ExpectedResponse } */
143258 ( res ) . send ( bufferOtStream ) ;
144-
145259 return ;
146260 }
147261
0 commit comments