19
19
// ignored entries get .resume() called on them straight away
20
20
21
21
import { EventEmitter as EE } from 'events'
22
- import { BrotliDecompress , Unzip } from 'minizlib'
22
+ import { BrotliDecompress , Unzip , ZstdDecompress } from 'minizlib'
23
23
import { Header } from './header.js'
24
24
import { TarOptions } from './options.js'
25
25
import { Pax } from './pax.js'
@@ -32,6 +32,8 @@ import {
32
32
33
33
const maxMetaEntrySize = 1024 * 1024
34
34
const gzipHeader = Buffer . from ( [ 0x1f , 0x8b ] )
35
+ const zstdHeader = Buffer . from ( [ 0x28 , 0xb5 , 0x2f , 0xfd ] )
36
+ const ZIP_HEADER_LEN = Math . max ( gzipHeader . length , zstdHeader . length )
35
37
36
38
const STATE = Symbol ( 'state' )
37
39
const WRITEENTRY = Symbol ( 'writeEntry' )
@@ -74,6 +76,7 @@ export class Parser extends EE implements Warner {
74
76
maxMetaEntrySize : number
75
77
filter : Exclude < TarOptions [ 'filter' ] , undefined >
76
78
brotli ?: TarOptions [ 'brotli' ]
79
+ zstd ?: TarOptions [ 'zstd' ]
77
80
78
81
writable : true = true
79
82
readable : false = false ;
@@ -87,7 +90,7 @@ export class Parser extends EE implements Warner {
87
90
[ EX ] ?: Pax ;
88
91
[ GEX ] ?: Pax ;
89
92
[ ENDED ] : boolean = false ;
90
- [ UNZIP ] ?: false | Unzip | BrotliDecompress ;
93
+ [ UNZIP ] ?: false | Unzip | BrotliDecompress | ZstdDecompress ;
91
94
[ ABORTED ] : boolean = false ;
92
95
[ SAW_VALID_ENTRY ] ?: boolean ;
93
96
[ SAW_NULL_BLOCK ] : boolean = false ;
@@ -135,9 +138,19 @@ export class Parser extends EE implements Warner {
135
138
// if it's a tbr file it MIGHT be brotli, but we don't know until
136
139
// we look at it and verify it's not a valid tar file.
137
140
this . brotli =
138
- ! opt . gzip && opt . brotli !== undefined ? opt . brotli
141
+ ! ( opt . gzip || opt . zstd ) && opt . brotli !== undefined ? opt . brotli
139
142
: isTBR ? undefined
140
- : false
143
+ : false
144
+
145
+ // zstd has magic bytes to identify it, but we also support explicit options
146
+ // and file extension detection
147
+ const isTZST =
148
+ opt . file &&
149
+ ( opt . file . endsWith ( '.tar.zst' ) || opt . file . endsWith ( '.tzst' ) )
150
+ this . zstd =
151
+ ! ( opt . gzip || opt . brotli ) && opt . zstd !== undefined ? opt . zstd
152
+ : isTZST ? true
153
+ : undefined
141
154
142
155
// have to set this so that streams are ok piping into it
143
156
this . on ( 'end' , ( ) => this [ CLOSESTREAM ] ( ) )
@@ -431,7 +444,7 @@ export class Parser extends EE implements Warner {
431
444
return false
432
445
}
433
446
434
- // first write, might be gzipped
447
+ // first write, might be gzipped, zstd, or brotli compressed
435
448
const needSniff =
436
449
this [ UNZIP ] === undefined ||
437
450
( this . brotli === undefined && this [ UNZIP ] === false )
@@ -440,7 +453,7 @@ export class Parser extends EE implements Warner {
440
453
chunk = Buffer . concat ( [ this [ BUFFER ] , chunk ] )
441
454
this [ BUFFER ] = undefined
442
455
}
443
- if ( chunk . length < gzipHeader . length ) {
456
+ if ( chunk . length < ZIP_HEADER_LEN ) {
444
457
this [ BUFFER ] = chunk
445
458
/* c8 ignore next */
446
459
cb ?.( )
@@ -458,7 +471,19 @@ export class Parser extends EE implements Warner {
458
471
}
459
472
}
460
473
461
- const maybeBrotli = this . brotli === undefined
474
+ // look for zstd header if gzip header not found
475
+ let isZstd = false
476
+ if ( this [ UNZIP ] === false && this . zstd !== false ) {
477
+ isZstd = true
478
+ for ( let i = 0 ; i < zstdHeader . length ; i ++ ) {
479
+ if ( chunk [ i ] !== zstdHeader [ i ] ) {
480
+ isZstd = false
481
+ break
482
+ }
483
+ }
484
+ }
485
+
486
+ const maybeBrotli = this . brotli === undefined && ! isZstd
462
487
if ( this [ UNZIP ] === false && maybeBrotli ) {
463
488
// read the first header to see if it's a valid tar file. If so,
464
489
// we can safely assume that it's not actually brotli, despite the
@@ -487,13 +512,15 @@ export class Parser extends EE implements Warner {
487
512
488
513
if (
489
514
this [ UNZIP ] === undefined ||
490
- ( this [ UNZIP ] === false && this . brotli )
515
+ ( this [ UNZIP ] === false && ( this . brotli || isZstd ) )
491
516
) {
492
517
const ended = this [ ENDED ]
493
518
this [ ENDED ] = false
494
519
this [ UNZIP ] =
495
520
this [ UNZIP ] === undefined ?
496
521
new Unzip ( { } )
522
+ : isZstd ?
523
+ new ZstdDecompress ( { } )
497
524
: new BrotliDecompress ( { } )
498
525
this [ UNZIP ] . on ( 'data' , chunk => this [ CONSUMECHUNK ] ( chunk ) )
499
526
this [ UNZIP ] . on ( 'error' , er => this . abort ( er as Error ) )
@@ -674,7 +701,7 @@ export class Parser extends EE implements Warner {
674
701
this [ UNZIP ] . end ( )
675
702
} else {
676
703
this [ ENDED ] = true
677
- if ( this . brotli === undefined )
704
+ if ( this . brotli === undefined || this . zstd === undefined )
678
705
chunk = chunk || Buffer . alloc ( 0 )
679
706
if ( chunk ) this . write ( chunk )
680
707
this [ MAYBEEND ] ( )
0 commit comments