summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/l3build/l3build-zip.lua
blob: bf03439f0d2a2ea452a802c10340a73222bee0cc (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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
--[[

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 function extra_timestamp(mod, access, creation)
  local flags = 0
  local local_extra, central_extra = '', ''
  if mod then
    flags = flags | 0x1
    local_extra = pack('<I4', mod)
    central_extra = local_extra
  end
  if access then
    flags = flags | 0x2
    local_extra = local_extra .. pack('<I4', access)
  end
  if creation then
    flags = flags | 0x4
    local_extra = local_extra .. pack('<I4', creation)
  end
  if flags == 0 then return '', '' end
  return pack('<c2I2B', 'UT', #central_extra + 1, flags) .. central_extra, pack('<c2I2B', 'UT', #local_extra + 1, flags) .. local_extra
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 timestamp = os.time()
    local date, time = encode_time(timestamp)
    local central_extra, local_extra = extra_timestamp(timestamp, nil, 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,
        #local_extra),
      innername,
      local_extra,
      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,
        #central_extra,
        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 .. central_extra
  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