summaryrefslogtreecommitdiffhomepage
path: root/dlang/src/write/zip.d
diff options
context:
space:
mode:
Diffstat (limited to 'dlang/src/write/zip.d')
-rw-r--r--dlang/src/write/zip.d125
1 files changed, 125 insertions, 0 deletions
diff --git a/dlang/src/write/zip.d b/dlang/src/write/zip.d
new file mode 100644
index 0000000..cfd8419
--- /dev/null
+++ b/dlang/src/write/zip.d
@@ -0,0 +1,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)];
+}