diff options
Diffstat (limited to 'dlang/src/write/zip.d')
| -rw-r--r-- | dlang/src/write/zip.d | 125 |
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)]; +} |
