summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/l3build/l3build-zip.lua
blob: 06f37cd8b3c79ccb16f1251450b27da4df80fa6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
--[[

File l3build-zip.lua Copyright (C) 2021 The LaTeX Project

It may be distributed and/or modified under the conditions of the
LaTeX Project Public License (LPPL), either version 1.3c of this
license or (at your option) any later version.  The latest version
of this license is in the file

   http://www.latex-project.org/lppl.txt

This file is part of the "l3build bundle" (The Work in LPPL)
and all files in that bundle must be distributed together.

-----------------------------------------------------------------------

The development version of the bundle can be found at

   https://github.com/latex3/l3build

for those people who are interested.

--]]

local concat = table.concat
local open = io.open
local osdate = os.date
local pack = string.pack
local setmetatable = setmetatable
local iotype = io.type

local compress = zlib.compress
local crc32 = zlib.crc32

local function encode_time(unix)
  local t = osdate('*t', unix)
  local date = t.day | (t.month << 5) | ((t.year-1980) << 9)
  local time = (t.sec//2) | (t.min << 5) | (t.hour << 11)
  return date, time
end

local meta = {__index = {
  add = function(z, filename, innername, binary, executable)
    innername = innername or filename

    local offset = z.f:seek'cur'

    local content do
      local f = iotype(filename) and filename or assert(open(filename, binary and 'rb' or 'r'))
      content = f:read'*a'
      f:close()
    end
    local crc32 = crc32(crc32(), content)
    local compressed = compress(content, nil, nil, -15)
    if #compressed >= #content then
      compressed = nil
    end
    local date, time = encode_time(nil)
    z.f:write(pack("<c4I2I2I2I2I2I4I4I4I2I2",
        'PK\3\4',
        compressed and 20 or 10, -- ZIP 2.0 to allow deflate
        0, -- We never set flags
        compressed and 8 or 0, -- Always use deflate
        time,
        date,
        crc32,
        compressed and #compressed or #content,
        #content,
        #innername,
        0),
      innername,
      compressed or content)
    local central = pack("<c4I2I2I2I2I2I2I4I4I4I2I2I2I2I2I4I4",
        'PK\1\2',
        (3 << 8) | 63, -- Use UNIX attributes, written against ZIP 6.3
        compressed and 20 or 10, -- ZIP 2.0 to allow deflate
        0, -- We never set flags
        compressed and 8 or 0, -- Always use deflate
        time,
        date,
        crc32,
        compressed and #compressed or #content,
        #content,
        #innername,
        0, -- no extra data
        0, -- no comment
        0, -- Disc 0
        binary and 0 or 1,
        (executable and 0x81ED--[[0100755]] or 0x81A4--[[0100644]]) << 16,
        offset)
    z.central[#z.central+1] = central .. innername
  end,
  close = function(z, comment)
    comment = comment or ''

    local offset = z.f:seek'cur'
    local central = concat(z.central)
    z.f:write(central, pack("<c4I2I2I2I2I4I4I2",
        'PK\5\6',
        0, -- This is disc 0
        0, -- central dictionary started on disc 0
        #z.central, -- Central disctionary entries on this disc
        #z.central, -- Central disctionary entries on all discs
        #central,
        offset,
        #comment), comment)
    return z.f:close()
  end,
}}

return function(filename)
  local f, msg = open(filename, 'wb')
  if not f then return f, msg end
  return setmetatable({
    f = f,
    offset = 1,
    central = {},
  }, meta)
end