ZipArchive.st
changeset 2861 e5b9ccd39972
parent 2860 04b2ef9b1ef8
child 2893 08c695e14c86
--- a/ZipArchive.st	Wed Dec 05 15:38:32 2012 +0100
+++ b/ZipArchive.st	Sat Dec 08 21:05:28 2012 +0100
@@ -3156,14 +3156,11 @@
 entries
     "return a collection of fileName entries"
 
-    |names|
-
-    names := OrderedCollection new.
-
-    self zipMembersDo:[:zipd |
-        names add:(zipd fileName)
-    ].
-    ^ names
+    ^ zipMembersByName keys
+!
+
+file
+    ^ file
 !
 
 fileSize
@@ -3176,20 +3173,13 @@
 members
     "return a collection of members"
 
-    |members|
-
-    members := OrderedCollection new.
-
-    self zipMembersDo:[:zipd |
-        members add:zipd
-    ].
-    ^ members
+    ^ zipMembersByName values.
 !
 
 membersMatching:aFileMatchPattern
     "return a collection of members which match aFileMatchPattern"
 
-    ^ self members select:[:m | aFileMatchPattern match:m fileName]
+    ^ self zipMembersByName select:[:m | aFileMatchPattern match:m fileName]
 !
 
 name
@@ -3198,6 +3188,12 @@
     ^ archiveName
 !
 
+numberOfEntries
+    "return the number of entries in the archive"
+
+    ^ zipMembersByName size
+!
+
 pathName
     "FileStream compatibility: answer the name of the underlying file - a String"
 
@@ -3211,6 +3207,10 @@
 
 size
     ^self fileSize
+!
+
+zipMembersByName
+    ^ zipMembersByName
 ! !
 
 !ZipArchive methodsFor:'error raising'!
@@ -3259,31 +3259,30 @@
     archiveName := filename name.
     mode := readOrWriteMode.
 
+    self openFile.
     mode ~~ #write ifTrue:[
         |mustCloseFile|
 
-        mustCloseFile := true.
-
-        self openFile.
         [
+            mustCloseFile := true.
             self readDirectory.
             mustCloseFile := false.
 
             mode == #append ifTrue:[
-                members := self entries collect:[:eachEntryName | self findMember:eachEntryName] thenSelect:[:eachEntry | eachEntry notNil].
+                members := self zipMembersByName values.
                 members isEmptyOrNil ifTrue:[^ self].
 
-                maxStartPosition := (members collect:[:eachMember | eachMember fileStart]) max.
-                lastMember := members detect:[:eachMember | eachMember fileStart = maxStartPosition].
-
-                file position0Based:(startOfArchive + lastMember fileStart + lastMember compressedSize).
+                maxStartPosition := members maxApplying:[:eachMember | self dataStartOf:eachMember].
+                lastMember := members detect:[:eachMember | eachMember dataStart = maxStartPosition].
+
+                file position0Based:(startOfArchive + lastMember dataStart + lastMember compressedSize).
                 mode := #write.
             ].
         ] ensure:[
             mustCloseFile ifTrue:[self closeFile].
         ].
-    ] ifFalse:[        
-        self openFile.
+    ] ifFalse:[
+        zipMembersByName := Dictionary new.
     ].
 
     "Modified: / 31-08-2010 / 12:39:25 / sr"
@@ -3294,6 +3293,7 @@
     "initialize the archive to read from aPositionableStream.
      Obsolete - backward compatibility."
 
+    self obsoleteMethodWarning.
     ^ self readingFrom:aPositionableStream
 !
 
@@ -3345,6 +3345,7 @@
     ] ifFalse:[
         archiveName := 'internal stream'.
     ].
+    zipMembersByName := Dictionary new.
 ! !
 
 !ZipArchive methodsFor:'private'!
@@ -3378,6 +3379,57 @@
     ]
 !
 
+dataStartOf:zipEntry
+    "fetch the absolute start address of the data of a given zipEntry.
+     Note: extra field and extra field length may be different from that in
+           the central directory entry. Sow e have to fetch the local header."
+
+    |dataStart fileHeaderStart fileNameLength extraFieldLength|
+
+    dataStart := zipEntry dataStart.
+    dataStart notNil ifTrue:[
+        ^ dataStart.
+    ].
+
+    fileHeaderStart := zipEntry relativeLocalHeaderOffset + startOfArchive.
+    (fileHeaderStart + 30) > endOfArchive ifTrue: [
+        ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - zipEntry end is out of the archive bounds'.
+    ].
+
+    "Now read the fileHeader:
+        0  local file header signature     4 bytes  (0x04034b50)
+        4  version needed to extract       2 bytes
+        6  general purpose bit flag        2 bytes
+        8  compression method              2 bytes
+        10 last mod file time              2 bytes
+        12 last mod file date              2 bytes
+        14 crc-32                          4 bytes
+        18 compressed size                 4 bytes
+        22 uncompressed size               4 bytes
+        26 file name length (x)            2 bytes
+        28 extra field length (y)          2 bytes
+              fixd size total len:    30
+        30 file name (variable size)
+        30+x    extra field (variable size)
+        30+x+y  data
+     Note: extra field and extra field length may be different from that in
+           the central directory entry!!
+    "
+
+    file position0Based:fileHeaderStart+26.
+    fileNameLength := file nextUnsignedShortMSB:false.
+    extraFieldLength := file nextUnsignedShortMSB:false.
+
+    dataStart := fileHeaderStart + 30 + fileNameLength + extraFieldLength.
+
+    (dataStart + (zipEntry compressedSize)) > endOfArchive ifTrue: [
+        ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - zipEntry end is out of the archive bounds'.
+    ].
+    zipEntry dataStart:dataStart.
+
+    ^ dataStart
+!
+
 openFile
     |fn|
 
@@ -3615,18 +3667,6 @@
     "Modified: / 19-11-2010 / 16:23:36 / cg"
 !
 
-addMember
-    "add a zipMember"
-
-    |zmemb |
-
-    self addMember:(zmemb := ZipMember new).
-    ^ zmemb.
-
-    "Created: / 29.3.1998 / 18:22:25 / cg"
-    "Modified: / 9.9.1998 / 20:33:32 / cg"
-!
-
 addMember:zmemb
     "add a zipMember"
 
@@ -3636,6 +3676,14 @@
         lastEntry next:zmemb.
     ].
     lastEntry := zmemb.
+    (zipMembersByName includesKey:zmemb fileName) ifTrue:[
+        "ignore duplicate entries for backward compatibility.
+         Argh: expecco once added wrong duplicates to the end of ets files.
+               The first entry is valid."
+        Transcript showCR:'Duplicate entry in ZIP (ignored): ', zmemb fileName.
+    ] ifFalse:[
+        zipMembersByName at:zmemb fileName put:zmemb.
+    ].
     ^ zmemb.
 
     "Modified: / 30.3.1998 / 17:13:20 / cg"
@@ -3669,20 +3717,7 @@
 findMember:name
     "find a zipMember by name"
 
-"/    zipMembersByName isNil ifTrue:[
-"/        zipMembersByName := Dictionary new.
-"/        self zipMembersDo:[:zipd |
-"/            zipMembersByName at:(zipd fileName) put:zipd.
-"/        ].
-"/    ].
-"/    ^ zipMembersByName at:name ifAbsent:nil.
-
-    self zipMembersDo:[:zipd |
-        (zipd fileName = name) ifTrue:[^ zipd].
-    ].
-    ^ nil
-
-    "Modified: / 18-11-2010 / 20:23:35 / cg"
+    ^ zipMembersByName at:name ifAbsent:[].
 !
 
 findMemberAllowForMissingTrailingSlash: name
@@ -3755,12 +3790,15 @@
         centralDirectory readFrom:file.
 
         "/ set file position to start of central directory
-        (pos0 - (centralDirectory centralDirectorySize)) < startOfArchive ifTrue: [
+        (pos0 - centralDirectory centralDirectoryStartOffset - centralDirectory centralDirectorySize) < startOfArchive ifTrue: [
             ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - central directory start is out of the archive bounds'.
         ].
 
+        startOfArchive := pos0 - centralDirectory centralDirectoryStartOffset - centralDirectory centralDirectorySize.
         file position0Based:(pos0 - (centralDirectory centralDirectorySize)).
 
+        zipMembersByName := Dictionary new:centralDirectory centralDirectoryTotalNoOfEntries.
+
         "/ read central directory entries
         1 to:(centralDirectory centralDirectoryTotalNoOfEntries) do:[:i |
             |zipd filename_length centralFileHeaderSignature relative_offset_local_header 
@@ -3776,8 +3814,7 @@
                 ^ self.
             ].
 
-            zipd := ZipMember new.
-            zipd readCentralDirectoryEntryFrom:file.
+            zipd := ZipMember new readCentralDirectoryEntryFrom:file.
             self addMember:zipd.
         ].
 
@@ -3870,21 +3907,13 @@
 !ZipArchive methodsFor:'queries'!
 
 isValidPath: anArchivePathName
-    ^ self members
-        contains:[:aMember |
-            |fn|
-
-            fn := aMember fileName.
-            ((fn startsWith:anArchivePathName,'/') or:[(fn = anArchivePathName)])
+    self zipMembersByName
+        keysDo:[:eachMemberName |
+            ((eachMemberName startsWith:anArchivePathName,'/') 
+             or:[eachMemberName = anArchivePathName]) ifTrue:[^ true]
         ].
 
-    "/ cg: wrong - what about (isValidPath:'foo'), if there is a file named 'foobar' ?!!
-"/    self members do: [:aMember|
-"/        (aMember fileName startsWith:anArchivePathName) ifTrue:[
-"/            ^ true
-"/        ].
-"/    ].
-"/    ^ false
+    ^ false.
 ! !
 
 !ZipArchive methodsFor:'reading'!
@@ -4038,7 +4067,7 @@
 !
 
 withPositionAndMemberFor:fileName do:aBlock
-    |zmemb  |
+    |zmemb dataStart|
 
     (file isNil or:[mode ~~ #read]) ifTrue:[
         ^ self error: 'ZipArchive not open for reading ...'.
@@ -4046,15 +4075,9 @@
 
     zmemb := self findMember:fileName.
     zmemb isNil ifTrue:[^ nil].
-    (zmemb fileStart + startOfArchive) > endOfArchive ifTrue: [
-        ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - zipEntry start is out of the archive bounds'.
-    ].
-
-    (zmemb fileStart + startOfArchive + (zmemb compressedSize)) > endOfArchive ifTrue: [
-        ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - zipEntry end is out of the archive bounds'.
-    ].
-
-    aBlock value:zmemb value:(zmemb fileStart + startOfArchive)
+
+    dataStart := self dataStartOf:zmemb.
+    aBlock value:zmemb value:dataStart.
 
     "Created: / 21-11-2010 / 11:51:41 / cg"
 ! !
@@ -4064,7 +4087,7 @@
 readStreamFor:nameOfFileInArchive
     "open a stream on archive contents identified by nameOfFileInArchive"
 
-    |zipEntry|
+    |zipEntry dataStart|
 
     (file isNil or:[mode ~~ #read]) ifTrue:[
         ^ OpenError raiseRequestWith:nameOfFileInArchive errorString:'ZipArchive not open for reading ...'.
@@ -4075,15 +4098,8 @@
         ^ OpenError raiseRequestWith:nameOfFileInArchive errorString:'ZipArchive member does not exist: '.
     ].
 
-    (zipEntry fileStart + startOfArchive) > endOfArchive ifTrue: [
-        ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - zipEntry start is out of the archive bounds'.
-    ].
-
-    (zipEntry fileStart + startOfArchive + (zipEntry compressedSize)) > endOfArchive ifTrue: [
-        ^ ZipFileFormatErrorSignal raiseRequestErrorString:' - zipEntry end is out of the archive bounds'.
-    ].
-
-    file position0Based:(zipEntry fileStart + startOfArchive).
+    dataStart := self dataStartOf:zipEntry.
+    file position0Based:dataStart.
 
     ^ (ZipReadStream zipFileStream:file zipEntry:zipEntry)
         zipArchive:self.
@@ -4131,6 +4147,7 @@
 
     <resource: #obsolete>
 
+    self obsoleteMethodWarning.
     ^ self addFile:aDirectoryName withContents:nil compressMethod:COMPRESSION_STORED asDirectory:true.
 
     "Modified: / 19-11-2010 / 15:38:59 / cg"
@@ -4166,8 +4183,6 @@
     ].
 
     zipEntry := ZipMember new default.
-    self addMember:zipEntry.
-
     theZipFileName := self validZipFileNameFrom:aFileName. 
 
     zipEntry fileName: theZipFileName.
@@ -4232,6 +4247,8 @@
     zipEntry uncompressedSize: unCompressedDataSize.
 
     zipEntry rewriteCrcAndSizeTo:file.
+    self addMember:zipEntry.
+
     file setToEnd.
 
     "Modified: / 19-11-2010 / 15:39:32 / cg"
@@ -4289,7 +4306,6 @@
                 theCompressMethod := COMPRESSION_STORED
             ].
     zipEntry := ZipMember new default.
-    self addMember:zipEntry.
     theZipFileName := self validZipFileNameFrom:aFileName.
     zipEntry fileName:theZipFileName.
     zipEntry fileNameLength:theZipFileName size.
@@ -4349,6 +4365,7 @@
     theCompressedData notNil ifTrue:[
         file nextPutBytes:zipEntry compressedSize from:theCompressedData.
     ].
+    self addMember:zipEntry.
 
     "Created: / 18-11-2010 / 19:31:10 / cg"
     "Modified: / 19-11-2010 / 17:47:01 / cg"
@@ -4370,7 +4387,7 @@
 
     |zipEntry curTime curDate theZipFileName theCompressMethod|
 
-    (file isNil or: [mode ~~ #write]) ifTrue: [
+    (file isNil or:[mode ~~ #write]) ifTrue: [
         ^ self error: 'ZipArchive not open for writing ...'.
     ].
 
@@ -4384,8 +4401,6 @@
     ].
 
     zipEntry := ZipMember new default.
-    self addMember:zipEntry.
-
     theZipFileName := self validZipFileNameFrom:nameOfFileInArchive. 
 
     zipEntry fileName: theZipFileName.
@@ -4406,6 +4421,7 @@
     file setToEnd.
 
     zipEntry writeTo:file.
+    self addMember:zipEntry.
 
     ^ (ZipWriteStream zipFileStream:file zipEntry:zipEntry)
         zipArchive:self.
@@ -4644,13 +4660,8 @@
 !
 
 dataStart
-    "tell the file offset, where tha data of this zip entry starts"
-    dataStart isNil ifTrue: [
-        dataStart := relativeLocalHeaderOffset 
-                    + "C_SIZEOFLOCALHEADER" 30 
-                    + fileNameLength 
-                    + extraFieldLength.
-    ].
+    "tell the file offset, where the data of this zip entry starts"
+
     ^ dataStart
     "Created: / 29.3.1998 / 18:28:40 / cg"
 !
@@ -4846,13 +4857,6 @@
 
 !ZipArchive::ZipMember methodsFor:'queries'!
 
-fileStart
-    ^ self dataStart
-    "/ ^ relative_offset_local_header + ZipArchive LREC_SIZE + 4 + name size
-
-    "Created: / 29.3.1998 / 19:10:57 / cg"
-!
-
 isDirectory
     ^ 
     ((externalFileAttributes ? 0) bitTest:EXTERNALFILEATTRIBUTES_ISDIRECTORY)
@@ -4874,15 +4878,15 @@
     lastModFileTime := aStream nextUnsignedShortMSB:false.   
     lastModFileDate := aStream nextUnsignedShortMSB:false.
     crc32 := aStream nextUnsignedLongMSB: false.
-    compressedSize := aStream nextLongMSB:false.     
-    uncompressedSize := aStream nextLongMSB:false.      
+    compressedSize := aStream nextUnsignedLongMSB:false.     
+    uncompressedSize := aStream nextUnsignedLongMSB:false.      
     fileNameLength := aStream nextUnsignedShortMSB:false.   
     extraFieldLength := aStream nextUnsignedShortMSB:false. 
     fileCommentLength := aStream nextUnsignedShortMSB:false. 
     diskNumberStart := aStream nextUnsignedShortMSB:false.  
     internalFileAttributes := aStream nextUnsignedShortMSB:false.   
-    externalFileAttributes := aStream nextLongMSB:false.
-    relativeLocalHeaderOffset := aStream nextLongMSB:false.
+    externalFileAttributes := aStream nextUnsignedLongMSB:false.
+    relativeLocalHeaderOffset := aStream nextUnsignedLongMSB:false.
 
 "/    (aStream position + fileNameLength) > endOfArchive ifTrue: [
 "/        ^ ZipArchive zipFileFormatErrorSignal raiseRequestErrorString:' - central directory entry out of archive bounds'.
@@ -4894,7 +4898,7 @@
 "/        (aStream position + extraFieldLength) > endOfArchive ifTrue: [
 "/            ^ ZipArchive zipFileFormatErrorSignal raiseRequestErrorString:' - central directory entry out of archive bounds'.
 "/        ].
-        extraField := String new:extraFieldLength.
+        extraField := ByteArray new:extraFieldLength.
         aStream nextBytes:extraFieldLength into:extraField.
     ].
 
@@ -5152,11 +5156,11 @@
 !ZipArchive class methodsFor:'documentation'!
 
 version
-    ^ '$Header: /cvs/stx/stx/libbasic2/ZipArchive.st,v 1.100 2012-12-05 14:38:32 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic2/ZipArchive.st,v 1.101 2012-12-08 20:05:28 stefan Exp $'
 !
 
 version_CVS
-    ^ '$Header: /cvs/stx/stx/libbasic2/ZipArchive.st,v 1.100 2012-12-05 14:38:32 stefan Exp $'
+    ^ '$Header: /cvs/stx/stx/libbasic2/ZipArchive.st,v 1.101 2012-12-08 20:05:28 stefan Exp $'
 ! !
 
 ZipArchive initialize!