zip.ml 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. (***********************************************************************)
  2. (* *)
  3. (* The CamlZip library *)
  4. (* *)
  5. (* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
  6. (* adapted to Extc lib by Caue Waneck *)
  7. (* *)
  8. (* Copyright 2001 Institut National de Recherche en Informatique et *)
  9. (* en Automatique. All rights reserved. This file is distributed *)
  10. (* under the terms of the GNU Lesser General Public License, with *)
  11. (* the special exception on linking described in file LICENSE. *)
  12. (* *)
  13. (***********************************************************************)
  14. (* $Id: zip.ml,v 1.5 2008/12/07 09:23:08 xleroy Exp $ *)
  15. (* Module [Zip]: reading and writing ZIP archives *)
  16. exception Error of string * string * string
  17. let read1 = input_byte
  18. let read2 ic =
  19. let lb = read1 ic in let hb = read1 ic in lb lor (hb lsl 8)
  20. let read4 ic =
  21. let lw = read2 ic in let hw = read2 ic in
  22. Int32.logor (Int32.of_int lw) (Int32.shift_left (Int32.of_int hw) 16)
  23. let read4_int ic =
  24. let lw = read2 ic in let hw = read2 ic in
  25. if hw > max_int lsr 16 then raise (Error("", "", "32-bit data too large"));
  26. lw lor (hw lsl 16)
  27. let readstring ic n =
  28. let s = Bytes.create n in
  29. really_input ic s 0 n; Bytes.to_string s
  30. let write1 = output_byte
  31. let write2 oc n =
  32. write1 oc n; write1 oc (n lsr 8)
  33. let write4 oc n =
  34. write2 oc (Int32.to_int n);
  35. write2 oc (Int32.to_int (Int32.shift_right_logical n 16))
  36. let write4_int oc n =
  37. write2 oc n;
  38. write2 oc (n lsr 16)
  39. let writestring oc s =
  40. output oc (Bytes.of_string s) 0 (String.length s)
  41. let writebytes oc s =
  42. output oc s 0 (Bytes.length s)
  43. type compression_method = Stored | Deflated
  44. type entry =
  45. { filename: string;
  46. extra: string;
  47. comment: string;
  48. methd: compression_method;
  49. mtime: float;
  50. crc: int32;
  51. uncompressed_size: int;
  52. compressed_size: int;
  53. is_directory: bool;
  54. file_offset: int64 }
  55. type in_file =
  56. { if_filename: string;
  57. if_channel: Stdlib.in_channel;
  58. if_entries: entry list;
  59. if_directory: (string, entry) Hashtbl.t;
  60. if_comment: string }
  61. let entries ifile = ifile.if_entries
  62. let comment ifile = ifile.if_comment
  63. type out_file =
  64. { of_filename: string;
  65. of_channel: Stdlib.out_channel;
  66. mutable of_entries: entry list;
  67. of_comment: string }
  68. (* Return the position of the last occurrence of s1 in s2, or -1 if not
  69. found. *)
  70. let strrstr pattern buf ofs len =
  71. let rec search i j =
  72. if i < ofs then -1
  73. else if j >= String.length pattern then i
  74. else if pattern.[j] = buf.[i + j] then search i (j+1)
  75. else search (i-1) 0
  76. in search (ofs + len - String.length pattern) 0
  77. (* Determine if a file name is a directory (ends with /) *)
  78. let filename_is_directory name =
  79. String.length name > 0 && name.[String.length name - 1] = '/'
  80. (* Convert between Unix dates and DOS dates *)
  81. let unixtime_of_dostime time date =
  82. fst(Unix.mktime
  83. { Unix.tm_sec = (time lsl 1) land 0x3e;
  84. Unix.tm_min = (time lsr 5) land 0x3f;
  85. Unix.tm_hour = (time lsr 11) land 0x1f;
  86. Unix.tm_mday = date land 0x1f;
  87. Unix.tm_mon = ((date lsr 5) land 0xf) - 1;
  88. Unix.tm_year = ((date lsr 9) land 0x7f) + 80;
  89. Unix.tm_wday = 0;
  90. Unix.tm_yday = 0;
  91. Unix.tm_isdst = false })
  92. let dostime_of_unixtime t =
  93. let tm = Unix.localtime t in
  94. (tm.Unix.tm_sec lsr 1
  95. + (tm.Unix.tm_min lsl 5)
  96. + (tm.Unix.tm_hour lsl 11),
  97. tm.Unix.tm_mday
  98. + (tm.Unix.tm_mon + 1) lsl 5
  99. + (tm.Unix.tm_year - 80) lsl 9)
  100. (* Read end of central directory record *)
  101. let read_ecd filename ic =
  102. let buf = Bytes.create 256 in
  103. let filelen = in_channel_length ic in
  104. let rec find_ecd pos len =
  105. (* On input, bytes 0 ... len - 1 of buf reflect what is at pos in ic *)
  106. if pos <= 0 || filelen - pos >= 0x10000 then
  107. raise (Error(filename, "",
  108. "end of central directory not found, not a ZIP file"));
  109. let toread = min pos 128 in
  110. (* Make room for "toread" extra bytes, and read them *)
  111. Bytes.blit buf 0 buf toread (256 - toread);
  112. let newpos = pos - toread in
  113. seek_in ic newpos;
  114. really_input ic buf 0 toread;
  115. let newlen = min (toread + len) 256 in
  116. (* Search for magic number *)
  117. let ofs = strrstr "PK\005\006" (Bytes.to_string buf) 0 newlen in
  118. if ofs < 0 || newlen < 22 ||
  119. (let comment_len =
  120. Char.code (Bytes.get buf (ofs + 20)) lor (Char.code (Bytes.get buf (ofs + 21)) lsl 8) in
  121. newpos + ofs + 22 + comment_len <> filelen) then
  122. find_ecd newpos newlen
  123. else
  124. newpos + ofs in
  125. seek_in ic (find_ecd filelen 0);
  126. let magic = read4 ic in
  127. let disk_no = read2 ic in
  128. let cd_disk_no = read2 ic in
  129. let _disk_entries = read2 ic in
  130. let cd_entries = read2 ic in
  131. let cd_size = read4 ic in
  132. let cd_offset = read4 ic in
  133. let comment_len = read2 ic in
  134. let comment = readstring ic comment_len in
  135. assert (magic = Int32.of_int 0x06054b50);
  136. if disk_no <> 0 || cd_disk_no <> 0 then
  137. raise (Error(filename, "", "multi-disk ZIP files not supported"));
  138. (cd_entries, cd_size, cd_offset, comment)
  139. (* Read central directory *)
  140. let read_cd filename ic cd_entries cd_offset cd_bound =
  141. let cd_bound = Int64.of_int32 cd_bound in
  142. try
  143. LargeFile.seek_in ic (Int64.of_int32 cd_offset);
  144. let e = ref [] in
  145. let entrycnt = ref 0 in
  146. while (LargeFile.pos_in ic < cd_bound) do
  147. incr entrycnt;
  148. let magic = read4 ic in
  149. let _version_made_by = read2 ic in
  150. let _version_needed = read2 ic in
  151. let flags = read2 ic in
  152. let methd = read2 ic in
  153. let lastmod_time = read2 ic in
  154. let lastmod_date = read2 ic in
  155. let crc = read4 ic in
  156. let compr_size = read4_int ic in
  157. let uncompr_size = read4_int ic in
  158. let name_len = read2 ic in
  159. let extra_len = read2 ic in
  160. let comment_len = read2 ic in
  161. let _disk_number = read2 ic in
  162. let _internal_attr = read2 ic in
  163. let _external_attr = read4 ic in
  164. let header_offset = Int64.of_int32(read4 ic) in
  165. let name = readstring ic name_len in
  166. let extra = readstring ic extra_len in
  167. let comment = readstring ic comment_len in
  168. if magic <> Int32.of_int 0x02014b50 then
  169. raise (Error(filename, name,
  170. "wrong file header in central directory"));
  171. if flags land 1 <> 0 then
  172. raise (Error(filename, name, "encrypted entries not supported"));
  173. e := { filename = name;
  174. extra = extra;
  175. comment = comment;
  176. methd = (match methd with
  177. 0 -> Stored
  178. | 8 -> Deflated
  179. | _ -> raise (Error(filename, name,
  180. "unknown compression method")));
  181. mtime = unixtime_of_dostime lastmod_time lastmod_date;
  182. crc = crc;
  183. uncompressed_size = uncompr_size;
  184. compressed_size = compr_size;
  185. is_directory = filename_is_directory name;
  186. file_offset = header_offset
  187. } :: !e
  188. done;
  189. assert((cd_bound = (LargeFile.pos_in ic)) &&
  190. (cd_entries = 65535 || !entrycnt = cd_entries));
  191. List.rev !e
  192. with End_of_file ->
  193. raise (Error(filename, "", "end-of-file while reading central directory"))
  194. (* Open a ZIP file for reading *)
  195. let open_in filename =
  196. let ic = Stdlib.open_in_bin filename in
  197. let (cd_entries, cd_size, cd_offset, cd_comment) = read_ecd filename ic in
  198. let entries =
  199. read_cd filename ic cd_entries cd_offset (Int32.add cd_offset cd_size) in
  200. let dir = Hashtbl.create (cd_entries / 3) in
  201. List.iter (fun e -> Hashtbl.add dir e.filename e) entries;
  202. { if_filename = filename;
  203. if_channel = ic;
  204. if_entries = entries;
  205. if_directory = dir;
  206. if_comment = cd_comment }
  207. (* Close a ZIP file opened for reading *)
  208. let close_in ifile =
  209. Stdlib.close_in ifile.if_channel
  210. (* Return the info associated with an entry *)
  211. let find_entry ifile name =
  212. Hashtbl.find ifile.if_directory name
  213. (* Position on an entry *)
  214. let goto_entry ifile e =
  215. try
  216. let ic = ifile.if_channel in
  217. LargeFile.seek_in ic e.file_offset;
  218. let magic = read4 ic in
  219. let _version_needed = read2 ic in
  220. let _flags = read2 ic in
  221. let _methd = read2 ic in
  222. let _lastmod_time = read2 ic in
  223. let _lastmod_date = read2 ic in
  224. let _crc = read4 ic in
  225. let _compr_size = read4_int ic in
  226. let _uncompr_size = read4_int ic in
  227. let filename_len = read2 ic in
  228. let extra_len = read2 ic in
  229. if magic <> Int32.of_int 0x04034b50 then
  230. raise (Error(ifile.if_filename, e.filename, "wrong local file header"));
  231. (* Could validate information read against directory entry, but
  232. what the heck *)
  233. LargeFile.seek_in ifile.if_channel
  234. (Int64.add e.file_offset (Int64.of_int (30 + filename_len + extra_len)))
  235. with End_of_file ->
  236. raise (Error(ifile.if_filename, e.filename, "truncated local file header"))
  237. (* Read the contents of an entry as a string *)
  238. let read_entry ifile e =
  239. try
  240. goto_entry ifile e;
  241. let res = Bytes.create e.uncompressed_size in
  242. match e.methd with
  243. Stored ->
  244. if e.compressed_size <> e.uncompressed_size then
  245. raise (Error(ifile.if_filename, e.filename,
  246. "wrong size for stored entry"));
  247. really_input ifile.if_channel res 0 e.uncompressed_size;
  248. Bytes.to_string res
  249. | Deflated ->
  250. let in_avail = ref e.compressed_size in
  251. let out_pos = ref 0 in
  252. begin try
  253. Zlib.uncompress ~header:false
  254. (fun buf ->
  255. let read = input ifile.if_channel buf 0
  256. (min !in_avail (Bytes.length buf)) in
  257. in_avail := !in_avail - read;
  258. read)
  259. (fun buf len ->
  260. if !out_pos + len > Bytes.length res then
  261. raise (Error(ifile.if_filename, e.filename,
  262. "wrong size for deflated entry (too much data)"));
  263. Bytes.blit buf 0 res !out_pos len;
  264. out_pos := !out_pos + len)
  265. with Failure(_) ->
  266. raise (Error(ifile.if_filename, e.filename, "decompression error"))
  267. end;
  268. if !out_pos <> Bytes.length res then
  269. raise (Error(ifile.if_filename, e.filename,
  270. "wrong size for deflated entry (not enough data)"));
  271. let crc = Zlib.update_crc Int32.zero res 0 (Bytes.length res) in
  272. if crc <> e.crc then
  273. raise (Error(ifile.if_filename, e.filename, "CRC mismatch"));
  274. Bytes.to_string res
  275. with End_of_file ->
  276. raise (Error(ifile.if_filename, e.filename, "truncated data"))
  277. (* Write the contents of an entry into an out channel *)
  278. let copy_entry_to_channel ifile e oc =
  279. try
  280. goto_entry ifile e;
  281. match e.methd with
  282. Stored ->
  283. if e.compressed_size <> e.uncompressed_size then
  284. raise (Error(ifile.if_filename, e.filename,
  285. "wrong size for stored entry"));
  286. let buf = Bytes.create 4096 in
  287. let rec copy n =
  288. if n > 0 then begin
  289. let r = input ifile.if_channel buf 0 (min n (Bytes.length buf)) in
  290. output oc buf 0 r;
  291. copy (n - r)
  292. end in
  293. copy e.uncompressed_size
  294. | Deflated ->
  295. let in_avail = ref e.compressed_size in
  296. let crc = ref Int32.zero in
  297. begin try
  298. Zlib.uncompress ~header:false
  299. (fun buf ->
  300. let read = input ifile.if_channel buf 0
  301. (min !in_avail (Bytes.length buf)) in
  302. in_avail := !in_avail - read;
  303. read)
  304. (fun buf len ->
  305. output oc buf 0 len;
  306. crc := Zlib.update_crc !crc buf 0 len)
  307. with Failure _ ->
  308. raise (Error(ifile.if_filename, e.filename, "decompression error"))
  309. end;
  310. if !crc <> e.crc then
  311. raise (Error(ifile.if_filename, e.filename, "CRC mismatch"))
  312. with End_of_file ->
  313. raise (Error(ifile.if_filename, e.filename, "truncated data"))
  314. (* Write the contents of an entry to a file *)
  315. let copy_entry_to_file ifile e outfilename =
  316. let oc = open_out_bin outfilename in
  317. try
  318. copy_entry_to_channel ifile e oc;
  319. close_out oc;
  320. begin try
  321. Unix.utimes outfilename e.mtime e.mtime
  322. with Unix.Unix_error(_, _, _) | Invalid_argument _ -> ()
  323. end
  324. with x ->
  325. close_out oc;
  326. Sys.remove outfilename;
  327. raise x
  328. (* Open a ZIP file for writing *)
  329. let open_out ?(comment = "") filename =
  330. if String.length comment >= 0x10000 then
  331. raise(Error(filename, "", "comment too long"));
  332. { of_filename = filename;
  333. of_channel = Stdlib.open_out_bin filename;
  334. of_entries = [];
  335. of_comment = comment }
  336. (* Close a ZIP file for writing. Add central directory. *)
  337. let write_directory_entry oc e =
  338. write4 oc (Int32.of_int 0x02014b50); (* signature *)
  339. let version = match e.methd with Stored -> 10 | Deflated -> 20 in
  340. write2 oc version; (* version made by *)
  341. write2 oc version; (* version needed to extract *)
  342. write2 oc 8; (* flags *)
  343. write2 oc (match e.methd with Stored -> 0 | Deflated -> 8); (* method *)
  344. let (time, date) = dostime_of_unixtime e.mtime in
  345. write2 oc time; (* last mod time *)
  346. write2 oc date; (* last mod date *)
  347. write4 oc e.crc; (* CRC32 *)
  348. write4_int oc e.compressed_size; (* compressed size *)
  349. write4_int oc e.uncompressed_size; (* uncompressed size *)
  350. write2 oc (String.length e.filename); (* filename length *)
  351. write2 oc (String.length e.extra); (* extra length *)
  352. write2 oc (String.length e.comment); (* comment length *)
  353. write2 oc 0; (* disk number start *)
  354. write2 oc 0; (* internal attributes *)
  355. write4_int oc 0; (* external attributes *)
  356. write4 oc (Int64.to_int32 e.file_offset); (* offset of local header *)
  357. writestring oc e.filename; (* filename *)
  358. writestring oc e.extra; (* extra info *)
  359. writestring oc e.comment (* file comment *)
  360. let close_out ofile =
  361. let oc = ofile.of_channel in
  362. let start_cd = pos_out oc in
  363. List.iter (write_directory_entry oc) (List.rev ofile.of_entries);
  364. let cd_size = pos_out oc - start_cd in
  365. let num_entries = List.length ofile.of_entries in
  366. if num_entries >= 0x10000 then
  367. raise(Error(ofile.of_filename, "", "too many entries"));
  368. write4 oc (Int32.of_int 0x06054b50); (* signature *)
  369. write2 oc 0; (* disk number *)
  370. write2 oc 0; (* number of disk with central dir *)
  371. write2 oc num_entries; (* # entries in this disk *)
  372. write2 oc num_entries; (* # entries in central dir *)
  373. write4_int oc cd_size; (* size of central dir *)
  374. write4_int oc start_cd; (* offset of central dir *)
  375. write2 oc (String.length ofile.of_comment); (* length of comment *)
  376. writestring oc ofile.of_comment; (* comment *)
  377. Stdlib.close_out oc
  378. (* Write a local file header and return the corresponding entry *)
  379. let add_entry_header ofile extra comment level mtime filename =
  380. if level < 0 || level > 9 then
  381. raise(Error(ofile.of_filename, filename, "wrong compression level"));
  382. if String.length filename >= 0x10000 then
  383. raise(Error(ofile.of_filename, filename, "filename too long"));
  384. if String.length extra >= 0x10000 then
  385. raise(Error(ofile.of_filename, filename, "extra data too long"));
  386. if String.length comment >= 0x10000 then
  387. raise(Error(ofile.of_filename, filename, "comment too long"));
  388. let oc = ofile.of_channel in
  389. let pos = LargeFile.pos_out oc in
  390. write4 oc (Int32.of_int 0x04034b50); (* signature *)
  391. let version = if level = 0 then 10 else 20 in
  392. write2 oc version; (* version needed to extract *)
  393. write2 oc 8; (* flags *)
  394. write2 oc (if level = 0 then 0 else 8); (* method *)
  395. let (time, date) = dostime_of_unixtime mtime in
  396. write2 oc time; (* last mod time *)
  397. write2 oc date; (* last mod date *)
  398. write4 oc Int32.zero; (* CRC32 - to be filled later *)
  399. write4_int oc 0; (* compressed size - later *)
  400. write4_int oc 0; (* uncompressed size - later *)
  401. write2 oc (String.length filename); (* filename length *)
  402. write2 oc (String.length extra); (* extra length *)
  403. writestring oc filename; (* filename *)
  404. writestring oc extra; (* extra info *)
  405. { filename = filename;
  406. extra = extra;
  407. comment = comment;
  408. methd = (if level = 0 then Stored else Deflated);
  409. mtime = mtime;
  410. crc = Int32.zero;
  411. uncompressed_size = 0;
  412. compressed_size = 0;
  413. is_directory = filename_is_directory filename;
  414. file_offset = pos }
  415. (* Write a data descriptor and update the entry *)
  416. let add_data_descriptor ofile crc compr_size uncompr_size entry =
  417. let oc = ofile.of_channel in
  418. write4 oc (Int32.of_int 0x08074b50); (* signature *)
  419. write4 oc crc; (* CRC *)
  420. write4_int oc compr_size; (* compressed size *)
  421. write4_int oc uncompr_size; (* uncompressed size *)
  422. { entry with crc = crc;
  423. uncompressed_size = uncompr_size;
  424. compressed_size = compr_size }
  425. (* Add an entry with the contents of a string *)
  426. let add_entry data ofile ?(extra = "") ?(comment = "")
  427. ?(level = 6) ?(mtime = Unix.time()) name =
  428. let data = Bytes.of_string data in
  429. let e = add_entry_header ofile extra comment level mtime name in
  430. let crc = Zlib.update_crc Int32.zero data 0 (Bytes.length data) in
  431. let compr_size =
  432. match level with
  433. 0 ->
  434. output ofile.of_channel data 0 (Bytes.length data);
  435. Bytes.length data
  436. | _ ->
  437. let in_pos = ref 0 in
  438. let out_pos = ref 0 in
  439. try
  440. Zlib.compress ~level ~header:false
  441. (fun buf ->
  442. let n = min (Bytes.length data - !in_pos)
  443. (Bytes.length buf) in
  444. Bytes.blit data !in_pos buf 0 n;
  445. in_pos := !in_pos + n;
  446. n)
  447. (fun buf n ->
  448. output ofile.of_channel buf 0 n;
  449. out_pos := !out_pos + n);
  450. !out_pos
  451. with Failure _ ->
  452. raise (Error(ofile.of_filename, name, "compression error")) in
  453. let e' = add_data_descriptor ofile crc compr_size (Bytes.length data) e in
  454. ofile.of_entries <- e' :: ofile.of_entries
  455. (* Add an entry with the contents of an in channel *)
  456. let copy_channel_to_entry ic ofile ?(extra = "") ?(comment = "")
  457. ?(level = 6) ?(mtime = Unix.time()) name =
  458. let e = add_entry_header ofile extra comment level mtime name in
  459. let crc = ref Int32.zero in
  460. let (compr_size, uncompr_size) =
  461. match level with
  462. 0 ->
  463. let buf = Bytes.create 4096 in
  464. let rec copy sz =
  465. let r = input ic buf 0 (Bytes.length buf) in
  466. if r = 0 then sz else begin
  467. crc := Zlib.update_crc !crc buf 0 r;
  468. output ofile.of_channel buf 0 r;
  469. copy (sz + r)
  470. end in
  471. let size = copy 0 in
  472. (size, size)
  473. | _ ->
  474. let in_pos = ref 0 in
  475. let out_pos = ref 0 in
  476. try
  477. Zlib.compress ~level ~header:false
  478. (fun buf ->
  479. let r = input ic buf 0 (Bytes.length buf) in
  480. crc := Zlib.update_crc !crc buf 0 r;
  481. in_pos := !in_pos + r;
  482. r)
  483. (fun buf n ->
  484. output ofile.of_channel buf 0 n;
  485. out_pos := !out_pos + n);
  486. (!out_pos, !in_pos)
  487. with Failure( _) ->
  488. raise (Error(ofile.of_filename, name, "compression error")) in
  489. let e' = add_data_descriptor ofile !crc compr_size uncompr_size e in
  490. ofile.of_entries <- e' :: ofile.of_entries
  491. (* Add an entry with the contents of a file *)
  492. let copy_file_to_entry infilename ofile ?(extra = "") ?(comment = "")
  493. ?(level = 6) ?mtime name =
  494. let ic = open_in_bin infilename in
  495. let mtime' =
  496. match mtime with
  497. Some t -> mtime
  498. | None ->
  499. try Some((Unix.stat infilename).Unix.st_mtime)
  500. with Unix.Unix_error(_,_,_) -> None in
  501. try
  502. copy_channel_to_entry ic ofile ~extra ~comment ~level ?mtime:mtime' name;
  503. Stdlib.close_in ic
  504. with x ->
  505. Stdlib.close_in ic; raise x
  506. (* Add an entry whose content will be produced by the caller *)
  507. let add_entry_generator ofile ?(extra = "") ?(comment = "")
  508. ?(level = 6) ?(mtime = Unix.time()) name =
  509. let e = add_entry_header ofile extra comment level mtime name in
  510. let crc = ref Int32.zero in
  511. let compr_size = ref 0 in
  512. let uncompr_size = ref 0 in
  513. let finished = ref false in
  514. let check () =
  515. if !finished then
  516. raise (Error(ofile.of_filename, name, "entry already finished"))
  517. in
  518. let finish () =
  519. finished := true;
  520. let e' = add_data_descriptor ofile !crc !compr_size !uncompr_size e in
  521. ofile.of_entries <- e' :: ofile.of_entries
  522. in
  523. match level with
  524. | 0 ->
  525. (fun buf pos len ->
  526. let buf = Bytes.of_string buf in
  527. check ();
  528. output ofile.of_channel buf pos len;
  529. compr_size := !compr_size + len;
  530. uncompr_size := !uncompr_size + len
  531. ),
  532. (fun () ->
  533. check ();
  534. finish ()
  535. )
  536. | _ ->
  537. let (send, flush) = Zlib.compress_direct ~level ~header:false
  538. (fun buf n ->
  539. output ofile.of_channel buf 0 n;
  540. compr_size := !compr_size + n)
  541. in
  542. (fun buf pos len ->
  543. let buf = Bytes.of_string buf in
  544. check ();
  545. try
  546. send buf pos len;
  547. uncompr_size := !uncompr_size + len;
  548. crc := Zlib.update_crc !crc buf pos len
  549. with Failure(_) ->
  550. raise (Error(ofile.of_filename, name, "compression error"))
  551. ),
  552. (fun () ->
  553. check ();
  554. try
  555. flush ();
  556. finish ()
  557. with Failure(_) ->
  558. raise (Error(ofile.of_filename, name, "compression error"))
  559. )