@@ -97,6 +97,16 @@ module.exports = warner(class Parser extends EE {
97
97
this . strict = ! ! opt . strict
98
98
this . maxMetaEntrySize = opt . maxMetaEntrySize || maxMetaEntrySize
99
99
this . filter = typeof opt . filter === 'function' ? opt . filter : noop
100
+ // Unlike gzip, brotli doesn't have any magic bytes to identify it
101
+ // Users need to explicitly tell us they're extracting a brotli file
102
+ // Or we infer from the file extension
103
+ const isTBR = ( opt . file && (
104
+ opt . file . endsWith ( '.tar.br' ) || opt . file . endsWith ( '.tbr' ) ) )
105
+ // if it's a tbr file it MIGHT be brotli, but we don't know until
106
+ // we look at it and verify it's not a valid tar file.
107
+ this . brotli = ! opt . gzip && opt . brotli !== undefined ? opt . brotli
108
+ : isTBR ? undefined
109
+ : false
100
110
101
111
// have to set this so that streams are ok piping into it
102
112
this . writable = true
@@ -347,7 +357,9 @@ module.exports = warner(class Parser extends EE {
347
357
}
348
358
349
359
// first write, might be gzipped
350
- if ( this [ UNZIP ] === null && chunk ) {
360
+ const needSniff = this [ UNZIP ] === null ||
361
+ this . brotli === undefined && this [ UNZIP ] === false
362
+ if ( needSniff && chunk ) {
351
363
if ( this [ BUFFER ] ) {
352
364
chunk = Buffer . concat ( [ this [ BUFFER ] , chunk ] )
353
365
this [ BUFFER ] = null
@@ -356,15 +368,45 @@ module.exports = warner(class Parser extends EE {
356
368
this [ BUFFER ] = chunk
357
369
return true
358
370
}
371
+
372
+ // look for gzip header
359
373
for ( let i = 0 ; this [ UNZIP ] === null && i < gzipHeader . length ; i ++ ) {
360
374
if ( chunk [ i ] !== gzipHeader [ i ] ) {
361
375
this [ UNZIP ] = false
362
376
}
363
377
}
364
- if ( this [ UNZIP ] === null ) {
378
+
379
+ const maybeBrotli = this . brotli === undefined
380
+ if ( this [ UNZIP ] === false && maybeBrotli ) {
381
+ // read the first header to see if it's a valid tar file. If so,
382
+ // we can safely assume that it's not actually brotli, despite the
383
+ // .tbr or .tar.br file extension.
384
+ // if we ended before getting a full chunk, yes, def brotli
385
+ if ( chunk . length < 512 ) {
386
+ if ( this [ ENDED ] ) {
387
+ this . brotli = true
388
+ } else {
389
+ this [ BUFFER ] = chunk
390
+ return true
391
+ }
392
+ } else {
393
+ // if it's tar, it's pretty reliably not brotli, chances of
394
+ // that happening are astronomical.
395
+ try {
396
+ new Header ( chunk . slice ( 0 , 512 ) )
397
+ this . brotli = false
398
+ } catch ( _ ) {
399
+ this . brotli = true
400
+ }
401
+ }
402
+ }
403
+
404
+ if ( this [ UNZIP ] === null || ( this [ UNZIP ] === false && this . brotli ) ) {
365
405
const ended = this [ ENDED ]
366
406
this [ ENDED ] = false
367
- this [ UNZIP ] = new zlib . Unzip ( )
407
+ this [ UNZIP ] = this [ UNZIP ] === null
408
+ ? new zlib . Unzip ( )
409
+ : new zlib . BrotliDecompress ( )
368
410
this [ UNZIP ] . on ( 'data' , chunk => this [ CONSUMECHUNK ] ( chunk ) )
369
411
this [ UNZIP ] . on ( 'error' , er => this . abort ( er ) )
370
412
this [ UNZIP ] . on ( 'end' , _ => {
@@ -502,6 +544,7 @@ module.exports = warner(class Parser extends EE {
502
544
this [ UNZIP ] . end ( chunk )
503
545
} else {
504
546
this [ ENDED ] = true
547
+ if ( this . brotli === undefined ) chunk = chunk || Buffer . alloc ( 0 )
505
548
this . write ( chunk )
506
549
}
507
550
}
0 commit comments