(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich. Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *) MODULE ethZip; (** Stefan Walthert **) IMPORT Files, Zlib := ethZlib, ZlibReaders := ethZlibReaders, ZlibWriters := ethZlibWriters; CONST (** result codes **) Ok* = 0; (** operation on zip-file was successful **) FileError* = -1; (** file not found **) NotZipArchiveError* = -2; (** file is not in zip format **) EntryNotFound* = -3; (** specified file was not found in zip-file **) EntryAlreadyExists* = -4; (** file is already stored in zip-file -> can not add specified file to zip-file **) NotSupportedError* = -5; (** can not extract specified file (compression method not supported/file is encrypted) **) DataError* = -6; (** file is corrupted **) BadName* = -7; (** bad file name *) ReaderError* = -8; (** e.g. Reader not opened before Read **) (** compression levels **) DefaultCompression* = ZlibWriters.DefaultCompression; NoCompression* = ZlibWriters.NoCompression; BestSpeed* = ZlibWriters.BestSpeed; BestCompression* = ZlibWriters.BestCompression; (** compression strategies **) DefaultStrategy* = ZlibWriters.DefaultStrategy; Filtered* = ZlibWriters.Filtered; HuffmanOnly* = ZlibWriters.HuffmanOnly; (* support *) Supported = 0; (* can extract file *) IncompatibleVersion = 1; (* version needed to extract < PKZIP 1.00 *) Encrypted = 2; (* file is encrypted *) UnsupCompMethod = 3; (* file not stored or deflated *) Stored = 0; (* file is stored (no compression) *) Deflated = 8; (* file is deflated *) SupportedCompMethods = {Stored, Deflated}; CompatibleVersions = 1; (* versions >= CompatibleVersions are supported *) (* headers *) LocalFileHeaderSignature = 04034B50H; CentralFileHeaderSignature = 02014B50H; EndOfCentralDirSignature = 06054B50H; TYPE Entry* = POINTER TO EntryDesc; (** description of a file stored in the zip-archive **) EntryDesc* = RECORD name-: ARRAY 256 OF CHAR; (** name of file stored in the zip-archive **) method: INTEGER; (* compression method *) time-, date-: LONGINT; (** (Oberon) time and date when file was last modified **) crc32: LONGINT; (* checksum of uncompressed file data *) compSize-, uncompSize-: LONGINT; (** size of compressed / uncompressed file **) intFileAttr: INTEGER; (* internal file attributes, not used in this implementation *) extFileAttr: LONGINT; (* external file attributes, not used in this implementation *) extraField (* for future expansions *), comment-: POINTER TO ARRAY OF CHAR; (** comment for this file **) genPurpBitFlag: INTEGER; support: SHORTINT; dataDescriptor: BOOLEAN; (* if set, data descriptor after (compressed) file data *) offsetLocal: LONGINT; (* offset of file header in central directory *) offsetFileData: LONGINT; (* offset of (compressed) file data *) offsetCentralDir: LONGINT; (* offset of local file header *) next: Entry END; Archive* = POINTER TO ArchiveDesc; (** description of a zipfile **) ArchiveDesc* = RECORD nofEntries-: INTEGER; (** total number of files stored in the zipfile **) comment-: POINTER TO ARRAY OF CHAR; (** comment for zipfile **) file: Files.File; (* pointer to the according zip-file *) offset: LONGINT; (* offset of end of central dir record *) firstEntry, lastEntry: Entry (* first and last Entry of Archive *) END; Reader* = POINTER TO ReaderDesc; ReaderDesc* = RECORD (** structure for reading from a zip-file into a buffer **) res-: LONGINT; (** result of last operation **) open: BOOLEAN; ent: Entry END; UncompReader = POINTER TO UncompReaderDesc; UncompReaderDesc = RECORD (ReaderDesc) (* structur for reading from a uncompressed entry *) fr: Files.Rider; crc32: LONGINT; (* crc32 of uncomressed data *) END; DefReader = POINTER TO DefReaderDesc; DefReaderDesc = RECORD (ReaderDesc) (* structure for reading from a deflated entry *) zr: ZlibReaders.Reader END; (* length of str *) PROCEDURE StringLength(VAR str(* in *): ARRAY OF CHAR): LONGINT; VAR i, l: LONGINT; BEGIN l := LEN(str); i := 0; WHILE (i < l) & (str[i] # 0X) DO INC(i) END; RETURN i END StringLength; (* Converts Oberon time into MS-DOS time *) PROCEDURE OberonToDosTime(t: LONGINT): INTEGER; BEGIN RETURN SHORT(t DIV 1000H MOD 20H * 800H + t DIV 40H MOD 40H * 20H + t MOD 40H DIV 2) END OberonToDosTime; (* Converts Oberon date into MS-DOS time *) PROCEDURE OberonToDosDate(d: LONGINT): INTEGER; BEGIN RETURN SHORT((d DIV 200H + 1900 - 1980) * 200H + d MOD 200H) END OberonToDosDate; (* Converts MS-DOS time into Oberon time *) PROCEDURE DosToOberonTime(t: INTEGER): LONGINT; BEGIN RETURN LONG(t) DIV 800H MOD 20H * 1000H + t DIV 20H MOD 40H * 40H + t MOD 20H * 2 END DosToOberonTime; (* Converts MS-DOS date into Oberon date *) PROCEDURE DosToOberonDate(d: INTEGER): LONGINT; BEGIN RETURN (LONG(d) DIV 200H MOD 80H + 1980 - 1900) * 200H + d MOD 200H END DosToOberonDate; (* Copy len bytes from src to dst; if compCRC32 is set, then the crc 32-checksum is computed *) PROCEDURE Copy(VAR src, dst: Files.Rider; len: LONGINT; compCRC32: BOOLEAN; VAR crc32: LONGINT); CONST BufSize = 4000H; VAR n: LONGINT; buf: ARRAY BufSize OF CHAR; BEGIN IF compCRC32 THEN crc32 := Zlib.CRC32(0, buf, -1, -1) END; REPEAT IF len < BufSize THEN n := len ELSE n := BufSize END; Files.ReadBytes(src, buf, n); IF compCRC32 THEN crc32 := Zlib.CRC32(crc32, buf, 0, n - src.res) END; Files.WriteBytes(dst, buf, n - src.res); DEC(len, n) UNTIL len = 0 END Copy; (* Reads an Entry, r must be at the start of a file header; returns NIL if read was not successful *) PROCEDURE ReadEntry(VAR r: Files.Rider): Entry; VAR ent: Entry; intDummy, nameLen, extraLen, commentLen: INTEGER; longDummy: LONGINT; bufDummy: ARRAY 256 OF CHAR; BEGIN Files.ReadLInt(r, longDummy); IF longDummy = CentralFileHeaderSignature THEN NEW(ent); ent.offsetCentralDir := Files.Pos(r) - 4; ent.support := 0; Files.ReadInt(r, intDummy); (* version made by *) Files.ReadInt(r, intDummy); (* version needed to extract *) IF (intDummy MOD 100H) / 10 < CompatibleVersions THEN ent.support := IncompatibleVersion END; Files.ReadInt(r, ent.genPurpBitFlag); (* general purpose bit flag *) IF ODD(intDummy) THEN ent.support := Encrypted (* bit 0: if set, file encrypted *) END; ent.dataDescriptor := ODD(intDummy DIV 8); (* bit 3: data descriptor after (compressed) file data *) Files.ReadInt(r, ent.method); (* compression method *) IF (ent.support = Supported) & ~(ent.method IN SupportedCompMethods) THEN ent.support := UnsupCompMethod END; Files.ReadInt(r, intDummy); ent.time := DosToOberonTime(intDummy); (* last mod file time *) Files.ReadInt(r, intDummy); ent.date := DosToOberonDate(intDummy); (* last mod file date *) Files.ReadLInt(r, ent.crc32); (* crc-32 *) Files.ReadLInt(r, ent.compSize); (* compressed size *) Files.ReadLInt(r, ent.uncompSize); (* uncompressed size *) Files.ReadInt(r, nameLen); (* filename length *) Files.ReadInt(r, extraLen); (* extra field length *) Files.ReadInt(r, commentLen); (* file comment length *) Files.ReadInt(r, intDummy); (* disk number start *) Files.ReadInt(r, ent.intFileAttr); (* internal file attributes *) Files.ReadLInt(r, ent.extFileAttr); (* external file attributes *) Files.ReadLInt(r, ent.offsetLocal); (* relative offset of local header *) Files.ReadBytes(r, ent.name, nameLen); (* filename *) IF extraLen # 0 THEN NEW(ent.extraField, extraLen); Files.ReadBytes(r, ent.extraField^, extraLen) (* extra field *) END; IF commentLen > 0 THEN NEW(ent.comment, commentLen); Files.ReadBytes(r, ent.comment^, commentLen) (* file comment *) END; (* read extra field length in the local file header (can be different from extra field length stored in the file header...) *) longDummy := Files.Pos(r); (* store actual position of file reader *) Files.Set(r, Files.Base(r), ent.offsetLocal + 28); (* set r to position of extra field length in local file header *) Files.ReadInt(r, extraLen); (* extra field length *) ent.offsetFileData := ent.offsetLocal + 30 + nameLen + extraLen; (* compute offset of file data *) Files.Set(r, Files.Base(r), longDummy); (* set position of file reader to previous position *) IF r.eof THEN (* if file is a zip-archive, r is not at end of file *) ent := NIL END END; RETURN ent; END ReadEntry; (* Writes a local file header *) PROCEDURE WriteLocalFileHeader(ent: Entry; VAR r: Files.Rider); BEGIN Files.WriteLInt(r, LocalFileHeaderSignature); (* local file header signature *) Files.WriteInt(r, CompatibleVersions * 10); (* version needed to extract *) Files.WriteInt(r, ent.genPurpBitFlag); (* general purpose bit flag *) Files.WriteInt(r, ent.method); (* compression method *) Files.WriteInt(r, OberonToDosTime(ent.time)); (* last mod file time *) Files.WriteInt(r, OberonToDosDate(ent.date)); (* last mod file date *) Files.WriteLInt(r, ent.crc32); (* crc-32 *) Files.WriteLInt(r, ent.compSize); (* compressed size *) Files.WriteLInt(r, ent.uncompSize); (* uncompressed size *) Files.WriteInt(r, SHORT(StringLength(ent.name))); (* filename length *) IF ent.extraField # NIL THEN Files.WriteInt(r, SHORT(LEN(ent.extraField^))) (* extra field length *) ELSE Files.WriteInt(r, 0) END; Files.WriteBytes(r, ent.name, StringLength(ent.name)); (* filename *) IF ent.extraField # NIL THEN Files.WriteBytes(r, ent.extraField^, LEN(ent.extraField^)) (* extra field *) END END WriteLocalFileHeader; (* Writes file header in central directory, updates ent.offsetCentralDir *) PROCEDURE WriteFileHeader(ent: Entry; VAR r: Files.Rider); BEGIN ent.offsetCentralDir := Files.Pos(r); Files.WriteLInt(r, CentralFileHeaderSignature); (* central file header signature *) Files.WriteInt(r, CompatibleVersions * 10); (* version made by *) Files.WriteInt(r, CompatibleVersions * 10); (* version needed to extract *) Files.WriteInt(r, ent.genPurpBitFlag); (* general purpose bit flag *) Files.WriteInt(r, ent.method); (* compression method *) Files.WriteInt(r, OberonToDosTime(ent.time)); (* last mod file time *) Files.WriteInt(r, OberonToDosDate(ent.date)); (* last mod file date *) Files.WriteLInt(r, ent.crc32); (* crc-32 *) Files.WriteLInt(r, ent.compSize); (* compressed size *) Files.WriteLInt(r, ent.uncompSize); (* uncompressed size *) Files.WriteInt(r, SHORT(StringLength(ent.name))); (* filename length *) IF ent.extraField = NIL THEN Files.WriteInt(r, 0) ELSE Files.WriteInt(r, SHORT(LEN(ent.extraField^))); (* extra field length *) END; IF ent.comment = NIL THEN Files.WriteInt(r, 0) ELSE Files.WriteInt(r, SHORT(LEN(ent.comment^))); (* file comment length *) END; Files.WriteInt(r, 0); (* disk number start *) Files.WriteInt(r, ent.intFileAttr); (* internal file attributes *) Files.WriteLInt(r, ent.extFileAttr); (* external file attributes *) Files.WriteLInt(r, ent.offsetLocal); (* relative offset of local header *) Files.WriteBytes(r, ent.name, StringLength(ent.name)); (* filename *) IF ent.extraField # NIL THEN Files.WriteBytes(r, ent.extraField^, LEN(ent.extraField^)) (* extra field *) END; IF ent.comment # NIL THEN Files.WriteBytes(r, ent.comment^, LEN(ent.comment^)) (* file comment *) END END WriteFileHeader; (* Writes end of central directory record *) PROCEDURE WriteEndOfCentDir(arc: Archive; VAR r: Files.Rider); VAR size: LONGINT; BEGIN Files.WriteLInt(r, EndOfCentralDirSignature); (* end of central dir signature *) Files.WriteInt(r, 0); (* number of this disk *) Files.WriteInt(r, 0); (* number of the disk with the start of the central directory *) Files.WriteInt(r, arc.nofEntries); (* total number of entries in the central dir on this disk *) Files.WriteInt(r, arc.nofEntries); (* total number of entries in the central dir *) IF arc.firstEntry # NIL THEN Files.WriteLInt(r, arc.offset - arc.firstEntry.offsetCentralDir) (* size of the central directory (without end of central dir record) *) ELSE Files.WriteLInt(r, 0) END; IF arc.firstEntry = NIL THEN Files.WriteLInt(r, arc.offset) (* offset of start of central directory with respect to the starting disk number *) ELSE Files.WriteLInt(r, arc.firstEntry.offsetCentralDir) (* offset of start of central directory with respect to the starting disk number *) END; IF arc.comment = NIL THEN Files.WriteInt(r, 0) (* zipfile comment length *) ELSE Files.WriteInt(r, SHORT(LEN(arc.comment^))); (* zipfile comment length *) Files.WriteBytes(r, arc.comment^, LEN(arc.comment^)) (* zipfile comment *) END END WriteEndOfCentDir; (* Writes central directory + end of central directory record, updates arc.offset and offsetCentralDir of entries *) PROCEDURE WriteCentralDirectory(arc: Archive; VAR r: Files.Rider); VAR ent: Entry; BEGIN ent := arc.firstEntry; WHILE ent # NIL DO WriteFileHeader(ent, r); ent := ent.next END; arc.offset := Files.Pos(r); WriteEndOfCentDir(arc, r) END WriteCentralDirectory; (** Returns an Archive data structure corresponding to the specified zipfile; possible results: - Ok: operation was successful - FileError: file with specified name does not exist - NotZipArchiveError: file is not a correct zipfile **) PROCEDURE OpenArchive*(name: ARRAY OF CHAR; VAR res: LONGINT): Archive; VAR arc: Archive; ent: Entry; f: Files.File; r: Files.Rider; longDummy: LONGINT; intDummy: INTEGER; BEGIN res := Ok; f := Files.Old(name); IF f = NIL THEN res := FileError ELSIF Files.Length(f) < 22 THEN res := NotZipArchiveError ELSE longDummy := 0; Files.Set(r, f, Files.Length(f) - 17); WHILE (longDummy # EndOfCentralDirSignature) & (Files.Pos(r) > 4) DO Files.Set(r, f, Files.Pos(r) - 5); Files.ReadLInt(r, longDummy) END; IF longDummy # EndOfCentralDirSignature THEN res := NotZipArchiveError ELSE NEW(arc); arc.file := f; arc.offset := Files.Pos(r) - 4; Files.ReadInt(r, intDummy); (* number of this disk *) Files.ReadInt(r, intDummy); (* number of the disk with the start of the central directory *) Files.ReadInt(r, intDummy); (* total number of entries in the central dir on this disk *) Files.ReadInt(r, arc.nofEntries); (* total number of entries in the central dir *) Files.ReadLInt(r, longDummy); (* size of the central directory *) Files.ReadLInt(r, longDummy); (* offset of start of central directory with respect to the starting disk number *) Files.ReadInt(r, intDummy); (* zipfile comment length *) IF intDummy # 0 THEN NEW(arc.comment, intDummy); Files.ReadBytes(r, arc.comment^, intDummy) (* zipfile comment *) END; IF Files.Pos(r) # Files.Length(f) THEN res := NotZipArchiveError; arc := NIL ELSE Files.Set(r, f, longDummy); (* set r on position of first file header in central dir *) arc.firstEntry := ReadEntry(r); arc.lastEntry := arc.firstEntry; ent := arc.firstEntry; intDummy := 0; WHILE ent # NIL DO arc.lastEntry := ent; INC(intDummy); (* count number of entries *) ent.next := ReadEntry(r); ent := ent.next END; IF intDummy # arc.nofEntries THEN res := NotZipArchiveError; arc := NIL END END; Files.Close(f) END END; RETURN arc END OpenArchive; (** Returns an Archive that corresponds to a file with specified name; if there is already a zip-file with the same name, this already existing archive is returned; possible results: cf. OpenArchive **) PROCEDURE CreateArchive*(VAR name: ARRAY OF CHAR; VAR res: LONGINT): Archive; VAR f: Files.File; r: Files.Rider; arc: Archive; BEGIN f := Files.Old(name); IF f # NIL THEN RETURN OpenArchive(name, res) ELSE f := Files.New(name); NEW(arc); arc.file := f; arc.nofEntries := 0; arc.offset := 0; Files.Set(r, f, 0); WriteEndOfCentDir(arc, r); Files.Register(f); res := Ok; RETURN arc END END CreateArchive; (** Returns the first entry of the Archive arc (NIL if there is no Entry) **) PROCEDURE FirstEntry*(arc: Archive): Entry; BEGIN IF arc = NIL THEN RETURN NIL ELSE RETURN arc.firstEntry END END FirstEntry; (** Returns the next Entry after ent **) PROCEDURE NextEntry*(ent: Entry): Entry; BEGIN RETURN ent.next END NextEntry; (** Returns the Entry that corresponds to the file with the specified name and that is stored in the Archive arc; possible results: - Ok: Operation was successful - NotZipArchiveError: arc is not a valid Archive - EntryNotFound: no Entry corresponding to name was found **) PROCEDURE GetEntry*(arc: Archive; VAR name: ARRAY OF CHAR; VAR res: LONGINT): Entry; VAR ent: Entry; BEGIN IF arc = NIL THEN res := NotZipArchiveError ELSE ent := arc.firstEntry; WHILE (ent # NIL) & (ent.name # name) DO ent := ent.next END; IF ent = NIL THEN res := EntryNotFound ELSE res := Ok END END; RETURN ent END GetEntry; (** Uncompresses and writes the data of Entry ent to Files.Rider dst; possible results: - Ok: Data extracted - NotZipArchiveError: arc is not a valid zip-archive - EntryNotFound: ent is not an Entry of arc - NotSupportedError: data of ent are encrypted or compression method is not supported - DataError: zipfile is corrupted - BadName: entry has a bad file name **) PROCEDURE ExtractEntry*(arc: Archive; ent: Entry; VAR dst: Files.Rider; VAR res: LONGINT); VAR src: Files.Rider; crc32: LONGINT; BEGIN IF arc = NIL THEN res := NotZipArchiveError ELSIF Files.Base(dst) = NIL THEN res := BadName ELSIF (ent = NIL) OR (ent # GetEntry(arc, ent.name, res)) THEN res := EntryNotFound ELSIF ~(ent.method IN SupportedCompMethods) OR (ent.support > Supported) THEN res := NotSupportedError ELSE CASE ent.method OF | Stored: Files.Set(src, arc.file, ent.offsetFileData); Copy(src, dst, ent.uncompSize, TRUE, crc32); IF crc32 = ent.crc32 THEN res := Ok ELSE res := DataError END | Deflated: Files.Set(src, arc.file, ent.offsetFileData); ZlibReaders.Uncompress(src, dst, crc32, res); IF (res = ZlibReaders.Ok) & (crc32 = ent.crc32) THEN res := Ok ELSE res := DataError END END; IF res = Ok THEN Files.Close(Files.Base(dst)); END END END ExtractEntry; (** Reads and compresses len bytes from Files.Rider src with specified level and strategy and writes them to a new Entry in the Archive arc; possible results: - Ok: file was added to arc - NotZipArchiveError: arc is not a valid zip-archive - EntryAlreadyExists: there is already an Entry in arc with the same name - DataError: error during compression - BadName: src is not based on a valid file **) PROCEDURE AddEntry*(arc: Archive; VAR name: ARRAY OF CHAR; VAR src: Files.Rider; len: LONGINT; level, strategy: SHORTINT; VAR res: LONGINT); VAR dst: Files.Rider; ent: Entry; start: LONGINT; BEGIN IF arc = NIL THEN res := NotZipArchiveError ELSIF Files.Base(src) = NIL THEN res := BadName ELSIF (GetEntry(arc, name, res) # NIL) & (res = Ok) THEN res := EntryAlreadyExists ELSE NEW(ent); COPY(name, ent.name); ent.genPurpBitFlag := 0; IF level = NoCompression THEN ent.method := Stored ELSE ent.method := Deflated END; Files.GetDate(Files.Base(src), ent.time, ent.date); ent.uncompSize := len; ent.intFileAttr := 0; ent.extFileAttr := 0; ent.comment := NIL; ent.support := Supported; ent.dataDescriptor := FALSE; IF arc.firstEntry # NIL THEN ent.offsetLocal := arc.firstEntry.offsetCentralDir ELSE ent.offsetLocal := 0 END; Files.Set(dst, arc.file, ent.offsetLocal); WriteLocalFileHeader(ent, dst); ent.offsetFileData := Files.Pos(dst); Files.Close(arc.file); start := Files.Pos(src); IF level = 0 THEN Copy(src, dst, len, TRUE, ent.crc32); ent.compSize := len; res := Ok ELSE ZlibWriters.Compress(src, dst, len, ent.compSize, level, strategy, ent.crc32, res); IF res # ZlibWriters.Ok THEN res := DataError ELSE res := Ok END END; IF res = Ok THEN ent.uncompSize := Files.Pos(src) - start; Files.Close(arc.file); Files.Set(dst, arc.file, ent.offsetLocal + 14); Files.WriteLInt(dst, ent.crc32); Files.WriteLInt(dst, ent.compSize); Files.Close(arc.file); IF arc.lastEntry # NIL THEN arc.lastEntry.next := ent ELSE (* archive has no entries *) arc.firstEntry := ent END; arc.lastEntry := ent; INC(arc.nofEntries); Files.Set(dst, arc.file, ent.offsetFileData + ent.compSize); WriteCentralDirectory(arc, dst); Files.Close(arc.file); res := Ok END; END END AddEntry; (** Deletes Entry ent from Archive arc; Possible results: - Ok: ent was deleted, ent is set to NIL - NotZipArchiveError: arc is not a valid zip-archive - EntryNotFound: ent is not an Entry of Archive arc **) PROCEDURE DeleteEntry*(arc: Archive; VAR ent: Entry; VAR res: LONGINT); CONST BufSize = 4000H; VAR f: Files.File; r1, r2: Files.Rider; ent2: Entry; arcname: ARRAY 256 OF CHAR; buf: ARRAY BufSize OF CHAR; offset, diff: LONGINT; BEGIN IF arc = NIL THEN res := NotZipArchiveError ELSIF arc.firstEntry = NIL THEN res := EntryNotFound ELSIF arc.firstEntry = ent THEN offset := arc.firstEntry.offsetLocal; (* arc.firstEntry.offsetLocal = 0 *) IF arc.lastEntry = arc.firstEntry THEN arc.lastEntry := arc.firstEntry.next (* = NIL *) END; arc.firstEntry := arc.firstEntry.next; ent2 := arc.firstEntry; res := Ok ELSE ent2 := arc.firstEntry; WHILE (ent2.next # NIL) & (ent2.next # ent) DO ent2 := ent2.next END; IF ent2.next = NIL THEN res := EntryNotFound ELSE IF arc.lastEntry = ent2.next THEN arc.lastEntry := ent2 END; offset := ent2.next.offsetLocal; ent2.next := ent2.next.next; ent2 := ent2.next; res := Ok END END; IF res = Ok THEN Files.GetName(arc.file, arcname); f := Files.New(arcname); Files.Set(r2, f, 0); Files.Set(r1, arc.file, 0); Copy(r1, r2, offset, FALSE, diff); (* no crc 32-checksum is computed -> diff used as dummy *) Files.Close(f); ASSERT(ent2 = ent.next); IF ent2 # NIL THEN Files.Set(r1, arc.file, ent2.offsetLocal); Copy(r1, r2, arc.firstEntry.offsetCentralDir - ent2.offsetLocal, FALSE, diff); (* arc.firstEntry can not be NIL because ent # NIL *) Files.Close(f); diff := ent2.offsetLocal - offset ELSE diff := arc.offset - offset END; WHILE (ent2 # NIL) DO (* update offsets of entries *) DEC(ent2.offsetLocal, diff); DEC(ent2.offsetFileData, diff); DEC(ent2.offsetCentralDir, diff); ent2 := ent2.next END; DEC(arc.offset, diff); DEC(arc.nofEntries); WriteCentralDirectory(arc, r2); Files.Register(f); arc.file := f; ent := NIL END END DeleteEntry; (** open a Reader to read uncompressed data from a zip entry directly to memory **) PROCEDURE OpenReader*(arc: Archive; ent: Entry): Reader; VAR dummyBuf: ARRAY 1 OF CHAR; fr: Files.Rider; r: Reader; ur: UncompReader; dr: DefReader; BEGIN IF ent.support = Supported THEN IF ent.method = Stored THEN NEW(ur); ur.crc32 := Zlib.CRC32(0, dummyBuf, -1, -1); Files.Set(ur.fr, arc.file, ent.offsetFileData); r := ur; r.open := TRUE; r.res := Ok ELSIF ent.method = Deflated THEN Files.Set(fr, arc.file, ent.offsetFileData); NEW(dr); ZlibReaders.Open(dr.zr, FALSE, fr); dr.res := dr.zr.res; r := dr; r.open := TRUE ELSE NEW(r); r.open := FALSE; r.res := NotSupportedError END; ELSE NEW(r); r.open := FALSE; r.res := NotSupportedError END; r.ent := ent; RETURN r; END OpenReader; (** read len bytes of uncompressed data into buf[offset] and return number of bytes actually read; Reader must be opened **) PROCEDURE ReadBytes*(r: Reader; VAR buf: ARRAY OF CHAR; offset, len: LONGINT; VAR read: LONGINT); VAR bufp: POINTER TO ARRAY OF CHAR; i: LONGINT; BEGIN IF r.open THEN IF r IS UncompReader THEN IF offset = 0 THEN Files.ReadBytes(r(UncompReader).fr, buf, len); ELSE NEW(bufp, len); Files.ReadBytes(r(UncompReader).fr, bufp^, len); FOR i := 0 TO len - 1 DO buf[offset + i] := bufp[i] END END; read := len - r(UncompReader).fr.res; r(UncompReader).crc32 := Zlib.CRC32(r(UncompReader).crc32, buf, offset, read) ELSIF r IS DefReader THEN ZlibReaders.ReadBytes(r(DefReader).zr, buf, offset, len, read); r.res := r(DefReader).zr.res END ELSE r.res := ReaderError END END ReadBytes; (** read decompressed byte **) PROCEDURE Read*(r: Reader; VAR ch: CHAR); VAR buf: ARRAY 1 OF CHAR; read: LONGINT; BEGIN ReadBytes(r, buf, 0, 1, read); ch := buf[0]; END Read; (** close Reader **) PROCEDURE Close*(r: Reader); BEGIN IF r.open THEN IF r IS UncompReader THEN IF r(UncompReader).crc32 # r.ent.crc32 THEN r.res := DataError ELSE r.res := Ok END ELSIF r IS DefReader THEN ZlibReaders.Close(r(DefReader).zr); IF r(DefReader).zr.crc32 # r.ent.crc32 THEN r.res := DataError ELSE r.res := r(DefReader).zr.res END ELSE r.res := ReaderError END; r.open := FALSE ELSE r.res := ReaderError END END Close; END ethZip.