@@ -7,8 +7,6 @@ const onFinishedStream = require("on-finished");
77const getFilenameFromUrl = require ( "./utils/getFilenameFromUrl" ) ;
88const { setStatusCode, send, pipe } = require ( "./utils/compatibleAPI" ) ;
99const ready = require ( "./utils/ready" ) ;
10- const escapeHtml = require ( "./utils/escapeHtml" ) ;
11- const etag = require ( "./utils/etag" ) ;
1210const parseTokenList = require ( "./utils/parseTokenList" ) ;
1311
1412/** @typedef {import("./index.js").NextFunction } NextFunction */
@@ -33,7 +31,7 @@ function getValueContentRangeHeader(type, size, range) {
3331 * Parse an HTTP Date into a number.
3432 *
3533 * @param {string } date
36- * @private
34+ * @returns { number }
3735 */
3836function parseHttpDate ( date ) {
3937 const timestamp = date && Date . parse ( date ) ;
@@ -140,6 +138,8 @@ function wrapper(context) {
140138 * @returns {void }
141139 */
142140 function sendError ( status , options ) {
141+ // eslint-disable-next-line global-require
142+ const escapeHtml = require ( "./utils/escapeHtml" ) ;
143143 const content = statuses [ status ] || String ( status ) ;
144144 let document = `<!DOCTYPE html>
145145<html lang="en">
@@ -201,17 +201,21 @@ function wrapper(context) {
201201 }
202202
203203 function isPreconditionFailure ( ) {
204- const match = req . headers [ "if-match" ] ;
205-
206- if ( match ) {
207- // eslint-disable-next-line no-shadow
204+ // if-match
205+ const ifMatch = req . headers [ "if-match" ] ;
206+
207+ // A recipient MUST ignore If-Unmodified-Since if the request contains
208+ // an If-Match header field; the condition in If-Match is considered to
209+ // be a more accurate replacement for the condition in
210+ // If-Unmodified-Since, and the two are only combined for the sake of
211+ // interoperating with older intermediaries that might not implement If-Match.
212+ if ( ifMatch ) {
208213 const etag = res . getHeader ( "ETag" ) ;
209214
210215 return (
211216 ! etag ||
212- ( match !== "*" &&
213- parseTokenList ( match ) . every (
214- // eslint-disable-next-line no-shadow
217+ ( ifMatch !== "*" &&
218+ parseTokenList ( ifMatch ) . every (
215219 ( match ) =>
216220 match !== etag &&
217221 match !== `W/${ etag } ` &&
@@ -220,6 +224,23 @@ function wrapper(context) {
220224 ) ;
221225 }
222226
227+ // if-unmodified-since
228+ const ifUnmodifiedSince = req . headers [ "if-unmodified-since" ] ;
229+
230+ if ( ifUnmodifiedSince ) {
231+ const unmodifiedSince = parseHttpDate ( ifUnmodifiedSince ) ;
232+
233+ // A recipient MUST ignore the If-Unmodified-Since header field if the
234+ // received field-value is not a valid HTTP-date.
235+ if ( ! isNaN ( unmodifiedSince ) ) {
236+ const lastModified = parseHttpDate (
237+ /** @type {string } */ ( res . getHeader ( "Last-Modified" ) ) ,
238+ ) ;
239+
240+ return isNaN ( lastModified ) || lastModified > unmodifiedSince ;
241+ }
242+ }
243+
223244 return false ;
224245 }
225246
@@ -288,9 +309,17 @@ function wrapper(context) {
288309
289310 if ( modifiedSince ) {
290311 const lastModified = resHeaders [ "last-modified" ] ;
312+ const parsedHttpDate = parseHttpDate ( modifiedSince ) ;
313+
314+ // A recipient MUST ignore the If-Modified-Since header field if the
315+ // received field-value is not a valid HTTP-date, or if the request
316+ // method is neither GET nor HEAD.
317+ if ( isNaN ( parsedHttpDate ) ) {
318+ return true ;
319+ }
320+
291321 const modifiedStale =
292- ! lastModified ||
293- ! ( parseHttpDate ( lastModified ) <= parseHttpDate ( modifiedSince ) ) ;
322+ ! lastModified || ! ( parseHttpDate ( lastModified ) <= parsedHttpDate ) ;
294323
295324 if ( modifiedStale ) {
296325 return false ;
@@ -300,6 +329,38 @@ function wrapper(context) {
300329 return true ;
301330 }
302331
332+ function isRangeFresh ( ) {
333+ const ifRange =
334+ /** @type {string | undefined } */
335+ ( req . headers [ "if-range" ] ) ;
336+
337+ if ( ! ifRange ) {
338+ return true ;
339+ }
340+
341+ // if-range as etag
342+ if ( ifRange . indexOf ( '"' ) !== - 1 ) {
343+ const etag = /** @type {string | undefined } */ ( res . getHeader ( "ETag" ) ) ;
344+
345+ if ( ! etag ) {
346+ return true ;
347+ }
348+
349+ return Boolean ( etag && ifRange . indexOf ( etag ) !== - 1 ) ;
350+ }
351+
352+ // if-range as modified date
353+ const lastModified =
354+ /** @type {string | undefined } */
355+ ( res . getHeader ( "Last-Modified" ) ) ;
356+
357+ if ( ! lastModified ) {
358+ return true ;
359+ }
360+
361+ return parseHttpDate ( lastModified ) <= parseHttpDate ( ifRange ) ;
362+ }
363+
303364 async function processRequest ( ) {
304365 // Pipe and SendFile
305366 /** @type {import("./utils/getFilenameFromUrl").Extra } */
@@ -372,16 +433,25 @@ function wrapper(context) {
372433 res . setHeader ( "Accept-Ranges" , "bytes" ) ;
373434 }
374435
375- const rangeHeader = /** @type {string } */ ( req . headers . range ) ;
376-
377436 let len = /** @type {import("fs").Stats } */ ( extra . stats ) . size ;
378437 let offset = 0 ;
379438
439+ const rangeHeader = /** @type {string } */ ( req . headers . range ) ;
440+
380441 if ( rangeHeader && BYTES_RANGE_REGEXP . test ( rangeHeader ) ) {
381- // eslint-disable-next-line global-require
382- const parsedRanges = require ( "range-parser" ) ( len , rangeHeader , {
383- combine : true ,
384- } ) ;
442+ let parsedRanges =
443+ /** @type {import("range-parser").Ranges | import("range-parser").Result | [] } */
444+ (
445+ // eslint-disable-next-line global-require
446+ require ( "range-parser" ) ( len , rangeHeader , {
447+ combine : true ,
448+ } )
449+ ) ;
450+
451+ // If-Range support
452+ if ( ! isRangeFresh ( ) ) {
453+ parsedRanges = [ ] ;
454+ }
385455
386456 if ( parsedRanges === - 1 ) {
387457 context . logger . error ( "Unsatisfiable range for 'Range' header." ) ;
@@ -460,13 +530,22 @@ function wrapper(context) {
460530 return ;
461531 }
462532
533+ if ( context . options . lastModified && ! res . getHeader ( "Last-Modified" ) ) {
534+ const modified =
535+ /** @type {import("fs").Stats } */
536+ ( extra . stats ) . mtime . toUTCString ( ) ;
537+
538+ res . setHeader ( "Last-Modified" , modified ) ;
539+ }
540+
463541 if ( context . options . etag && ! res . getHeader ( "ETag" ) ) {
464542 const value =
465543 context . options . etag === "weak"
466544 ? /** @type {import("fs").Stats } */ ( extra . stats )
467545 : bufferOrStream ;
468546
469- const val = await etag ( value ) ;
547+ // eslint-disable-next-line global-require
548+ const val = await require ( "./utils/etag" ) ( value ) ;
470549
471550 if ( val . buffer ) {
472551 bufferOrStream = val . buffer ;
@@ -493,7 +572,10 @@ function wrapper(context) {
493572 if (
494573 isCachable ( ) &&
495574 isFresh ( {
496- etag : /** @type {string } */ ( res . getHeader ( "ETag" ) ) ,
575+ etag : /** @type {string | undefined } */ ( res . getHeader ( "ETag" ) ) ,
576+ "last-modified" :
577+ /** @type {string | undefined } */
578+ ( res . getHeader ( "Last-Modified" ) ) ,
497579 } )
498580 ) {
499581 setStatusCode ( res , 304 ) ;
@@ -537,8 +619,6 @@ function wrapper(context) {
537619 /** @type {import("fs").ReadStream } */ ( bufferOrStream ) . pipe
538620 ) === "function" ;
539621
540- console . log ( isPipeSupports ) ;
541-
542622 if ( ! isPipeSupports ) {
543623 send ( res , /** @type {Buffer } */ ( bufferOrStream ) ) ;
544624 return ;
0 commit comments