@@ -97,6 +97,16 @@ module.exports = warner(class Parser extends EE {
9797 this . strict = ! ! opt . strict
9898 this . maxMetaEntrySize = opt . maxMetaEntrySize || maxMetaEntrySize
9999 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
100110
101111 // have to set this so that streams are ok piping into it
102112 this . writable = true
@@ -347,7 +357,9 @@ module.exports = warner(class Parser extends EE {
347357 }
348358
349359 // 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 ) {
351363 if ( this [ BUFFER ] ) {
352364 chunk = Buffer . concat ( [ this [ BUFFER ] , chunk ] )
353365 this [ BUFFER ] = null
@@ -356,15 +368,45 @@ module.exports = warner(class Parser extends EE {
356368 this [ BUFFER ] = chunk
357369 return true
358370 }
371+
372+ // look for gzip header
359373 for ( let i = 0 ; this [ UNZIP ] === null && i < gzipHeader . length ; i ++ ) {
360374 if ( chunk [ i ] !== gzipHeader [ i ] ) {
361375 this [ UNZIP ] = false
362376 }
363377 }
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 ) ) {
365405 const ended = this [ ENDED ]
366406 this [ ENDED ] = false
367- this [ UNZIP ] = new zlib . Unzip ( )
407+ this [ UNZIP ] = this [ UNZIP ] === null
408+ ? new zlib . Unzip ( )
409+ : new zlib . BrotliDecompress ( )
368410 this [ UNZIP ] . on ( 'data' , chunk => this [ CONSUMECHUNK ] ( chunk ) )
369411 this [ UNZIP ] . on ( 'error' , er => this . abort ( er ) )
370412 this [ UNZIP ] . on ( 'end' , _ => {
@@ -502,6 +544,7 @@ module.exports = warner(class Parser extends EE {
502544 this [ UNZIP ] . end ( chunk )
503545 } else {
504546 this [ ENDED ] = true
547+ if ( this . brotli === undefined ) chunk = chunk || Buffer . alloc ( 0 )
505548 this . write ( chunk )
506549 }
507550 }
0 commit comments