import zlib from 'zlib' ; import events from 'events' ; import stream from 'stream' ; import fs from 'fs' ; export type AcceptedFile = File | string ; function isFile ( val ) { return typeof File !== 'undefined' && val instanceof File ; } export interface FileReader < T , S = T > { reader : ( file : T , buffer : Buffer , offset_in_buffer : number , length : number , position_in_file : number ) => Promise < number > | number ; opener ? : ( file : T ) => S | Promise < S > ; getSize : ( file : S , original : T ) => Promise < number > | number ; getName : ( file : S , original : T ) => Promise < string > | string ; } ; /**
 * Read a part of a Blob into a Node.js buffer,
 * from position to position+length, into Buffer offset to offset+length
 */ async function blobFileReader ( file , buffer , offset_in_buffer , length , position_in_file ) { const sliced = file . slice ( position_in_file , position_in_file + length ) ; const copy_buffer = await new Promise ( ( resolve , reject ) => { const reader = new FileReader ; reader . onload = ( ) => { resolve ( Buffer . from ( reader . result as ArrayBuffer ) ) ; } ; reader . onerror = ( e ) => { reject ( e ) ; } ; reader . readAsArrayBuffer ( sliced ) ; } ) as Buffer ; buffer . set ( copy_buffer , offset_in_buffer ) ; return copy_buffer . byteLength ; } /**
 * Read a part of a file (via a file descriptor) into a Node.js buffer,
 * from position to position+length, into Buffer offset to offset+length
 */ async function numberFileReader ( file , buffer , offset_in_buffer , length , position_in_file ) { const bytesRead = await new Promise ( ( resolve , reject ) => { fs . read ( file , buffer , offset_in_buffer , length , position_in_file , ( err , bytesRead ) => { if ( err ) { reject ( err ) ; return ; } resolve ( bytesRead ) ; } ) ; } ) as number ; return bytesRead ; } license const consts = { LOCHDR : 30 , LOCSIG : 0x04034b50 , LOCVER : 4 , LOCFLG : 6 , LOCHOW : 8 , LOCTIM : 10 , LOCCRC : 14 , LOCSIZ : 18 , LOCLEN : 22 , LOCNAM : 26 , LOCEXT : 28 , EXTSIG : 0x08074b50 , EXTHDR : 16 , EXTCRC : 4 , EXTSIZ : 8 , EXTLEN : 12 , CENHDR : 46 , CENSIG : 0x02014b50 , CENVEM : 4 , CENVER : 6 , CENFLG : 8 , CENHOW : 10 , CENTIM : 12 , CENCRC : 16 , CENSIZ : 20 , CENLEN : 24 , CENNAM : 28 , CENEXT : 30 , CENCOM : 32 , CENDSK : 34 , CENATT : 36 , CENATX : 38 , CENOFF : 42 , ENDHDR : 22 , ENDSIG : 0x06054b50 , ENDSIGFIRST : 0x50 , ENDSUB : 8 , ENDTOT : 10 , ENDSIZ : 12 , ENDOFF : 16 , ENDCOM : 20 , MAXFILECOMMENT : 0xFFFF , ENDL64HDR : 20 , ENDL64SIG : 0x07064b50 , ENDL64SIGFIRST : 0x50 , ENDL64OFS : 8 , END64HDR : 56 , END64SIG : 0x06064b50 , END64SIGFIRST : 0x50 , END64SUB : 24 , END64TOT : 32 , END64SIZ : 40 , END64OFF : 48 , STORED : 0 , SHRUNK : 1 , REDUCED1 : 2 , REDUCED2 : 3 , REDUCED3 : 4 , REDUCED4 : 5 , IMPLODED : 6 , DEFLATED : 8 , ENHANCED_DEFLATED : 9 , PKWARE : 10 , BZIP2 : 12 , LZMA : 14 , IBM_TERSE : 18 , IBM_LZ77 : 19 , FLG_ENC : 0 , FLG_COMP1 : 1 , FLG_COMP2 : 2 , FLG_DESC : 4 , FLG_ENH : 8 , FLG_STR : 16 , FLG_LNG : 1024 , FLG_MSK : 4096 , FLG_ENTRY_ENC : 1 , EF_ID : 0 , EF_SIZE : 2 , ID_ZIP64 : 0x0001 , ID_AVINFO : 0x0007 , ID_PFS : 0x0008 , ID_OS2 : 0x0009 , ID_NTFS : 0x000a , ID_OPENVMS : 0x000c , ID_UNIX : 0x000d , ID_FORK : 0x000e , ID_PATCH : 0x000f , ID_X509_PKCS7 : 0x0014 , ID_X509_CERTID_F : 0x0015 , ID_X509_CERTID_C : 0x0016 , ID_STRONGENC : 0x0017 , ID_RECORD_MGT : 0x0018 , ID_X509_PKCS7_RL : 0x0019 , ID_IBM1 : 0x0065 , ID_IBM2 : 0x0066 , ID_POSZIP : 0x4690 , EF_ZIP64_OR_32 : 0xffffffff , EF_ZIP64_OR_16 : 0xffff } ; export default class StreamZip < T , S = T > extends events . EventEmitter { file : S ; fileSize : number ; ready = false ; fileName : string ; _entries : { [ name ] : ZipEntry ; } = { } ; centralDirectory : CentralDirectoryHeader ; comment : string ; entriesCount : number ; op : { win ? : FileWindowBuffer < S > ; totalReadLength ? : number ; minPos ? : number ; pos ? : number ; lastPos ? : number ; chunkSize ? : number ; firstByte ? : number ; entriesLeft ? : number ; sig ? : number ; complete ? : Function ; lastBufferPosition ? : number ; lastBytesRead ? : number ; entry ? : ZipEntry ; move ? : boolean ; } ; config : { entries : boolean ; skipEntryNameValidation : boolean ; } ; constructor ( file , protected fileReader ? , storeEntries = true , public chunkSize ? , skip_valid = false ) { super ( ) ; this . config = { entries : storeEntries , skipEntryNameValidation : skip_valid } ; if ( isFile ( file ) ) { this . fileReader = { reader : blobFileReader , getName : f => f . name , getSize : f => f . size } as FileReader < File > ; } else if ( typeof file === 'string' ) { this . fileReader = { reader : numberFileReader , opener : async ( f ) => { const r = await new Promise ( ( resolve , reject ) => { fs . open ( f , 'r' , ( err , f ) => { if ( err ) { reject ( err ) ; return ; } resolve ( f ) ; } ) ; } ) as number ; return r ; } , getSize : f => { return new Promise ( ( resolve , reject ) => { fs . fstat ( f , ( err , stat ) => { if ( err ) { reject ( err ) ; return ; } resolve ( stat . size ) ; } ) ; } ) as Promise < number > ; } , getName : ( _ , original ) => original } as FileReader < string , number > ; } else if ( ! fileReader ) { throw new Error ( "File format is not supported, you must include a file reader." ) ; } ( async ( ) => { if ( this . fileReader . opener ) { this . file = await this . fileReader . opener ( file ) ; } else { this . file = file ; } this . fileName = await this . fileReader . getName ( this . file , file ) ; this . fileSize = await this . fileReader . getSize ( this . file , file ) ; this . open ( chunkSize ) ; } ) ( ) . catch ( err => { this . emit ( 'error' , err ) ; } ) ; } protected open ( chunkSize ? ) { this . chunkSize = chunkSize || Math . round ( this . fileSize / 1000 ) ; this . chunkSize = Math . max ( Math . min ( this . chunkSize , Math . min ( 128 * 1024 , this . fileSize ) ) , Math . min ( 1024 , this . fileSize ) ) ; this . readCentralDirectory ( ) ; } protected readCentralDirectory ( ) { const totalReadLength = Math . min ( consts . ENDHDR + consts . MAXFILECOMMENT , this . fileSize ) ; this . op = { win : new FileWindowBuffer ( this . file , this . fileReader ) , totalReadLength : totalReadLength , minPos : this . fileSize - totalReadLength , lastPos : this . fileSize , chunkSize : Math . min ( 1024 , this . chunkSize ) , firstByte : consts . ENDSIGFIRST , sig : consts . ENDSIG , complete : this . readCentralDirectoryComplete } ; this . op . win . read ( this . fileSize - this . op . chunkSize , this . op . chunkSize ) . then ( bytes => this . readUntilFoundCallback ( null , bytes ) ) . catch ( err => this . readUntilFoundCallback ( err , 0 ) ) ; } protected readUntilFoundCallback ( err , bytesRead ) { if ( err || ! bytesRead ) return this . emit ( 'error' , err || 'Archive read error' ) ; var buffer = this . op . win . buffer , pos = this . op . lastPos , bufferPosition = pos - this . op . win . position , minPos = this . op . minPos ; while ( -- pos >= minPos && -- bufferPosition >= 0 ) { if ( buffer . length - bufferPosition >= 4 && buffer [ bufferPosition ] === this . op . firstByte ) { if ( buffer . readUInt32LE ( bufferPosition ) === this . op . sig ) { this . op . lastBufferPosition = bufferPosition ; this . op . lastBytesRead = bytesRead ; this . op . complete ( ) ; return ; } } } if ( pos === minPos ) { return this . emit ( 'error' , 'Bad archive' ) ; } this . op . lastPos = pos + 1 ; this . op . chunkSize *= 2 ; if ( pos <= minPos ) return this . emit ( 'error' , 'Bad archive' ) ; var expandLength = Math . min ( this . op . chunkSize , pos - minPos ) ; this . op . win . expandLeft ( expandLength ) . then ( bytes => this . readUntilFoundCallback ( null , bytes ) ) . catch ( err => this . readUntilFoundCallback ( err , 0 ) ) ; } readCentralDirectoryComplete = ( ) => { var buffer = this . op . win . buffer ; var pos = this . op . lastBufferPosition ; try { const centralDirectory = new CentralDirectoryHeader ( ) ; centralDirectory . read ( buffer . slice ( pos , pos + consts . ENDHDR ) ) ; centralDirectory . headerOffset = this . op . win . position + pos ; if ( centralDirectory . commentLength ) this . comment = buffer . slice ( pos + consts . ENDHDR , pos + consts . ENDHDR + centralDirectory . commentLength ) . toString ( ) ; else this . comment = null ; this . entriesCount = centralDirectory . volumeEntries ; this . centralDirectory = centralDirectory ; if ( ( centralDirectory . volumeEntries === consts . EF_ZIP64_OR_16 && centralDirectory . totalEntries === consts . EF_ZIP64_OR_16 ) || centralDirectory . size === consts . EF_ZIP64_OR_32 || centralDirectory . offset === consts . EF_ZIP64_OR_32 ) { return this . readZip64CentralDirectoryLocator ( ) ; } else { this . op = { } ; return this . readEntries ( ) ; } } catch ( err ) { this . emit ( 'error' , err ) ; } } ; protected readEntries ( ) { this . op = { win : new FileWindowBuffer ( this . file , this . fileReader ) , pos : this . centralDirectory . offset , chunkSize : this . chunkSize , entriesLeft : this . centralDirectory . volumeEntries } ; return this . op . win . read ( this . op . pos , Math . min ( this . chunkSize , this . fileSize - this . op . pos ) ) . then ( bytes => this . readEntriesCallback ( bytes ) ) . catch ( err => this . emit ( 'error' , err ) ) ; } protected readEntriesCallback ( bytesRead ) { if ( ! bytesRead ) return this . emit ( 'error' , 'Entries read error' ) ; var buffer = this . op . win . buffer , bufferPos = this . op . pos - this . op . win . position , bufferLength = buffer . length , entry = this . op . entry ; try { while ( this . op . entriesLeft > 0 ) { if ( ! entry ) { entry = new ZipEntry ( ) ; entry . readHeader ( buffer , bufferPos ) ; entry . headerOffset = this . op . win . position + bufferPos ; this . op . entry = entry ; this . op . pos += consts . CENHDR ; bufferPos += consts . CENHDR ; } var entryHeaderSize = entry . fnameLen + entry . extraLen + entry . comLen ; var advanceBytes = entryHeaderSize + ( this . op . entriesLeft > 1 ? consts . CENHDR : 0 ) ; if ( bufferLength - bufferPos < advanceBytes ) { this . op . win . moveRight ( this . chunkSize , bufferPos ) . then ( bytesRead => this . readEntriesCallback ( bytesRead ) ) ; this . op . move = true ; return ; } entry . read ( buffer , bufferPos ) ; if ( ! this . config . skipEntryNameValidation ) { entry . validateName ( ) ; } if ( this . entries ) this . entries [ entry . name ] = entry ; this . emit ( 'entry' , entry ) ; this . op . entry = entry = null ; this . op . entriesLeft -- ; this . op . pos += entryHeaderSize ; bufferPos += entryHeaderSize ; } this . emit ( 'ready' ) ; } catch ( err ) { this . emit ( 'error' , err ) ; } } protected checkEntriesExist ( ) { if ( ! this . _entries ) throw new Error ( 'storeEntries disabled' ) ; } entry ( name ) { this . checkEntriesExist ( ) ; return this . entries [ name ] ; } get entries ( ) { this . checkEntriesExist ( ) ; return this . _entries ; } readZip64CentralDirectoryLocator ( ) { var length = consts . ENDL64HDR ; if ( this . op . lastBufferPosition > length ) { this . op . lastBufferPosition -= length ; return this . readZip64CentralDirectoryLocatorComplete ( ) ; } else { this . op = { win : this . op . win , totalReadLength : length , minPos : this . op . win . position - length , lastPos : this . op . win . position , chunkSize : this . op . chunkSize , firstByte : consts . ENDL64SIGFIRST , sig : consts . ENDL64SIG , complete : this . readZip64CentralDirectoryLocatorComplete } ; return this . op . win . read ( this . op . lastPos - this . op . chunkSize , this . op . chunkSize ) . then ( bytes => this . readUntilFoundCallback ( null , bytes ) ) . catch ( err => this . readUntilFoundCallback ( err , 0 ) ) ; } } readZip64CentralDirectoryLocatorComplete = ( ) => { var buffer = this . op . win . buffer ; var locHeader = new CentralDirectoryLoc64Header ( ) ; locHeader . read ( buffer . slice ( this . op . lastBufferPosition , this . op . lastBufferPosition + consts . ENDL64HDR ) ) ; var readLength = this . fileSize - locHeader . headerOffset ; this . op = { win : this . op . win , totalReadLength : readLength , minPos : locHeader . headerOffset , lastPos : this . op . lastPos , chunkSize : this . op . chunkSize , firstByte : consts . END64SIGFIRST , sig : consts . END64SIG , complete : this . readZip64CentralDirectoryComplete } ; return this . op . win . read ( this . fileSize - this . op . chunkSize , this . op . chunkSize ) . then ( bytes => this . readUntilFoundCallback ( null , bytes ) ) . catch ( err => this . readUntilFoundCallback ( err , 0 ) ) ; } ; readZip64CentralDirectoryComplete = ( ) => { var buffer = this . op . win . buffer ; var zip64cd = new CentralDirectoryZip64Header ( ) ; zip64cd . read ( buffer . slice ( this . op . lastBufferPosition , this . op . lastBufferPosition + consts . END64HDR ) ) ; this . centralDirectory . volumeEntries = zip64cd . volumeEntries ; this . centralDirectory . totalEntries = zip64cd . totalEntries ; this . centralDirectory . size = zip64cd . size ; this . centralDirectory . offset = zip64cd . offset ; this . entriesCount = zip64cd . volumeEntries ; this . op = { } ; this . readEntries ( ) ; } ; dataOffset ( entry ) { return entry . offset + consts . LOCHDR + entry . fnameLen + entry . extraLen ; } canVerifyCrc ( entry ) { return ( entry . flags & 0x8 ) !== 0x8 ; } stream ( entry ) { return this . openEntry ( entry ) . then ( ( [ _ , entry ] ) => { var offset = this . dataOffset ( entry ) ; var entryStream = new EntryDataReaderStream ( this . file , offset , entry . compressedSize , this . fileReader ) ; if ( entry . method === consts . STORED ) { } else if ( entry . method === consts . DEFLATED || entry . method === consts . ENHANCED_DEFLATED ) { entryStream = entryStream . pipe ( zlib . createInflateRaw ( ) ) ; } else { throw new Error ( 'Unknown compression method: ' + entry . method ) ; } if ( this . canVerifyCrc ( entry ) ) entryStream = entryStream . pipe ( new EntryVerifyStream ( entryStream , entry . crc , entry . size ) ) ; return entryStream ; } ) ; } async openEntry ( entry ) { if ( typeof entry === 'string' ) { this . checkEntriesExist ( ) ; entry = this . entries [ entry ] ; if ( ! entry ) throw new Error ( 'Entry not found' ) ; } if ( ! entry . isFile ) throw new Error ( 'Entry is not file' ) ; if ( ! this . file ) throw new Error ( 'Archive closed' ) ; var buffer = Buffer . alloc ( consts . LOCHDR ) ; await new FsRead ( this . file , buffer , 0 , buffer . length , entry . offset , this . fileReader ) . read ( ) ; var readEx ; try { entry . readDataHeader ( buffer ) ; if ( entry . encrypted ) { readEx = 'Entry encrypted' ; } } catch ( ex ) { readEx = ex ; } return [ readEx , entry ] as [ any , ZipEntry ] ; } close ( ) { this . file = null ; } async entryData ( entry ) { var err = null ; [ err , entry ] = await this . openEntry ( entry ) ; if ( err ) throw err ; var data = Buffer . alloc ( entry . compressedSize ) ; await new FsRead ( this . file , data , 0 , entry . compressedSize , this . dataOffset ( entry ) , this . fileReader ) . read ( ) ; if ( entry . method === consts . STORED ) { } else if ( entry . method === consts . DEFLATED || entry . method === consts . ENHANCED_DEFLATED ) { data = zlib . inflateRawSync ( data ) ; } else { throw new Error ( 'Unknown compression method: ' + entry . method ) ; } if ( data . length !== entry . size ) throw new Error ( 'Invalid size' ) ; if ( this . canVerifyCrc ( entry ) ) { var verify = new CrcVerify ( entry . crc , entry . size ) ; verify . data ( data ) ; } return data ; } } class CentralDirectoryHeader { volumeEntries : number ; totalEntries : number ; size : number ; offset : number ; commentLength : number ; headerOffset : number ; read ( data ) { if ( data . length !== consts . ENDHDR || data . readUInt32LE ( 0 ) !== consts . ENDSIG ) throw new Error ( 'Invalid central directory' ) ; this . volumeEntries = data . readUInt16LE ( consts . ENDSUB ) ; this . totalEntries = data . readUInt16LE ( consts . ENDTOT ) ; this . size = data . readUInt32LE ( consts . ENDSIZ ) ; this . offset = data . readUInt32LE ( consts . ENDOFF ) ; this . commentLength = data . readUInt16LE ( consts . ENDCOM ) ; } } class CentralDirectoryLoc64Header { headerOffset : number ; read ( data ) { if ( data . length !== consts . ENDL64HDR || data . readUInt32LE ( 0 ) !== consts . ENDL64SIG ) throw new Error ( 'Invalid zip64 central directory locator' ) ; this . headerOffset = Util . readUInt64LE ( data , consts . ENDSUB ) ; } } class CentralDirectoryZip64Header { volumeEntries : number ; totalEntries : number ; size : number ; offset : number ; read ( data ) { if ( data . length !== consts . END64HDR || data . readUInt32LE ( 0 ) !== consts . END64SIG ) throw new Error ( 'Invalid central directory' ) ; this . volumeEntries = Util . readUInt64LE ( data , consts . END64SUB ) ; this . totalEntries = Util . readUInt64LE ( data , consts . END64TOT ) ; this . size = Util . readUInt64LE ( data , consts . END64SIZ ) ; this . offset = Util . readUInt64LE ( data , consts . END64OFF ) ; } } export class ZipEntry { verMade : number ; version : number ; flags : number ; method : number ; time : number ; crc : number ; compressedSize : number ; size : number ; fnameLen : number ; extraLen : number ; comLen : number ; diskStart : number ; inattr : number ; attr : number ; offset : number ; name : string ; isDirectory : boolean ; comment : string ; headerOffset : number ; readHeader ( data , offset ) { if ( data . length < offset + consts . CENHDR || data . readUInt32LE ( offset ) !== consts . CENSIG ) { throw new Error ( 'Invalid entry header' ) ; } this . verMade = data . readUInt16LE ( offset + consts . CENVEM ) ; this . version = data . readUInt16LE ( offset + consts . CENVER ) ; this . flags = data . readUInt16LE ( offset + consts . CENFLG ) ; this . method = data . readUInt16LE ( offset + consts . CENHOW ) ; var timebytes = data . readUInt16LE ( offset + consts . CENTIM ) ; var datebytes = data . readUInt16LE ( offset + consts . CENTIM + 2 ) ; this . time = parseZipTime ( timebytes , datebytes ) ; this . crc = data . readUInt32LE ( offset + consts . CENCRC ) ; this . compressedSize = data . readUInt32LE ( offset + consts . CENSIZ ) ; this . size = data . readUInt32LE ( offset + consts . CENLEN ) ; this . fnameLen = data . readUInt16LE ( offset + consts . CENNAM ) ; this . extraLen = data . readUInt16LE ( offset + consts . CENEXT ) ; this . comLen = data . readUInt16LE ( offset + consts . CENCOM ) ; this . diskStart = data . readUInt16LE ( offset + consts . CENDSK ) ; this . inattr = data . readUInt16LE ( offset + consts . CENATT ) ; this . attr = data . readUInt32LE ( offset + consts . CENATX ) ; this . offset = data . readUInt32LE ( offset + consts . CENOFF ) ; } readDataHeader ( data ) { if ( data . readUInt32LE ( 0 ) !== consts . LOCSIG ) { throw new Error ( 'Invalid local header' ) ; } this . version = data . readUInt16LE ( consts . LOCVER ) ; this . flags = data . readUInt16LE ( consts . LOCFLG ) ; this . method = data . readUInt16LE ( consts . LOCHOW ) ; var timebytes = data . readUInt16LE ( consts . LOCTIM ) ; var datebytes = data . readUInt16LE ( consts . LOCTIM + 2 ) ; this . time = parseZipTime ( timebytes , datebytes ) ; this . crc = data . readUInt32LE ( consts . LOCCRC ) || this . crc ; var compressedSize = data . readUInt32LE ( consts . LOCSIZ ) ; if ( compressedSize && compressedSize !== consts . EF_ZIP64_OR_32 ) { this . compressedSize = compressedSize ; } var size = data . readUInt32LE ( consts . LOCLEN ) ; if ( size && size !== consts . EF_ZIP64_OR_32 ) { this . size = size ; } this . fnameLen = data . readUInt16LE ( consts . LOCNAM ) ; this . extraLen = data . readUInt16LE ( consts . LOCEXT ) ; } read ( data , offset ) { this . name = data . slice ( offset , offset += this . fnameLen ) . toString ( ) ; var lastChar = data [ offset - 1 ] ; this . isDirectory = ( lastChar === 47 ) || ( lastChar === 92 ) ; if ( this . extraLen ) { this . readExtra ( data , offset ) ; offset += this . extraLen ; } this . comment = this . comLen ? data . slice ( offset , offset + this . comLen ) . toString ( ) : null ; } readExtra ( data , offset ) { var signature , size , maxPos = offset + this . extraLen ; while ( offset < maxPos ) { signature = data . readUInt16LE ( offset ) ; offset += 2 ; size = data . readUInt16LE ( offset ) ; offset += 2 ; if ( consts . ID_ZIP64 === signature ) { this . parseZip64Extra ( data , offset , size ) ; } offset += size ; } } validateName ( ) { if ( /\\|^\w+:|^\/|(^|\/)\.\.(\/|$)/ . test ( this . name ) ) { throw new Error ( 'Malicious entry: ' + this . name ) ; } } parseZip64Extra ( data , offset , length ) { if ( length >= 8 && this . size === consts . EF_ZIP64_OR_32 ) { this . size = Util . readUInt64LE ( data , offset ) ; offset += 8 ; length -= 8 ; } if ( length >= 8 && this . compressedSize === consts . EF_ZIP64_OR_32 ) { this . compressedSize = Util . readUInt64LE ( data , offset ) ; offset += 8 ; length -= 8 ; } if ( length >= 8 && this . offset === consts . EF_ZIP64_OR_32 ) { this . offset = Util . readUInt64LE ( data , offset ) ; offset += 8 ; length -= 8 ; } if ( length >= 4 && this . diskStart === consts . EF_ZIP64_OR_16 ) { this . diskStart = data . readUInt32LE ( offset ) ; } } get encrypted ( ) { return ( this . flags & consts . FLG_ENTRY_ENC ) === consts . FLG_ENTRY_ENC ; } get isFile ( ) { return ! this . isDirectory ; } } function toBits ( dec , size ) { let b = ( dec >>> 0 ) . toString ( 2 ) ; while ( b . length < size ) b = '0' + b ; return b . split ( '' ) ; } function parseZipTime ( timebytes , datebytes ) { const timebits = toBits ( timebytes , 16 ) ; const datebits = toBits ( datebytes , 16 ) ; const mt = { h : parseInt ( timebits . slice ( 0 , 5 ) . join ( '' ) , 2 ) , m : parseInt ( timebits . slice ( 5 , 11 ) . join ( '' ) , 2 ) , s : parseInt ( timebits . slice ( 11 , 16 ) . join ( '' ) , 2 ) * 2 , Y : parseInt ( datebits . slice ( 0 , 7 ) . join ( '' ) , 2 ) + 1980 , M : parseInt ( datebits . slice ( 7 , 11 ) . join ( '' ) , 2 ) , D : parseInt ( datebits . slice ( 11 , 16 ) . join ( '' ) , 2 ) , } ; const dt_str = [ mt . Y , mt . M , mt . D ] . join ( '-' ) + ' ' + [ mt . h , mt . m , mt . s ] . join ( ':' ) + ' GMT+0' ; return new Date ( dt_str ) . getTime ( ) ; } class FsRead < S > { bytesRead = 0 ; waiting = false ; constructor ( public file , public buffer , public offset , public length , public position , public fileReader ) { } async read ( ) { this . waiting = true ; const readed = await this . fileReader . reader ( this . file , this . buffer , this . offset + this . bytesRead , this . length - this . bytesRead , this . position + this . bytesRead ) ; return this . readCallback ( readed ) ; } readCallback ( bytesRead ) { if ( typeof bytesRead === 'number' ) this . bytesRead += bytesRead ; if ( ! bytesRead || this . bytesRead === this . length ) { this . waiting = false ; return Promise . resolve ( this . bytesRead ) ; } else { return this . read ( ) ; } } } class FileWindowBuffer < S > { position : number ; buffer : Buffer = Buffer . alloc ( 0 ) ; fsOp : FsRead < S > = null ; constructor ( public file , public fileReader ) { } checkOp ( ) { if ( this . fsOp && this . fsOp . waiting ) { throw new Error ( 'Operation in progress' ) ; } } async read ( pos , length ) { this . checkOp ( ) ; if ( this . buffer . length < length ) this . buffer = Buffer . alloc ( length ) ; this . position = pos ; this . fsOp = new FsRead ( this . file , this . buffer , 0 , length , this . position , this . fileReader ) ; return await this . fsOp . read ( ) ; } async expandLeft ( length ) { this . checkOp ( ) ; this . buffer = Buffer . concat ( [ Buffer . alloc ( length ) , this . buffer ] ) ; this . position -= length ; if ( this . position < 0 ) this . position = 0 ; this . fsOp = new FsRead ( this . file , this . buffer , 0 , length , this . position , this . fileReader ) ; return await this . fsOp . read ( ) ; } async expandRight ( length ) { this . checkOp ( ) ; var offset = this . buffer . length ; this . buffer = Buffer . concat ( [ this . buffer , Buffer . alloc ( length ) ] ) ; this . fsOp = new FsRead ( this . file , this . buffer , offset , length , this . position + offset , this . fileReader ) ; return await this . fsOp . read ( ) ; } async moveRight ( _ , shift ) { this . checkOp ( ) ; if ( shift ) { this . buffer . copy ( this . buffer , 0 , shift ) ; } else { shift = 0 ; } this . position += shift ; this . fsOp = new FsRead ( this . file , this . buffer , this . buffer . length - shift , shift , this . position + this . buffer . length - shift , this . fileReader ) ; return await this . fsOp . read ( ) ; } } class EntryDataReaderStream < S > extends stream . Readable { pos : number = 0 ; constructor ( public file , public offset , public length , public fileReader ) { super ( ) ; } _read ( n ) { var buffer = Buffer . alloc ( Math . min ( n , this . length - this . pos ) ) ; if ( buffer . length ) { const prom = this . fileReader . reader ( this . file , buffer , 0 , buffer . length , this . offset + this . pos ) ; if ( prom instanceof Promise ) { prom . then ( read => this . readCallback ( read , buffer ) ) ; } else { this . readCallback ( prom , buffer ) ; } } else { this . push ( null ) ; } } readCallback ( bytesRead , buffer ) { this . pos += bytesRead ; if ( ! bytesRead ) { this . push ( null ) ; } else { if ( bytesRead !== buffer . length ) buffer = buffer . slice ( 0 , bytesRead ) ; this . push ( buffer ) ; } } } class EntryVerifyStream < S > extends stream . Transform { verify : CrcVerify ; constructor ( baseStm , crc , size ) { super ( ) ; this . verify = new CrcVerify ( crc , size ) ; baseStm . on ( 'error' , ( e ) => { this . emit ( 'error' , e ) ; } ) ; } _transform ( data , _ , callback ) { var err ; try { this . verify . data ( data ) ; } catch ( e ) { err = e ; } callback ( err , data ) ; } } class CrcVerify { state = { crc : ~ 0 , size : 0 } ; static crcTable : any [ ] ; constructor ( public crc , public size ) { } data ( data ) { var crcTable = CrcVerify . getCrcTable ( ) ; var crc = this . state . crc , off = 0 , len = data . length ; while ( -- len >= 0 ) crc = crcTable [ ( crc ^ data [ off ++ ] ) & 0xff ] ^ ( crc >>> 8 ) ; this . state . crc = crc ; this . state . size += data . length ; if ( this . state . size >= this . size ) { var buf = Buffer . alloc ( 4 ) ; buf . writeInt32LE ( ~ this . state . crc & 0xffffffff , 0 ) ; crc = buf . readUInt32LE ( 0 ) ; if ( crc !== this . crc ) throw new Error ( 'Invalid CRC' ) ; if ( this . state . size !== this . size ) throw new Error ( 'Invalid size' ) ; } } static getCrcTable ( ) { var crcTable = CrcVerify . crcTable ; if ( ! crcTable ) { CrcVerify . crcTable = crcTable = [ ] ; var b = Buffer . alloc ( 4 ) ; for ( var n = 0 ; n < 256 ; n ++ ) { var c = n ; for ( var k = 8 ; -- k >= 0 ; ) if ( ( c & 1 ) !== 0 ) { c = 0xedb88320 ^ ( c >>> 1 ) ; } else { c = c >>> 1 ; } if ( c < 0 ) { b . writeInt32LE ( c , 0 ) ; c = b . readUInt32LE ( 0 ) ; } crcTable [ n ] = c ; } } return crcTable ; } } const Util = { readUInt64LE : function ( buffer , offset ) { return ( buffer . readUInt32LE ( offset + 4 ) * 0x0000000100000000 ) + buffer . readUInt32LE ( offset ) ; } } ;