MODULE Unzip;
IMPORT Streams, Inflate, CRC, Files, Dates, Strings, Commands;
CONST
EndOfCentralDirSig = 006054B50H;
CentralFileHeadSig = 002014B50H;
LocalFileHeadSig = 004034B50H;
TYPE
Entry* = POINTER TO RECORD
method, pos: LONGINT;
crc*, csize*, size*: LONGINT;
td*: Dates.DateTime;
name*: Strings.String;
next: Entry
END;
SizeReader = OBJECT
VAR input: Streams.Reader; max: LONGINT;
PROCEDURE Receive(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
BEGIN
IF min > max THEN min := max END;
input.Bytes(buf, ofs, min, len);
DEC(max, len); res := input.res;
IF (max = 0) & (res = Streams.Ok) THEN
res := Streams.EOF
END
END Receive;
PROCEDURE &Init*(input: Streams.Reader; size: LONGINT);
BEGIN
SELF.input := input; SELF.max := size
END Init;
END SizeReader;
ZipFile* = OBJECT
VAR
F: Files.File;
root: Entry; entries: LONGINT;
PROCEDURE FindEntry*(CONST name: ARRAY OF CHAR): Entry;
VAR e: Entry; i: LONGINT;
BEGIN
e := root; i := 0;
WHILE (e # NIL) & (e.name^ # name) DO
e := e.next
END;
RETURN e
END FindEntry;
PROCEDURE GetFirst*(): Entry;
BEGIN
RETURN root
END GetFirst;
PROCEDURE GetNext*(e: Entry): Entry;
BEGIN
RETURN e.next
END GetNext;
PROCEDURE NoOfEntries*(): LONGINT;
BEGIN
RETURN entries
END NoOfEntries;
PROCEDURE OpenReceiver*(VAR R: Streams.Receiver; entry: Entry; VAR res: LONGINT);
VAR fR: Files.Reader; sig: LONGINT; e: Entry; I: Inflate.Reader; S: SizeReader;
BEGIN
R := NIL; res := Streams.FormatError;
Files.OpenReader(fR, F, entry.pos); fR.RawLInt(sig);
IF sig # LocalFileHeadSig THEN RETURN END;
NEW(e); ReadEntry(fR, e, TRUE);
IF e.crc = entry.crc THEN
IF e.method = 8 THEN
NEW(I, fR); R := I.Receive; res := Streams.Ok
ELSIF (e.method = 0) & (e.size = e.csize) THEN
NEW(S, fR, e.size); R := S.Receive; res := Streams.Ok
END
END
END OpenReceiver;
PROCEDURE Extract*(entry: Entry; dest: Streams.Writer; VAR res: LONGINT);
VAR receiver : Streams.Receiver; R: Streams.Reader; buf: ARRAY 1024 OF CHAR; l: LONGINT; crc: CRC.CRC32Stream;
BEGIN
OpenReceiver(receiver, entry, res);
NEW(R, receiver, 1024);
IF res # Streams.Ok THEN RETURN END;
NEW(crc);
R.Bytes(buf, 0, 1024, l);
WHILE l > 0 DO
dest.Bytes(buf, 0, l); crc.Bytes(buf, 0, l);
R.Bytes(buf, 0, 1024, l)
END;
crc.Update();
IF R.res = Streams.EOF THEN
IF entry.crc = crc.GetCRC() THEN
res := Streams.Ok
END
ELSE
res := R.res
END
END Extract;
PROCEDURE &New*(F: Files.File; VAR res: LONGINT);
VAR R: Files.Reader; r, e: Entry; pos, sig, l, j: LONGINT; i: INTEGER;
BEGIN
res := Streams.Ok; SELF.F := NIL; root := NIL; entries := 0;
pos := F.Length()-20; sig := 0;
WHILE (sig # EndOfCentralDirSig) & (pos > 0) DO
DEC(pos);
Files.OpenReader(R, F, pos);
R.RawLInt(sig)
END;
IF sig # EndOfCentralDirSig THEN res := Streams.FormatError; RETURN END;
R.RawInt(i); R.RawInt(i);
R.RawInt(i); entries := i;
R.RawInt(i); R.RawLInt(l);
R.RawLInt(pos);
IF R.res # Streams.Ok THEN res := R.res END;
IF (pos < 0) OR (pos >= F.Length()) THEN res := Streams.FormatError; RETURN END;
Files.OpenReader(R, F, pos);
NEW(r); r.next := NIL; e := r;
j := 0;
WHILE j < entries DO
NEW(e.next); e := e.next; e.next := NIL;
R.RawLInt(sig);
IF sig = CentralFileHeadSig THEN
ReadEntry(R, e, FALSE)
ELSE
res := Streams.FormatError; RETURN
END;
INC(j)
END;
R.RawLInt(sig);
IF sig # EndOfCentralDirSig THEN res := Streams.FormatError; RETURN END;
IF res = Streams.Ok THEN
SELF.F := F; root := r.next
ELSE
SELF.F := NIL; root := NIL; entries := 0
END
END New;
END ZipFile;
PROCEDURE DosToOberonTime(t: LONGINT): LONGINT;
BEGIN
RETURN t DIV 800H MOD 20H * 1000H + t DIV 20H MOD 40H * 40H + t MOD 20H * 2
END DosToOberonTime;
PROCEDURE DosToOberonDate(d: LONGINT): LONGINT;
BEGIN
RETURN (d DIV 200H MOD 80H + 1980 - 1900) * 200H + d MOD 200H
END DosToOberonDate;
PROCEDURE ReadEntry(R: Streams.Reader; entry: Entry; local: BOOLEAN);
VAR l, nl, xl, t, d: LONGINT; i: INTEGER;
BEGIN
IF local THEN
R.RawInt(i);
R.RawInt(i); R.RawInt(i); entry.method := i;
R.RawInt(i); t := DosToOberonTime(i);
R.RawInt(i); d := DosToOberonDate(i);
entry.td := Dates.OberonToDateTime(d, t);
R.RawLInt(entry.crc);
R.RawLInt(entry.csize);
R.RawLInt(entry.size);
R.RawInt(i); nl := i;
R.RawInt(i); xl := i;
NEW(entry.name, nl+1);
l := 0;
WHILE l < nl DO
R.Char(entry.name[l]); INC(l)
END;
entry.name[l] := 0X;
R.SkipBytes(xl)
ELSE
R.RawInt(i); R.RawInt(i);
R.RawInt(i); R.RawInt(i); entry.method := i;
R.RawInt(i); t := DosToOberonTime(i);
R.RawInt(i); d := DosToOberonDate(i);
entry.td := Dates.OberonToDateTime(d, t);
R.RawLInt(entry.crc);
R.RawLInt(entry.csize);
R.RawLInt(entry.size);
R.RawInt(i); nl := i;
R.RawInt(i); xl := i;
R.RawInt(i); xl := xl + i;
R.RawInt(i); R.RawInt(i);
R.RawLInt(l); R.RawLInt(entry.pos);
NEW(entry.name, nl+1);
l := 0;
WHILE l < nl DO
R.Char(entry.name[l]); INC(l)
END;
entry.name[l] := 0X;
R.SkipBytes(xl)
END
END ReadEntry;
PROCEDURE StripPrefix(CONST long: ARRAY OF CHAR; VAR short: ARRAY OF CHAR);
VAR i, j: LONGINT; ch: CHAR;
BEGIN
i := 0; j := 0; ch := long[0];
WHILE ch # 0X DO
IF (ch = "/") OR (ch = "\") THEN
j := 0
ELSE
short[j] := ch; INC(j)
END;
INC(i); ch := long[i]
END;
short[j] := 0X
END StripPrefix;
PROCEDURE ExtractEntry(w: Streams.Writer; zip: ZipFile; entry: Entry; name: ARRAY OF CHAR; backup, path: BOOLEAN);
VAR F: Files.File; W: Files.Writer; res: LONGINT; bak: Files.FileName;
BEGIN
IF ~path THEN StripPrefix(name, name) END;
w.String(name);
F := Files.New(name);
IF F = NIL THEN
w.String(" failed"); w.Ln(); RETURN
END;
Files.OpenWriter(W, F, 0);
zip.Extract(entry, W, res);
IF res = Streams.Ok THEN
IF backup THEN
COPY(name, bak); Strings.Append(bak, ".Bak");
Files.Rename(name, bak, res);
IF (res # 0) & (res # 2) THEN w.String("Backup failed on "); w.String(name); w.Ln END
END;
W.Update(); Files.Register(F)
ELSE
w.String(" failed")
END;
w.Ln()
END ExtractEntry;
PROCEDURE Extract*(context : Commands.Context);
VAR
F: Files.File; zip: ZipFile; name, fs: Files.FileName; res: LONGINT;
e: Entry; opt: ARRAY 32 OF CHAR; ch: CHAR; backup, path, prefix: BOOLEAN;
BEGIN
context.arg.SkipWhitespace();
backup := TRUE; prefix := FALSE; path := FALSE;
WHILE context.arg.Peek() = "\" DO
context.arg.Char(ch); context.arg.SkipWhitespace(); context.arg.String(opt);
IF opt = "o" THEN
backup := FALSE
ELSIF opt = "d" THEN
path := TRUE
ELSIF opt = "p" THEN
prefix := TRUE;
context.arg.SkipWhitespace(); context.arg.String(fs)
ELSE
context.error.String("unknown option "); context.error.String(opt); context.error.Ln();
RETURN
END;
context.arg.SkipWhitespace()
END;
context.arg.String(name); context.arg.SkipWhitespace();
IF name = "" THEN RETURN END;
F := Files.Old(name);
IF F = NIL THEN RETURN END;
NEW(zip, F, res);
IF res = Streams.Ok THEN
context.arg.String(name);
WHILE name # "" DO
e := zip.FindEntry(name);
IF e # NIL THEN
IF prefix THEN
COPY(fs, name); Strings.Append(name, e.name^)
END;
ExtractEntry(context.out, zip, e, name, backup, path)
ELSE
context.error.String(name); context.error.String(" not found"); context.error.Ln()
END;
context.arg.SkipWhitespace(); context.arg.String(name)
END;
ELSE
context.error.String(name); context.error.String(" is not a valid zip file"); context.error.Ln()
END;
END Extract;
PROCEDURE ExtractAll*(context : Commands.Context);
VAR
F: Files.File; zip: ZipFile; name, fs: Files.FileName; res: LONGINT;
e: Entry; opt: ARRAY 32 OF CHAR; ch: CHAR; backup, path, prefix: BOOLEAN;
BEGIN
context.arg.SkipWhitespace();
backup := TRUE; prefix := FALSE; path := FALSE;
WHILE context.arg.Peek() = "\" DO
context.arg.Char(ch); context.arg.SkipWhitespace(); context.arg.String(opt);
IF opt = "o" THEN
backup := FALSE
ELSIF opt = "d" THEN
path := TRUE
ELSIF opt = "p" THEN
prefix := TRUE;
context.arg.SkipWhitespace(); context.arg.String(fs)
ELSE
context.error.String("unknown option "); context.error.String(opt); context.error.Ln();
RETURN
END;
context.arg.SkipWhitespace()
END;
context.arg.String(name);
WHILE name # "" DO
F := Files.Old(name);
IF F # NIL THEN
NEW(zip, F, res);
IF res = Streams.Ok THEN
e := zip.GetFirst();
WHILE e # NIL DO
IF prefix THEN
COPY(fs, name); Strings.Append(name, e.name^)
ELSE
COPY(e.name^, name)
END;
ExtractEntry(context.out, zip, e, name, backup, path);
e := zip.GetNext(e)
END;
ELSE
context.error.String(name); context.error.String(" is not a valid zip file"); context.error.Ln()
END
ELSE
context.error.String(name); context.error.String(" not found"); context.error.Ln()
END;
context.arg.SkipWhitespace(); context.arg.String(name)
END;
END ExtractAll;
PROCEDURE Directory*(context : Commands.Context);
VAR
F: Files.File; zip: ZipFile; name: Files.FileName; res, i: LONGINT;
e: Entry; opt: ARRAY 32 OF CHAR; ch: CHAR; detail: BOOLEAN;
BEGIN
context.arg.SkipWhitespace();
detail := FALSE;
WHILE context.arg.Peek() = "\" DO
context.arg.Char(ch); context.arg.SkipWhitespace(); context.arg.String(opt);
IF opt = "d" THEN
detail := TRUE
ELSE
context.error.String("unknown option "); context.error.String(opt); context.error.Ln();
RETURN
END;
context.arg.SkipWhitespace()
END;
context.arg.String(name);
IF name = "" THEN RETURN END;
F := Files.Old(name);
IF F = NIL THEN RETURN END;
NEW(zip, F, res);
IF res = Streams.Ok THEN
context.out.String("Directory of "); context.out.String(name);
context.out.Ln(); context.out.Ln();
e := zip.GetFirst(); i := 0;
WHILE e # NIL DO
INC(i);
context.out.String(e.name^);
IF detail THEN
context.out.Char(09X); Strings.DateToStr(e.td, opt); context.out.String(opt);
context.out.String(" "); Strings.TimeToStr(e.td, opt); context.out.String(opt);
context.out.Char(09X); context.out.Int(e.size, 0);
context.out.Char(09X); context.out.Int(e.csize, 0);
context.out.Ln()
ELSE
IF (i MOD 2) = 0 THEN
context.out.Ln()
ELSE
context.out.Char(09X)
END
END;
e := zip.GetNext(e)
END;
context.out.Ln()
ELSE
context.error.String(name); context.error.String(" is not a valid zip file"); context.error.Ln()
END;
END Directory;
END Unzip.
SystemTools.Free Unzip Inflate ~
Inflate.Mod Unzip.Mod