Unzip.Mod 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. MODULE Unzip; (** AUTHOR "ejz"; PURPOSE "Aos unzip program"; *)
  2. IMPORT Streams, Inflate, CRC, Files, Dates, Strings, Commands;
  3. CONST
  4. EndOfCentralDirSig = 006054B50H;
  5. CentralFileHeadSig = 002014B50H;
  6. LocalFileHeadSig = 004034B50H;
  7. TYPE
  8. Entry* = POINTER TO RECORD
  9. method, pos: LONGINT;
  10. crc*, csize*, size*: LONGINT;
  11. td*: Dates.DateTime;
  12. name*: Strings.String;
  13. next: Entry
  14. END;
  15. SizeReader = OBJECT
  16. VAR input: Streams.Reader; max: LONGINT;
  17. PROCEDURE Receive(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len: LONGINT; VAR res: WORD);
  18. BEGIN
  19. IF min > max THEN min := max END;
  20. input.Bytes(buf, ofs, min, len);
  21. DEC(max, len); res := input.res;
  22. IF (max = 0) & (res = Streams.Ok) THEN
  23. res := Streams.EOF
  24. END
  25. END Receive;
  26. PROCEDURE &Init*(input: Streams.Reader; size: LONGINT);
  27. BEGIN
  28. SELF.input := input; SELF.max := size
  29. END Init;
  30. END SizeReader;
  31. ZipFile* = OBJECT
  32. VAR
  33. F: Files.File;
  34. root: Entry; entries: LONGINT;
  35. PROCEDURE FindEntry*(CONST name: ARRAY OF CHAR): Entry;
  36. VAR e: Entry; i: LONGINT;
  37. BEGIN
  38. e := root; i := 0;
  39. WHILE (e # NIL) & (e.name^ # name) DO
  40. e := e.next
  41. END;
  42. RETURN e
  43. END FindEntry;
  44. PROCEDURE GetFirst*(): Entry;
  45. BEGIN
  46. RETURN root
  47. END GetFirst;
  48. PROCEDURE GetNext*(e: Entry): Entry;
  49. BEGIN
  50. RETURN e.next
  51. END GetNext;
  52. PROCEDURE NoOfEntries*(): LONGINT;
  53. BEGIN
  54. RETURN entries
  55. END NoOfEntries;
  56. PROCEDURE OpenReceiver*(VAR R: Streams.Receiver; entry: Entry; VAR res: WORD);
  57. VAR fR: Files.Reader; sig: LONGINT; e: Entry; I: Inflate.Reader; S: SizeReader;
  58. BEGIN
  59. R := NIL; res := Streams.FormatError;
  60. Files.OpenReader(fR, F, entry.pos); fR.RawLInt(sig);
  61. IF sig # LocalFileHeadSig THEN RETURN END;
  62. NEW(e); ReadEntry(fR, e, TRUE);
  63. IF e.crc = entry.crc THEN
  64. IF e.method = 8 THEN (* Deflate *)
  65. NEW(I, fR); R := I.Receive; res := Streams.Ok
  66. ELSIF (e.method = 0) & (e.size = e.csize) THEN (* Stored *)
  67. NEW(S, fR, e.size); R := S.Receive; res := Streams.Ok
  68. END
  69. END
  70. END OpenReceiver;
  71. PROCEDURE Extract*(entry: Entry; dest: Streams.Writer; VAR res: WORD);
  72. VAR receiver : Streams.Receiver; R: Streams.Reader; buf: ARRAY 1024 OF CHAR; l: LONGINT; crc: CRC.CRC32Stream;
  73. BEGIN
  74. OpenReceiver(receiver, entry, res);
  75. NEW(R, receiver, 1024);
  76. IF res # Streams.Ok THEN RETURN END;
  77. NEW(crc);
  78. R.Bytes(buf, 0, 1024, l);
  79. WHILE l > 0 DO
  80. dest.Bytes(buf, 0, l); crc.Bytes(buf, 0, l);
  81. R.Bytes(buf, 0, 1024, l)
  82. END;
  83. crc.Update();
  84. IF R.res = Streams.EOF THEN
  85. IF entry.crc = crc.GetCRC() THEN
  86. res := Streams.Ok
  87. END
  88. ELSE
  89. res := R.res
  90. END
  91. END Extract;
  92. PROCEDURE &New*(F: Files.File; VAR res: WORD);
  93. VAR R: Files.Reader; r, e: Entry; pos, sig, l, j: LONGINT; i: INTEGER;
  94. BEGIN
  95. res := Streams.Ok; SELF.F := NIL; root := NIL; entries := 0;
  96. pos := F.Length()-20; sig := 0;
  97. WHILE (sig # EndOfCentralDirSig) & (pos > 0) DO
  98. DEC(pos);
  99. Files.OpenReader(R, F, pos);
  100. R.RawLInt(sig)
  101. END;
  102. IF sig # EndOfCentralDirSig THEN res := Streams.FormatError; RETURN END;
  103. R.RawInt(i); R.RawInt(i);
  104. R.RawInt(i); entries := i;
  105. R.RawInt(i); R.RawLInt(l);
  106. R.RawLInt(pos);
  107. IF R.res # Streams.Ok THEN res := R.res END;
  108. IF (pos < 0) OR (pos >= F.Length()) THEN res := Streams.FormatError; RETURN END;
  109. Files.OpenReader(R, F, pos);
  110. NEW(r); r.next := NIL; e := r;
  111. j := 0;
  112. WHILE j < entries DO
  113. NEW(e.next); e := e.next; e.next := NIL;
  114. R.RawLInt(sig);
  115. IF sig = CentralFileHeadSig THEN
  116. ReadEntry(R, e, FALSE)
  117. ELSE
  118. res := Streams.FormatError; RETURN
  119. END;
  120. INC(j)
  121. END;
  122. R.RawLInt(sig);
  123. IF sig # EndOfCentralDirSig THEN res := Streams.FormatError; RETURN END;
  124. IF res = Streams.Ok THEN
  125. SELF.F := F; root := r.next
  126. ELSE
  127. SELF.F := NIL; root := NIL; entries := 0
  128. END
  129. END New;
  130. END ZipFile;
  131. PROCEDURE DosToOberonTime(t: LONGINT): LONGINT;
  132. BEGIN
  133. RETURN t DIV 800H MOD 20H * 1000H + t DIV 20H MOD 40H * 40H + t MOD 20H * 2
  134. END DosToOberonTime;
  135. PROCEDURE DosToOberonDate(d: LONGINT): LONGINT;
  136. BEGIN
  137. RETURN (d DIV 200H MOD 80H + 1980 - 1900) * 200H + d MOD 200H
  138. END DosToOberonDate;
  139. PROCEDURE ReadEntry(R: Streams.Reader; entry: Entry; local: BOOLEAN);
  140. VAR l, nl, xl, t, d: LONGINT; i: INTEGER;
  141. BEGIN
  142. IF local THEN
  143. R.RawInt(i);
  144. R.RawInt(i); R.RawInt(i); entry.method := i;
  145. R.RawInt(i); t := DosToOberonTime(i);
  146. R.RawInt(i); d := DosToOberonDate(i);
  147. entry.td := Dates.OberonToDateTime(d, t);
  148. R.RawLInt(entry.crc);
  149. R.RawLInt(entry.csize);
  150. R.RawLInt(entry.size);
  151. R.RawInt(i); nl := i;
  152. R.RawInt(i); xl := i;
  153. NEW(entry.name, nl+1);
  154. l := 0;
  155. WHILE l < nl DO
  156. R.Char(entry.name[l]); INC(l)
  157. END;
  158. entry.name[l] := 0X;
  159. R.SkipBytes(xl)
  160. ELSE
  161. R.RawInt(i); R.RawInt(i);
  162. R.RawInt(i); R.RawInt(i); entry.method := i;
  163. R.RawInt(i); t := DosToOberonTime(i);
  164. R.RawInt(i); d := DosToOberonDate(i);
  165. entry.td := Dates.OberonToDateTime(d, t);
  166. R.RawLInt(entry.crc);
  167. R.RawLInt(entry.csize);
  168. R.RawLInt(entry.size);
  169. R.RawInt(i); nl := i;
  170. R.RawInt(i); xl := i;
  171. R.RawInt(i); xl := xl + i;
  172. R.RawInt(i); R.RawInt(i);
  173. R.RawLInt(l); R.RawLInt(entry.pos);
  174. NEW(entry.name, nl+1);
  175. l := 0;
  176. WHILE l < nl DO
  177. R.Char(entry.name[l]); INC(l)
  178. END;
  179. entry.name[l] := 0X;
  180. R.SkipBytes(xl)
  181. END
  182. END ReadEntry;
  183. PROCEDURE StripPrefix(CONST long: ARRAY OF CHAR; VAR short: ARRAY OF CHAR);
  184. VAR i, j: LONGINT; ch: CHAR;
  185. BEGIN
  186. i := 0; j := 0; ch := long[0];
  187. WHILE ch # 0X DO
  188. IF (ch = "/") OR (ch = "\") THEN
  189. j := 0
  190. ELSE
  191. short[j] := ch; INC(j)
  192. END;
  193. INC(i); ch := long[i]
  194. END;
  195. short[j] := 0X
  196. END StripPrefix;
  197. PROCEDURE ExtractEntry(w: Streams.Writer; zip: ZipFile; entry: Entry; name: ARRAY OF CHAR; backup, path: BOOLEAN);
  198. VAR F: Files.File; W: Files.Writer; res: WORD; bak: Files.FileName;
  199. BEGIN
  200. IF ~path THEN StripPrefix(name, name) END;
  201. w.String(name);
  202. F := Files.New(name);
  203. IF F = NIL THEN
  204. w.String(" failed"); w.Ln(); RETURN
  205. END;
  206. Files.OpenWriter(W, F, 0);
  207. zip.Extract(entry, W, res);
  208. IF res = Streams.Ok THEN
  209. IF backup THEN
  210. COPY(name, bak); Strings.Append(bak, ".Bak");
  211. Files.Rename(name, bak, res);
  212. (* ASSERT(res = 0) what if it did not exist before ? *)
  213. IF (res # 0) & (res # 2) THEN w.String("Backup failed on "); w.String(name); w.Ln END
  214. END;
  215. W.Update(); Files.Register(F)
  216. ELSE
  217. w.String(" failed")
  218. END;
  219. w.Ln()
  220. END ExtractEntry;
  221. (* Extract [ \o ] [ \d ] [ \p prefix ] zip { entry } ~ *)
  222. PROCEDURE Extract*(context : Commands.Context);
  223. VAR
  224. F: Files.File; zip: ZipFile; name, fs: Files.FileName; res: WORD;
  225. e: Entry; opt: ARRAY 32 OF CHAR; ch: CHAR; backup, path, prefix: BOOLEAN;
  226. BEGIN
  227. context.arg.SkipWhitespace();
  228. backup := TRUE; prefix := FALSE; path := FALSE;
  229. WHILE context.arg.Peek() = "\" DO
  230. context.arg.Char(ch); context.arg.SkipWhitespace(); context.arg.String(opt);
  231. IF opt = "o" THEN
  232. backup := FALSE
  233. ELSIF opt = "d" THEN
  234. path := TRUE
  235. ELSIF opt = "p" THEN
  236. prefix := TRUE;
  237. context.arg.SkipWhitespace(); context.arg.String(fs)
  238. ELSE
  239. context.error.String("unknown option "); context.error.String(opt); context.error.Ln();
  240. RETURN
  241. END;
  242. context.arg.SkipWhitespace()
  243. END;
  244. context.arg.String(name); context.arg.SkipWhitespace();
  245. IF name = "" THEN RETURN END;
  246. F := Files.Old(name);
  247. IF F = NIL THEN RETURN END;
  248. NEW(zip, F, res);
  249. IF res = Streams.Ok THEN
  250. context.arg.String(name);
  251. WHILE name # "" DO
  252. e := zip.FindEntry(name);
  253. IF e # NIL THEN
  254. IF prefix THEN
  255. COPY(fs, name); Strings.Append(name, e.name^)
  256. END;
  257. ExtractEntry(context.out, zip, e, name, backup, path)
  258. ELSE
  259. context.error.String(name); context.error.String(" not found"); context.error.Ln()
  260. END;
  261. context.arg.SkipWhitespace(); context.arg.String(name)
  262. END;
  263. ELSE
  264. context.error.String(name); context.error.String(" is not a valid zip file"); context.error.Ln()
  265. END;
  266. END Extract;
  267. (* ExtractAll [ \o ] [ \d ] [ \p prefix ] zip ~ *)
  268. PROCEDURE ExtractAll*(context : Commands.Context);
  269. VAR
  270. F: Files.File; zip: ZipFile; name, fs: Files.FileName; res: WORD;
  271. e: Entry; opt: ARRAY 32 OF CHAR; ch: CHAR; backup, path, prefix: BOOLEAN;
  272. BEGIN
  273. context.arg.SkipWhitespace();
  274. backup := TRUE; prefix := FALSE; path := FALSE;
  275. WHILE context.arg.Peek() = "\" DO
  276. context.arg.Char(ch); context.arg.SkipWhitespace(); context.arg.String(opt);
  277. IF opt = "o" THEN
  278. backup := FALSE
  279. ELSIF opt = "d" THEN
  280. path := TRUE
  281. ELSIF opt = "p" THEN
  282. prefix := TRUE;
  283. context.arg.SkipWhitespace(); context.arg.String(fs)
  284. ELSE
  285. context.error.String("unknown option "); context.error.String(opt); context.error.Ln();
  286. RETURN
  287. END;
  288. context.arg.SkipWhitespace()
  289. END;
  290. context.arg.String(name);
  291. WHILE name # "" DO
  292. F := Files.Old(name);
  293. IF F # NIL THEN
  294. NEW(zip, F, res);
  295. IF res = Streams.Ok THEN
  296. e := zip.GetFirst();
  297. WHILE e # NIL DO
  298. IF prefix THEN
  299. COPY(fs, name); Strings.Append(name, e.name^)
  300. ELSE
  301. COPY(e.name^, name)
  302. END;
  303. ExtractEntry(context.out, zip, e, name, backup, path);
  304. e := zip.GetNext(e)
  305. END;
  306. ELSE
  307. context.error.String(name); context.error.String(" is not a valid zip file"); context.error.Ln()
  308. END
  309. ELSE
  310. context.error.String(name); context.error.String(" not found"); context.error.Ln()
  311. END;
  312. context.arg.SkipWhitespace(); context.arg.String(name)
  313. END;
  314. END ExtractAll;
  315. (* Directory [ \d ] zip ~ *)
  316. PROCEDURE Directory*(context : Commands.Context);
  317. VAR
  318. F: Files.File; zip: ZipFile; name: Files.FileName; res: WORD; i: LONGINT;
  319. e: Entry; opt: ARRAY 32 OF CHAR; ch: CHAR; detail: BOOLEAN;
  320. BEGIN
  321. context.arg.SkipWhitespace();
  322. detail := FALSE;
  323. WHILE context.arg.Peek() = "\" DO
  324. context.arg.Char(ch); context.arg.SkipWhitespace(); context.arg.String(opt);
  325. IF opt = "d" THEN
  326. detail := TRUE
  327. ELSE
  328. context.error.String("unknown option "); context.error.String(opt); context.error.Ln();
  329. RETURN
  330. END;
  331. context.arg.SkipWhitespace()
  332. END;
  333. context.arg.String(name);
  334. IF name = "" THEN RETURN END;
  335. F := Files.Old(name);
  336. IF F = NIL THEN RETURN END;
  337. NEW(zip, F, res);
  338. IF res = Streams.Ok THEN
  339. context.out.String("Directory of "); context.out.String(name);
  340. context.out.Ln(); context.out.Ln();
  341. e := zip.GetFirst(); i := 0;
  342. WHILE e # NIL DO
  343. INC(i);
  344. context.out.String(e.name^);
  345. IF detail THEN
  346. context.out.Char(09X); Strings.DateToStr(e.td, opt); context.out.String(opt);
  347. context.out.String(" "); Strings.TimeToStr(e.td, opt); context.out.String(opt);
  348. context.out.Char(09X); context.out.Int(e.size, 0);
  349. context.out.Char(09X); context.out.Int(e.csize, 0);
  350. context.out.Ln()
  351. ELSE
  352. IF (i MOD 2) = 0 THEN
  353. context.out.Ln()
  354. ELSE
  355. context.out.Char(09X)
  356. END
  357. END;
  358. e := zip.GetNext(e)
  359. END;
  360. context.out.Ln()
  361. ELSE
  362. context.error.String(name); context.error.String(" is not a valid zip file"); context.error.Ln()
  363. END;
  364. END Directory;
  365. END Unzip.
  366. System.Free Unzip Inflate ~
  367. Inflate.Mod Unzip.Mod