@@ -7,8 +7,6 @@ const onFinishedStream = require("on-finished");
7
7
const getFilenameFromUrl = require ( "./utils/getFilenameFromUrl" ) ;
8
8
const { setStatusCode, send, pipe } = require ( "./utils/compatibleAPI" ) ;
9
9
const ready = require ( "./utils/ready" ) ;
10
- const escapeHtml = require ( "./utils/escapeHtml" ) ;
11
- const etag = require ( "./utils/etag" ) ;
12
10
const parseTokenList = require ( "./utils/parseTokenList" ) ;
13
11
14
12
/** @typedef {import("./index.js").NextFunction } NextFunction */
@@ -33,7 +31,7 @@ function getValueContentRangeHeader(type, size, range) {
33
31
* Parse an HTTP Date into a number.
34
32
*
35
33
* @param {string } date
36
- * @private
34
+ * @returns { number }
37
35
*/
38
36
function parseHttpDate ( date ) {
39
37
const timestamp = date && Date . parse ( date ) ;
@@ -140,6 +138,8 @@ function wrapper(context) {
140
138
* @returns {void }
141
139
*/
142
140
function sendError ( status , options ) {
141
+ // eslint-disable-next-line global-require
142
+ const escapeHtml = require ( "./utils/escapeHtml" ) ;
143
143
const content = statuses [ status ] || String ( status ) ;
144
144
let document = `<!DOCTYPE html>
145
145
<html lang="en">
@@ -201,17 +201,21 @@ function wrapper(context) {
201
201
}
202
202
203
203
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 ) {
208
213
const etag = res . getHeader ( "ETag" ) ;
209
214
210
215
return (
211
216
! etag ||
212
- ( match !== "*" &&
213
- parseTokenList ( match ) . every (
214
- // eslint-disable-next-line no-shadow
217
+ ( ifMatch !== "*" &&
218
+ parseTokenList ( ifMatch ) . every (
215
219
( match ) =>
216
220
match !== etag &&
217
221
match !== `W/${ etag } ` &&
@@ -220,6 +224,23 @@ function wrapper(context) {
220
224
) ;
221
225
}
222
226
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
+
223
244
return false ;
224
245
}
225
246
@@ -288,9 +309,17 @@ function wrapper(context) {
288
309
289
310
if ( modifiedSince ) {
290
311
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
+
291
321
const modifiedStale =
292
- ! lastModified ||
293
- ! ( parseHttpDate ( lastModified ) <= parseHttpDate ( modifiedSince ) ) ;
322
+ ! lastModified || ! ( parseHttpDate ( lastModified ) <= parsedHttpDate ) ;
294
323
295
324
if ( modifiedStale ) {
296
325
return false ;
@@ -300,6 +329,38 @@ function wrapper(context) {
300
329
return true ;
301
330
}
302
331
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
+
303
364
async function processRequest ( ) {
304
365
// Pipe and SendFile
305
366
/** @type {import("./utils/getFilenameFromUrl").Extra } */
@@ -372,16 +433,25 @@ function wrapper(context) {
372
433
res . setHeader ( "Accept-Ranges" , "bytes" ) ;
373
434
}
374
435
375
- const rangeHeader = /** @type {string } */ ( req . headers . range ) ;
376
-
377
436
let len = /** @type {import("fs").Stats } */ ( extra . stats ) . size ;
378
437
let offset = 0 ;
379
438
439
+ const rangeHeader = /** @type {string } */ ( req . headers . range ) ;
440
+
380
441
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
+ }
385
455
386
456
if ( parsedRanges === - 1 ) {
387
457
context . logger . error ( "Unsatisfiable range for 'Range' header." ) ;
@@ -460,13 +530,22 @@ function wrapper(context) {
460
530
return ;
461
531
}
462
532
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
+
463
541
if ( context . options . etag && ! res . getHeader ( "ETag" ) ) {
464
542
const value =
465
543
context . options . etag === "weak"
466
544
? /** @type {import("fs").Stats } */ ( extra . stats )
467
545
: bufferOrStream ;
468
546
469
- const val = await etag ( value ) ;
547
+ // eslint-disable-next-line global-require
548
+ const val = await require ( "./utils/etag" ) ( value ) ;
470
549
471
550
if ( val . buffer ) {
472
551
bufferOrStream = val . buffer ;
@@ -493,7 +572,10 @@ function wrapper(context) {
493
572
if (
494
573
isCachable ( ) &&
495
574
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" ) ) ,
497
579
} )
498
580
) {
499
581
setStatusCode ( res , 304 ) ;
@@ -537,8 +619,6 @@ function wrapper(context) {
537
619
/** @type {import("fs").ReadStream } */ ( bufferOrStream ) . pipe
538
620
) === "function" ;
539
621
540
- console . log ( isPipeSupports ) ;
541
-
542
622
if ( ! isPipeSupports ) {
543
623
send ( res , /** @type {Buffer } */ ( bufferOrStream ) ) ;
544
624
return ;
0 commit comments