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
|
module write.zip;
/+ ↓ writing a zip, stored only.
.
An epub is a zip with rules: the first entry must be "mimetype", stored,
with no extra field, so that a reading system can identify the file from
its first bytes without inflating anything. Everything else may be
deflated and does not have to be.
.
So: stored only. Deflate would need zlib and would buy compression on a
sample writer's output, which is not what is being demonstrated, and it
would put a second thing in the way of the two writers agreeing byte for
byte. What is written here is a valid zip that epubcheck accepts.
.
No timestamps. Every entry is dated 1980-01-01, the epoch of the format,
which is what makes the same document produce the same bytes twice. Spine
does the same.
+/
import std.array : appender;
import std.digest.crc : crc32Of;
import std.string : representation;
struct ZipEntryOut {
string name;
ubyte[] data;
}
/+ ↓ the archive, entries in the order given +/
ubyte[] zipWrite(ZipEntryOut[] entries) {
auto out_ = appender!(ubyte[]);
uint[] offsets;
uint[] crcs;
foreach (e; entries) {
offsets ~= cast(uint) out_.data.length;
uint crc = crc32(e.data);
crcs ~= crc;
out_ ~= localHeader(e, crc);
out_ ~= e.data;
}
uint cd_offset = cast(uint) out_.data.length;
foreach (i, e; entries) {
out_ ~= centralEntry(e, crcs[i], offsets[i]);
}
uint cd_size = cast(uint) out_.data.length - cd_offset;
out_ ~= endRecord(cast(ushort) entries.length, cd_size, cd_offset);
return out_.data;
}
private enum ushort method_stored = 0;
private enum ushort version_needed = 20; /+ 2.0, which stored requires +/
private enum ushort dos_time = 0; /+ 00:00:00 +/
private enum ushort dos_date = 0x0021; /+ 1980-01-01 +/
private ubyte[] localHeader(ZipEntryOut e, uint crc) {
auto b = appender!(ubyte[]);
b ~= u32(0x04034b50);
b ~= u16(version_needed);
b ~= u16(0); /+ flags: none; the name is ascii +/
b ~= u16(method_stored);
b ~= u16(dos_time);
b ~= u16(dos_date);
b ~= u32(crc);
b ~= u32(cast(uint) e.data.length);
b ~= u32(cast(uint) e.data.length);
b ~= u16(cast(ushort) e.name.length);
b ~= u16(0); /+ no extra field, which "mimetype" requires +/
b ~= cast(ubyte[]) e.name.dup.representation.dup;
return b.data;
}
private ubyte[] centralEntry(ZipEntryOut e, uint crc, uint offset) {
auto b = appender!(ubyte[]);
b ~= u32(0x02014b50);
b ~= u16(version_needed); /+ made by +/
b ~= u16(version_needed);
b ~= u16(0);
b ~= u16(method_stored);
b ~= u16(dos_time);
b ~= u16(dos_date);
b ~= u32(crc);
b ~= u32(cast(uint) e.data.length);
b ~= u32(cast(uint) e.data.length);
b ~= u16(cast(ushort) e.name.length);
b ~= u16(0); /+ extra +/
b ~= u16(0); /+ comment +/
b ~= u16(0); /+ disk +/
b ~= u16(0); /+ internal attributes +/
b ~= u32(0); /+ external attributes +/
b ~= u32(offset);
b ~= cast(ubyte[]) e.name.dup.representation.dup;
return b.data;
}
private ubyte[] endRecord(ushort count, uint cd_size, uint cd_offset) {
auto b = appender!(ubyte[]);
b ~= u32(0x06054b50);
b ~= u16(0); /+ this disk +/
b ~= u16(0); /+ the disk the central directory starts on +/
b ~= u16(count);
b ~= u16(count);
b ~= u32(cd_size);
b ~= u32(cd_offset);
b ~= u16(0); /+ no archive comment +/
return b.data;
}
private uint crc32(const(ubyte)[] data) {
auto d = crc32Of(data);
/+ ↓ crc32Of gives the four bytes little endian, which is the order the
header wants them in, so they are read back in that order +/
return (cast(uint) d[0])
| (cast(uint) d[1] << 8)
| (cast(uint) d[2] << 16)
| (cast(uint) d[3] << 24);
}
private ubyte[] u16(ushort v) {
return [cast(ubyte)(v & 0xff), cast(ubyte)((v >> 8) & 0xff)];
}
private ubyte[] u32(uint v) {
return [cast(ubyte)(v & 0xff), cast(ubyte)((v >> 8) & 0xff),
cast(ubyte)((v >> 16) & 0xff), cast(ubyte)((v >> 24) & 0xff)];
}
|