'From Squeak3.1alpha of 4 February 2001 [latest update: #3677] on 20 February 2001 at 2:44:32 pm'! "Change Set: ZipDir-ar Date: 20 February 2001 Author: Andreas Raab A very first and simple approach at accessing .zip file directories."! Object subclass: #ZipFileDirectory instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Compression'! !ZipFileDirectory commentStamp: '' prior: 0! Very, VERY basic support for reading/writing zip files. Should be put into some FileDirectory structure.! Object subclass: #ZipFileEntry instanceVariableNames: 'name localHeaderStart version flags method fileTime fileDate crc compressedSize uncompressedSize extraFields comment compressedStream ' classVariableNames: '' poolDictionaries: '' category: 'System-Compression'! !ZipFileEntry commentStamp: '' prior: 0! An entry in a zip file. Can be used both for adding and listing files. When adding, the following fields need to be filled in with the appropriate information (all other can be left blank, the defaults will work): name The name of the entry. The format includes any sub-directories, e.g., 'foo/bar' will create the file bar in the directory foo. All directories must be separated by FORWARD slashes (e.g., URL style). Directories that should be created should preceede any files contained in that directory, e.g., the order for the entries creating 'foo/bar' should be 'foo/' (creates directory) 'foo/bar' (creates bar in foo) I am not sure if that is a requirement but that's what WinZip does. method The compression method of the file. Should be either one of 0: Uncompressed. 8: Deflated. Note that deflation is the 'raw' method here - no special headers or anything so clients can go straight with ZipFileEntryWriteStream rather than GZipWriteStream or ZLibWriteStream. crc The CRC32 of the file (now is this on the compressed or uncompressed one?) compressedSize The compressed size of the file uncompressedSize The uncompressed size of the file compressedStream A stream containing the compressed data. If nil, it is assumed that compressedSize == uncompressedSize == method == 0, e.g., an empty file or directory. ! GZipWriteStream subclass: #ZipFileEntryWriteStream instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'System-Compression'! !ZipFileEntryWriteStream commentStamp: '' prior: 0! Encodes the information specifically for use with '.zip' files. Difference to GZip is that no header and no footer are required - this will be encoded later with the ZipFileDirectory.! !GZipWriteStream methodsFor: 'initialize-release' stamp: 'ar 2/19/2001 23:46'! release "Write crc and the number of bytes encoded" super release. self updateCrc. crc _ crc bitXor: 16rFFFFFFFF. encoder flushBits. self writeFooter.! ! !GZipWriteStream methodsFor: 'initialize-release' stamp: 'ar 2/19/2001 23:46'! writeFooter "Write some footer information for the crc" 0 to: 3 do:[:i| encoder nextBytePut: (crc >> (i*8) bitAnd: 255)]. 0 to: 3 do:[:i| encoder nextBytePut: (bytesWritten >> (i*8) bitAnd: 255)].! ! !GZipWriteStream methodsFor: 'accessing' stamp: 'ar 2/19/2001 23:48'! crc ^crc! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/19/2001 23:02'! positionToAnyHeader: aStream "Search forward from the current position in aStream until we find a local file header. This is the hard way of doing it just looking through the entire file to see if we find a matching signature." | buf pos start idx sig1 sig2 sig3 | buf _ String new: 4100. "4096+4 for sliding buffer" "Signature is 'PK\1\2' or 'PK\3\4' or 'PK\5\6'" sig1 _ (ByteArray with: 16r50 with: 16r4B with: 16r01 with: 16r02) asString. sig2 _ (ByteArray with: 16r50 with: 16r4B with: 16r03 with: 16r04) asString. sig3 _ (ByteArray with: 16r50 with: 16r4B with: 16r05 with: 16r06) asString. start _ 1. [true] whileTrue:[ pos _ aStream position. buf _ aStream next: buf size - start + 1 into: buf startingAt: start. buf size < start ifTrue:[^false]. "note: search for sig2 first - this is almost always the first one" idx _ buf findString: sig2 startingAt: 1. (idx > 0) ifTrue:[ "found entry" aStream position: pos + idx - start. ^true]. idx _ buf findString: sig1 startingAt: 1. (idx > 0) ifTrue:[ "found entry" aStream position: pos + idx - start. ^true]. idx _ buf findString: sig3 startingAt: 1. (idx > 0) ifTrue:[ "found entry" aStream position: pos + idx - start. ^true]. "note: no need to replace from n; if the buffer isn't filled then the next read will fail" buf replaceFrom: 1 to: 4 with: buf startingAt: buf size - 3. start _ 5].! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/19/2001 22:35'! readCentralFileHeaderFrom: aStream "Read a local file header from the given stream. Position the stream after the entry (possibly to the start of the next entry)." | sig entry fileNameLength extraFieldLength check commentLength pos | pos _ aStream position. "Signature is 'PK\1\2'" sig _ (ByteArray with: 16r50 with: 16r4B with: 16r01 with: 16r02) asString. check _ aStream next: 4 into: (String new: 4). check = sig ifFalse:[aStream position: pos. ^nil]. entry _ ZipFileEntry new. entry versionMadeBy: (aStream nextLittleEndianNumber: 2). entry version: (aStream nextLittleEndianNumber: 2). entry flags: (aStream nextLittleEndianNumber: 2). entry compressionMethod: (aStream nextLittleEndianNumber: 2). entry fileTime: (aStream nextLittleEndianNumber: 2). entry fileDate: (aStream nextLittleEndianNumber: 2). entry crc: (aStream nextLittleEndianNumber: 4). entry compressedSize: (aStream nextLittleEndianNumber: 4). entry uncompressedSize: (aStream nextLittleEndianNumber: 4). fileNameLength _ aStream nextLittleEndianNumber: 2. extraFieldLength _ aStream nextLittleEndianNumber: 2. commentLength _ aStream nextLittleEndianNumber: 2. entry diskNumberStart: (aStream nextLittleEndianNumber: 2). entry internalFileAttributes: (aStream nextLittleEndianNumber: 2). entry externalFileAttributes: (aStream nextLittleEndianNumber: 4). entry localHeaderStart: (aStream nextLittleEndianNumber: 4). entry name: (aStream next: fileNameLength) asString. entry extraFields: (aStream next: extraFieldLength) asByteArray. entry comment: (aStream next: commentLength) asString. ^entry! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/20/2001 00:09'! readEndOfCentralDirectoryFrom: aStream "Read a local file header from the given stream. Position the stream after the entry (possibly to the start of the next entry)." | sig check pos temp | pos _ aStream position. "Signature is 'PK\5\6'" sig _ (ByteArray with: 16r50 with: 16r4B with: 16r05 with: 16r06) asString. check _ aStream next: 4 into: (String new: 4). check = sig ifFalse:[aStream position: pos. ^false]. temp _ aStream nextLittleEndianNumber: 2. "# of this disk" "Transcript cr; show:'disk number: ', temp printString." temp _ aStream nextLittleEndianNumber: 2. "# of disk with central dir start" "Transcript cr; show:'disk with central directory start: ', temp printString." temp _ aStream nextLittleEndianNumber: 2. "# of entries in central dir on this disk" "Transcript cr; show:'number of entries in central dir: ', temp printString." temp _ aStream nextLittleEndianNumber: 2. "total # of entries in central dir" "Transcript cr; show:'total number of entries in central dir: ', temp printString." temp _ aStream nextLittleEndianNumber: 4. "size of central directory" "Transcript cr; show:'size of central dir: ', temp printString." temp _ aStream nextLittleEndianNumber: 4. "offset of start of central directory" "Transcript cr; show:'offset of central dir: ', temp printString." temp _ aStream nextLittleEndianNumber: 2. "zip file comment" "Transcript cr; show:'comment size: ', temp printString." temp _ aStream next: temp. "ignored" "Transcript cr; show:'comment: ', temp asString." ^true! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/20/2001 00:11'! readGlobalZipEntriesIn: aStream "Scan the contents of the underlying zip file and return all the entries." ^(self readZipEntriesIn: aStream) last! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/20/2001 00:09'! readLocalFileHeaderFrom: aStream "Read a local file header from the given stream. Position the stream after the entry (possibly to the start of the next entry)." | sig entry fileNameLength extraFieldLength check pos | pos _ aStream position. "Signature is 'PK\3\4'" sig _ (ByteArray with: 16r50 with: 16r4B with: 16r03 with: 16r04) asString. check _ aStream next: 4 into: (String new: 4). check = sig ifFalse:[aStream position: pos. ^nil]. entry _ ZipFileEntry new. entry localHeaderStart: pos. entry version: (aStream nextLittleEndianNumber: 2). entry flags: (aStream nextLittleEndianNumber: 2). entry compressionMethod: (aStream nextLittleEndianNumber: 2). entry fileTime: (aStream nextLittleEndianNumber: 2). entry fileDate: (aStream nextLittleEndianNumber: 2). entry crc: (aStream nextLittleEndianNumber: 4). entry compressedSize: (aStream nextLittleEndianNumber: 4). entry uncompressedSize: (aStream nextLittleEndianNumber: 4). fileNameLength _ aStream nextLittleEndianNumber: 2. extraFieldLength _ aStream nextLittleEndianNumber: 2. entry name: (aStream next: fileNameLength) asString. entry extraFields: (aStream next: extraFieldLength) asByteArray. (entry flags anyMask: 8) ifTrue:[ "Meaning that the compressed/uncompressed sizes are zero and a data descriptor follows. But how should we find that guy?" self error:'Cannot handle this']. "skip to the end of the data - we have all the information we need" aStream skip: entry compressedSize. ^entry! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/20/2001 00:11'! readLocalZipEntriesIn: aStream "Scan the contents of the underlying zip file and return all the entries." ^(self readZipEntriesIn: aStream) first! ! !ZipFileDirectory methodsFor: 'reading' stamp: 'ar 2/20/2001 00:09'! readZipEntriesIn: aStream "Scan the contents of the underlying zip file and return all the entries." | entry localEntries globalEntries | aStream position: 0. self positionToAnyHeader: aStream. localEntries _ WriteStream on: Array new. [entry _ self readLocalFileHeaderFrom: aStream. entry == nil] whileFalse:[localEntries nextPut: entry]. "Transcript cr; show:'Central directory start: ', aStream position printString." globalEntries _ WriteStream on: Array new. [entry _ self readCentralFileHeaderFrom: aStream. entry == nil] whileFalse:[globalEntries nextPut: entry]. "Transcript cr; show:'Central directory end: ', aStream position printString." (self readEndOfCentralDirectoryFrom: aStream) ifFalse:[self error:'End of central direcctory expected']. aStream atEnd ifFalse:[self error:'Trailing garbage detected']. ^{localEntries contents. globalEntries contents}! ! !ZipFileDirectory methodsFor: 'writing' stamp: 'ar 2/20/2001 00:07'! writeCentralFileHeader: entry on: aStream "Write a local file header on the given stream" | sig | "Signature is 'PK\1\2'" sig _ ByteArray with: 16r50 with: 16r4B with: 16r01 with: 16r02. aStream nextPutAll: sig. aStream nextLittleEndianNumber: 2 put: entry versionMadeBy. aStream nextLittleEndianNumber: 2 put: entry version. aStream nextLittleEndianNumber: 2 put: entry flags. aStream nextLittleEndianNumber: 2 put: entry compressionMethod. aStream nextLittleEndianNumber: 2 put: entry fileTime. aStream nextLittleEndianNumber: 2 put: entry fileDate. aStream nextLittleEndianNumber: 4 put: entry crc. aStream nextLittleEndianNumber: 4 put: entry compressedSize. aStream nextLittleEndianNumber: 4 put: entry uncompressedSize. aStream nextLittleEndianNumber: 2 put: entry name size. aStream nextLittleEndianNumber: 2 put: entry extraFields size. aStream nextLittleEndianNumber: 2 put: entry comment size. aStream nextLittleEndianNumber: 2 put: entry diskNumberStart. aStream nextLittleEndianNumber: 2 put: entry internalFileAttributes. aStream nextLittleEndianNumber: 4 put: entry externalFileAttributes. aStream nextLittleEndianNumber: 4 put: entry localHeaderStart. aStream nextPutAll: entry name asByteArray. aStream nextPutAll: entry extraFields asByteArray. aStream nextPutAll: entry comment asByteArray.! ! !ZipFileDirectory methodsFor: 'writing' stamp: 'ar 2/19/2001 23:26'! writeEndOf: entries centralHeader: centralStart on: aStream | distance sig | "Signature is 'PK\5\6'" distance _ aStream position - centralStart. sig _ ByteArray with: 16r50 with: 16r4B with: 16r05 with: 16r06. aStream nextPutAll: sig. aStream nextLittleEndianNumber: 2 put: 0. "# of disk" aStream nextLittleEndianNumber: 2 put: 0. "# of disk w/ central dir" aStream nextLittleEndianNumber: 2 put: entries size. "# of entries" aStream nextLittleEndianNumber: 2 put: entries size. "total # of entries" aStream nextLittleEndianNumber: 4 put: distance. "size of central dir" aStream nextLittleEndianNumber: 4 put: centralStart. "offset of central dir" aStream nextLittleEndianNumber: 2 put: 0. "zip file comment" ! ! !ZipFileDirectory methodsFor: 'writing' stamp: 'ar 2/20/2001 00:03'! writeFileData: entry on: aStream "Write the contents of the entry onto the given stream" | buf source | buf _ ByteArray new: 4096. source _ entry compressedStream. source ifNil:[^self]. "e.g., a directory or so, must have 0/0 sizes" source position: 0. [buf _ source next: buf size into: buf. buf isEmpty] whileFalse:[aStream nextPutAll: buf].! ! !ZipFileDirectory methodsFor: 'writing' stamp: 'ar 2/20/2001 00:06'! writeLocalFileHeader: entry on: aStream "Write a local file header on the given stream" | sig | entry localHeaderStart: aStream position. "remember it" "Signature is 'PK\3\4'" sig _ ByteArray with: 16r50 with: 16r4B with: 16r03 with: 16r04. aStream nextPutAll: sig. aStream nextLittleEndianNumber: 2 put: entry version. aStream nextLittleEndianNumber: 2 put: entry flags. aStream nextLittleEndianNumber: 2 put: entry compressionMethod. aStream nextLittleEndianNumber: 2 put: entry fileTime. aStream nextLittleEndianNumber: 2 put: entry fileDate. aStream nextLittleEndianNumber: 4 put: entry crc. aStream nextLittleEndianNumber: 4 put: entry compressedSize. aStream nextLittleEndianNumber: 4 put: entry uncompressedSize. aStream nextLittleEndianNumber: 2 put: entry name size. aStream nextLittleEndianNumber: 2 put: entry extraFields size. aStream nextPutAll: entry name asByteArray. aStream nextPutAll: entry extraFields asByteArray. ! ! !ZipFileDirectory methodsFor: 'writing' stamp: 'ar 2/19/2001 23:27'! writeZipEntries: aCollection on: aStream "Assume: aStream is emtpy, okay?" | centralStart | aCollection do:[:entry| self writeLocalFileHeader: entry on: aStream. self writeFileData: entry on: aStream. ]. centralStart _ aStream position. aCollection do:[:entry| self writeCentralFileHeader: entry on: aStream. ]. self writeEndOf: aCollection centralHeader: centralStart on: aStream.! ! !ZipFileDirectory class methodsFor: 'testing' stamp: 'ar 2/20/2001 00:07'! printEntriesIn: aFileName "ZipFileDirectory printEntriesIn: 'TestSqueaklets.zip'" | file entries all | file _ FileDirectory default readOnlyFileNamed: aFileName. file binary. all _ self new readZipEntriesIn: file. file close. true ifTrue:[^all]. entries _ all first. entries _ entries sortBy:[:e1 :e2| e1 name <= e2 name]. entries do:[:entry| Transcript cr; show: entry compressionMethod printString, ': ', entry name, ' ', entry compressedSize printString,'/', entry uncompressedSize printString. ]. Transcript cr; show: entries size printString,' entries'. ^all! ! !ZipFileDirectory class methodsFor: 'testing' stamp: 'ar 2/20/2001 00:03'! zipFilesFrom: baseDir into: zipFileName "ZipFileDirectory zipFilesFrom: 'Andreas' into: 'TestSqueaklets.zip'" | fd files entries n f zip encoder entry outStream | fd _ FileDirectory default directoryNamed: baseDir. files _ fd fileNames. "make up entries" ('Compressing files in ', baseDir) displayProgressAt: Sensor cursorPoint from: 1 to: files size during:[:bar| n _ 0. entries _ files collect:[:fName| bar value: (n _ n + 1). f _ fd readOnlyFileNamed: fName. f binary. zip _ ReadWriteStream on: (ByteArray new: f size // 8). encoder _ ZipFileEntryWriteStream on: zip. [f atEnd] whileFalse:[encoder nextPutAll: (f next: 4096)]. encoder close. entry _ ZipFileEntry new. "note: using fName instead of baseDir/fName results in not creating baseDir before" entry name: fName. entry compressionMethod: (f size = 0 ifTrue:[0] ifFalse:[8]). entry crc: encoder crc. entry compressedSize: zip size. entry uncompressedSize: f size. entry compressedStream: (f size = 0 ifTrue:[nil] ifFalse:[zip]). f close. entry]. ]. outStream _ FileDirectory default newFileNamed: zipFileName. ('Writing file ', zipFileName) displayProgressAt: Sensor cursorPoint from: 1 to: 1 during:[:bar| self new writeZipEntries: entries on: outStream. bar value: 1]. outStream close.! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 23:20'! comment ^comment ifNil:['']! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:16'! comment: aString comment _ aString! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:45'! compressedSize ^compressedSize! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:45'! compressedSize: aNumber compressedSize _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 23:28'! compressedStream ^compressedStream! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 23:28'! compressedStream: aStream compressedStream _ aStream! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:47'! compressionMethod ^method! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:47'! compressionMethod: aNumber method _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:44'! crc ^crc! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:45'! crc: aNumber crc _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 23:21'! extraFields ^extraFields ifNil:['']! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:46'! extraFields: aByteArray extraFields _ aByteArray! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:27'! fileDate ^fileDate ifNil:[0]! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:44'! fileDate: aNumber fileDate _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:27'! fileTime ^fileTime ifNil:[0]! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:44'! fileTime: aNumber fileTime _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:23'! flags ^flags ifNil:[0]! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:44'! flags: aNumber flags _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:32'! localHeaderStart ^localHeaderStart! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:32'! localHeaderStart: aNumber localHeaderStart _ aNumber.! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:43'! name ^name! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:43'! name: aString name _ aString! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:45'! uncompressedSize ^uncompressedSize! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:46'! uncompressedSize: aNumber uncompressedSize _ aNumber! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 22:48'! version ^version ifNil:[20]! ! !ZipFileEntry methodsFor: 'accessing' stamp: 'ar 2/19/2001 21:43'! version: aNumber version _ aNumber! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 23:19'! diskNumberStart ^0! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 22:14'! diskNumberStart: aNumber! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 23:19'! externalFileAttributes ^0! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 22:15'! externalFileAttributes: aNumber! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 23:20'! internalFileAttributes ^0! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 22:15'! internalFileAttributes: aNumber! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 23:20'! versionMadeBy ^self version! ! !ZipFileEntry methodsFor: 'ignored properties' stamp: 'ar 2/19/2001 22:14'! versionMadeBy: aNumber! ! !ZipFileEntry methodsFor: 'printing' stamp: 'ar 2/19/2001 22:47'! printOn: aStream super printOn: aStream. aStream nextPutAll:'('; print: name; nextPutAll:' compressed: '; print: compressedSize; nextPutAll:' uncompressed: '; print: uncompressedSize; nextPutAll:' version: '; print: version; nextPutAll:' method: '; print: method; nextPutAll:' flags: '; print: flags; nextPutAll:')'.! ! !ZipFileEntryWriteStream methodsFor: 'initialize-release' stamp: 'ar 2/19/2001 23:48'! writeFooter "No footer needed"! ! !ZipFileEntryWriteStream methodsFor: 'initialize-release' stamp: 'ar 2/19/2001 23:48'! writeHeader "No header needed"! ! !ZipFileDirectory reorganize! ('reading' positionToAnyHeader: readCentralFileHeaderFrom: readEndOfCentralDirectoryFrom: readGlobalZipEntriesIn: readLocalFileHeaderFrom: readLocalZipEntriesIn: readZipEntriesIn:) ('writing' writeCentralFileHeader:on: writeEndOf:centralHeader:on: writeFileData:on: writeLocalFileHeader:on: writeZipEntries:on:) !