/lua/luarocks/tools/zip.lua

https://github.com/hiili/WindowsTorch · Lua · 248 lines · 187 code · 28 blank · 33 comment · 25 complexity · 34ccede543ec1466a2daddc5f3b1ab1a MD5 · raw file

  1. --- A Lua implementation of .zip file archiving (used for creating .rock files),
  2. -- using only lzlib.
  3. --module("luarocks.tools.zip", package.seeall)
  4. local zip = {}
  5. local zlib = require("zlib")
  6. local fs = require("luarocks.fs")
  7. local dir = require("luarocks.dir")
  8. local function number_to_bytestring(number, nbytes)
  9. local out = {}
  10. for _ = 1, nbytes do
  11. local byte = number % 256
  12. table.insert(out, string.char(byte))
  13. number = (number - byte) / 256
  14. end
  15. return table.concat(out)
  16. end
  17. --- Begin a new file to be stored inside the zipfile.
  18. -- @param self handle of the zipfile being written.
  19. -- @param filename filenome of the file to be added to the zipfile.
  20. -- @return true if succeeded, nil in case of failure.
  21. local function zipwriter_open_new_file_in_zip(self, filename)
  22. if self.in_open_file then
  23. self:close_file_in_zip()
  24. return nil
  25. end
  26. local lfh = {}
  27. self.local_file_header = lfh
  28. lfh.last_mod_file_time = 0 -- TODO
  29. lfh.last_mod_file_date = 0 -- TODO
  30. lfh.crc32 = 0 -- initial value
  31. lfh.compressed_size = 0 -- unknown yet
  32. lfh.uncompressed_size = 0 -- unknown yet
  33. lfh.file_name_length = #filename
  34. lfh.extra_field_length = 0
  35. lfh.file_name = filename:gsub("\\", "/")
  36. lfh.external_attr = 0 -- TODO properly store permissions
  37. self.in_open_file = true
  38. self.data = {}
  39. return true
  40. end
  41. --- Write data to the file currently being stored in the zipfile.
  42. -- @param self handle of the zipfile being written.
  43. -- @param buf string containing data to be written.
  44. -- @return true if succeeded, nil in case of failure.
  45. local function zipwriter_write_file_in_zip(self, buf)
  46. if not self.in_open_file then
  47. return nil
  48. end
  49. local lfh = self.local_file_header
  50. local cbuf = zlib.compress(buf):sub(3, -5)
  51. lfh.crc32 = zlib.crc32(lfh.crc32, buf)
  52. lfh.compressed_size = lfh.compressed_size + #cbuf
  53. lfh.uncompressed_size = lfh.uncompressed_size + #buf
  54. table.insert(self.data, cbuf)
  55. return true
  56. end
  57. --- Complete the writing of a file stored in the zipfile.
  58. -- @param self handle of the zipfile being written.
  59. -- @return true if succeeded, nil in case of failure.
  60. local function zipwriter_close_file_in_zip(self)
  61. local zh = self.ziphandle
  62. if not self.in_open_file then
  63. return nil
  64. end
  65. -- Local file header
  66. local lfh = self.local_file_header
  67. lfh.offset = zh:seek()
  68. zh:write(number_to_bytestring(0x04034b50, 4)) -- signature
  69. zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
  70. zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
  71. zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
  72. zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
  73. zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
  74. zh:write(number_to_bytestring(lfh.crc32, 4))
  75. zh:write(number_to_bytestring(lfh.compressed_size, 4))
  76. zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
  77. zh:write(number_to_bytestring(lfh.file_name_length, 2))
  78. zh:write(number_to_bytestring(lfh.extra_field_length, 2))
  79. zh:write(lfh.file_name)
  80. -- File data
  81. for _, cbuf in ipairs(self.data) do
  82. zh:write(cbuf)
  83. end
  84. -- Data descriptor
  85. zh:write(number_to_bytestring(lfh.crc32, 4))
  86. zh:write(number_to_bytestring(lfh.compressed_size, 4))
  87. zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
  88. table.insert(self.files, lfh)
  89. self.in_open_file = false
  90. return true
  91. end
  92. -- @return boolean or (boolean, string): true on success,
  93. -- false and an error message on failure.
  94. local function zipwriter_add(self, file)
  95. local fin
  96. local ok, err = self:open_new_file_in_zip(file)
  97. if not ok then
  98. err = "error in opening "..file.." in zipfile"
  99. else
  100. fin = io.open(fs.absolute_name(file), "rb")
  101. if not fin then
  102. ok = false
  103. err = "error opening "..file.." for reading"
  104. end
  105. end
  106. if ok then
  107. local buf = fin:read("*a")
  108. if not buf then
  109. err = "error reading "..file
  110. ok = false
  111. else
  112. ok = self:write_file_in_zip(buf)
  113. if not ok then
  114. err = "error in writing "..file.." in the zipfile"
  115. end
  116. end
  117. end
  118. if fin then
  119. fin:close()
  120. end
  121. if ok then
  122. ok = self:close_file_in_zip()
  123. if not ok then
  124. err = "error in writing "..file.." in the zipfile"
  125. end
  126. end
  127. return ok == true, err
  128. end
  129. --- Complete the writing of the zipfile.
  130. -- @param self handle of the zipfile being written.
  131. -- @return true if succeeded, nil in case of failure.
  132. local function zipwriter_close(self)
  133. local zh = self.ziphandle
  134. local central_directory_offset = zh:seek()
  135. local size_of_central_directory = 0
  136. -- Central directory structure
  137. for _, lfh in ipairs(self.files) do
  138. zh:write(number_to_bytestring(0x02014b50, 4)) -- signature
  139. zh:write(number_to_bytestring(3, 2)) -- version made by: UNIX
  140. zh:write(number_to_bytestring(20, 2)) -- version needed to extract: 2.0
  141. zh:write(number_to_bytestring(0, 2)) -- general purpose bit flag
  142. zh:write(number_to_bytestring(8, 2)) -- compression method: deflate
  143. zh:write(number_to_bytestring(lfh.last_mod_file_time, 2))
  144. zh:write(number_to_bytestring(lfh.last_mod_file_date, 2))
  145. zh:write(number_to_bytestring(lfh.crc32, 4))
  146. zh:write(number_to_bytestring(lfh.compressed_size, 4))
  147. zh:write(number_to_bytestring(lfh.uncompressed_size, 4))
  148. zh:write(number_to_bytestring(lfh.file_name_length, 2))
  149. zh:write(number_to_bytestring(lfh.extra_field_length, 2))
  150. zh:write(number_to_bytestring(0, 2)) -- file comment length
  151. zh:write(number_to_bytestring(0, 2)) -- disk number start
  152. zh:write(number_to_bytestring(0, 2)) -- internal file attributes
  153. zh:write(number_to_bytestring(lfh.external_attr, 4)) -- external file attributes
  154. zh:write(number_to_bytestring(lfh.offset, 4)) -- relative offset of local header
  155. zh:write(lfh.file_name)
  156. size_of_central_directory = size_of_central_directory + 46 + lfh.file_name_length
  157. end
  158. -- End of central directory record
  159. zh:write(number_to_bytestring(0x06054b50, 4)) -- signature
  160. zh:write(number_to_bytestring(0, 2)) -- number of this disk
  161. zh:write(number_to_bytestring(0, 2)) -- number of disk with start of central directory
  162. zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir on this disk
  163. zh:write(number_to_bytestring(#self.files, 2)) -- total number of entries in the central dir
  164. zh:write(number_to_bytestring(size_of_central_directory, 4))
  165. zh:write(number_to_bytestring(central_directory_offset, 4))
  166. zh:write(number_to_bytestring(0, 2)) -- zip file comment length
  167. zh:close()
  168. return true
  169. end
  170. --- Return a zip handle open for writing.
  171. -- @param name filename of the zipfile to be created.
  172. -- @return a zip handle, or nil in case of error.
  173. function zip.new_zipwriter(name)
  174. local zw = {}
  175. zw.ziphandle = io.open(fs.absolute_name(name), "wb")
  176. if not zw.ziphandle then
  177. return nil
  178. end
  179. zw.files = {}
  180. zw.in_open_file = false
  181. zw.add = zipwriter_add
  182. zw.close = zipwriter_close
  183. zw.open_new_file_in_zip = zipwriter_open_new_file_in_zip
  184. zw.write_file_in_zip = zipwriter_write_file_in_zip
  185. zw.close_file_in_zip = zipwriter_close_file_in_zip
  186. return zw
  187. end
  188. --- Compress files in a .zip archive.
  189. -- @param zipfile string: pathname of .zip archive to be created.
  190. -- @param ... Filenames to be stored in the archive are given as
  191. -- additional arguments.
  192. -- @return boolean or (boolean, string): true on success,
  193. -- false and an error message on failure.
  194. function zip.zip(zipfile, ...)
  195. local zw = zip.new_zipwriter(zipfile)
  196. if not zw then
  197. return nil, "error opening "..zipfile
  198. end
  199. local ok, err
  200. for _, file in pairs({...}) do
  201. if fs.is_dir(file) then
  202. for _, entry in pairs(fs.find(file)) do
  203. local fullname = dir.path(file, entry)
  204. if fs.is_file(fullname) then
  205. ok, err = zw:add(fullname)
  206. if not ok then break end
  207. end
  208. end
  209. else
  210. ok, err = zw:add(file)
  211. if not ok then break end
  212. end
  213. end
  214. ok = zw:close()
  215. if not ok then
  216. return false, "error closing "..zipfile
  217. end
  218. return ok, err
  219. end
  220. return zip