dlzma: small fixes; added "vanilized" streaming codec expample
[iv.d.git] / _obsolete_dont_use / ziparcstm.d
blob6ef63499e9794522db325313a244aa4e7aa6b176
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // severely outdated ZIP archive interface
18 // use iv.vfs instead
19 module iv.ziparcstm /*is aliced*/;
21 import iv.alice;
22 import iv.stream;
25 // ////////////////////////////////////////////////////////////////////////// //
26 abstract class ArchiveFile {
27 public:
28 // mNormNames: `true` for convert names to lower case, do case-insensitive comparison (ASCII only)
30 static auto opCall(T) (T fname, bool normNames=true) if (is(T : const(char)[])) {
31 import std.stdio : File;
32 static if (is(T == string)) {
33 auto fl = File(fname);
34 } else {
35 auto fl = File(fname.idup); // alas
37 return new ZipArchiveImpl!(typeof(fl))(fl, normNames);
40 // this will make a copy of `st`
41 static auto opCall(ST) (auto ref ST st, bool normNames=true) if (isReadableStream!ST && isSeekableStream!ST && streamHasSize!ST) {
42 return new ZipArchiveStream!ST(st, normNames);
45 protected:
46 import core.sys.posix.sys.types : off64_t = off_t;
48 static struct FileInfo {
49 bool packed; // only "store" and "deflate" are supported
50 ulong pksize;
51 ulong size;
52 ulong hdrofs;
53 string path;
54 string name;
57 // for dir range
58 public static struct DirEntry {
59 string path;
60 string name;
61 ulong size;
64 protected:
65 FileInfo[] dir;
66 bool mNormNames; // true: convert names to lower case, do case-insensitive comparison (ASCII only)
68 protected:
69 // ////////////////////////////////////////////////////////////////////// //
70 static import core.sync.mutex;
72 core.sync.mutex.Mutex lock;
74 void initLock () {
75 lock = new core.sync.mutex.Mutex;
78 public:
79 abstract @property bool isOpen () nothrow;
80 abstract ssize read (void* buf, usize count) nothrow;
81 abstract long seek (long ofs, int whence=0) nothrow;
82 abstract int close () nothrow;
84 final @property auto files () {
85 static struct Range {
86 private:
87 ArchiveFile me;
88 ulong curindex;
90 nothrow @safe @nogc:
91 this (ArchiveFile ame, ulong aidx=0) { me = ame; curindex = aidx; }
93 public:
94 @property bool empty () const { return (curindex >= me.dir.length); }
95 @property DirEntry front () const {
96 return DirEntry(
97 (curindex < me.dir.length ? me.dir[cast(usize)curindex].path : null),
98 (curindex < me.dir.length ? me.dir[cast(usize)curindex].name : null),
99 (curindex < me.dir.length ? me.dir[cast(usize)curindex].size : 0));
101 @property Range save () { return Range(me, curindex); }
102 void popFront () { if (curindex < me.dir.length) ++curindex; }
103 @property ulong length () const { return me.dir.length; }
104 @property ulong position () const { return curindex; } // current position
105 @property void position (ulong np) { curindex = np; }
106 void rewind () { curindex = 0; }
108 return Range(this);
111 auto fopen (ref in DirEntry de) {
112 static bool strequ() (const(char)[] s0, const(char)[] s1) {
113 if (s0.length != s1.length) return false;
114 foreach (immutable idx, char ch; s0) {
115 char c1 = s1[idx];
116 if (ch >= 'A' && ch <= 'Z') ch += 32; // poor man's `toLower()`
117 if (c1 >= 'A' && c1 <= 'Z') c1 += 32; // poor man's `toLower()`
118 if (ch != c1) return false;
120 return true;
123 foreach (immutable idx, ref fi; dir) {
124 if (mNormNames) {
125 if (strequ(fi.path, de.path) && strequ(fi.name, de.name)) return InnerFileStream(this, idx, fi.name);
126 } else {
127 if (fi.path == de.path && fi.name == de.name) return InnerFileStream(this, idx, fi.name);
131 throw new NamedException!"ZipArchive"("file not found");
134 auto fopen (const(char)[] fname) {
135 DirEntry de;
136 auto pos = fname.length;
137 while (pos > 0 && fname[pos-1] != '/') --pos;
138 if (pos) {
139 de.path = cast(string)fname[0..pos]; // it's safe here
140 de.name = cast(string)fname[pos..$]; // it's safe here
141 } else {
142 de.name = cast(string)fname; // it's safe here
144 return fopen(de);
147 // we might want to return the same file struct for disk and archived files
148 static auto fopenDisk(T) (T fname) if (is(T : const(char)[])) { return InnerFileStream(fname); }
150 private:
151 // ////////////////////////////////////////////////////////////////////// //
152 static private struct InnerFileStream {
153 public:
154 import core.stdc.stdio : SEEK_SET, SEEK_CUR, SEEK_END;
156 string name;
158 private:
159 InnerFileCookied mStData;
161 this(T) (T filename) if (is(T : const(char)[])) {
162 import core.sys.linux.stdio : fopencookie;
163 import core.stdc.stdio : FILE;
164 import core.stdc.stdio : fopen, fclose, fseek, ftell;
165 import core.stdc.stdlib : calloc, free;
166 import etc.c.zlib;
167 import std.internal.cstring : tempCString;
168 import core.memory : GC;
170 // open disk file
171 FILE* fl = fopen(filename.tempCString!char(), "rb");
172 if (fl is null) throw new NamedException!"ZipArchive"("can't open file '"~filename.idup~"'");
173 scope(failure) fclose(fl);
174 // get size
175 if (fseek(fl, 0, SEEK_END) < 0) throw new NamedException!"ZipArchive"("can't get file size for '"~filename.idup~"'");
176 auto sz = ftell(fl);
177 if (sz == -1) throw new NamedException!"ZipArchive"("can't get file size for '"~filename.idup~"'");
178 if (sz > 0xffff_ffff) throw new NamedException!"ZipArchive"("file '"~filename.idup~"' too big");
180 this.initialize(); // fills mStData
181 mStData.stpos = 0;
182 mStData.size = cast(uint)sz;
183 mStData.pksize = 0;
184 mStData.mode = InnerFileCookied.Mode.Raw;
185 mStData.fl = fl;
186 // ok
187 mStData.initialize();
188 static if (is(T == string)) {
189 name = filename;
190 } else {
191 name = filename.idup;
195 this(T) (ArchiveFile za, uint idx, T filename) if (is(T : const(char)[])) {
196 import core.sys.linux.stdio : fopencookie;
197 import core.stdc.stdio : FILE;
198 import core.stdc.stdio : fopen, fclose, fseek, ftell;
199 import core.stdc.stdlib : calloc, free;
200 import etc.c.zlib;
201 import std.internal.cstring : tempCString;
202 import core.memory : GC;
204 assert(za !is null);
205 if (!za.isOpen) throw new NamedException!"ZipArchive"("archive wasn't opened");
206 //if (zfl.name.length == 0) throw new NamedException!"ZipArchive"("archive has no name");
207 if (idx >= za.dir.length) throw new NamedException!"ZipArchive"("invalid dir index");
208 ulong stofs;
210 za.lock.lock();
211 scope(exit) za.lock.unlock();
212 // read file header
213 ZipFileHeader zfh = void;
214 if (za.seek(za.dir[idx].hdrofs) == -1) throw new NamedException!"ZipArchive"("seek error");
215 if (za.read(&zfh, zfh.sizeof) != zfh.sizeof) throw new NamedException!"ZipArchive"("reading error");
216 if (zfh.sign != "PK\x03\x04") throw new NamedException!"ZipArchive"("invalid archive entry");
217 // skip name and extra
218 auto xpos = za.seek(0, SEEK_CUR);
219 if (xpos == -1) throw new NamedException!"ZipArchive"("seek error");
220 stofs = xpos+zfh.namelen+zfh.extlen;
223 this.initialize(); // fills mStData
224 mStData.stpos = stofs;
225 mStData.size = cast(uint)za.dir[idx].size; //FIXME
226 mStData.pksize = cast(uint)za.dir[idx].pksize; //FIXME
227 mStData.mode = (za.dir[idx].packed ? InnerFileCookied.Mode.Zip : InnerFileCookied.Mode.Raw);
228 mStData.za = za;
229 mStData.lock = za.lock;
230 // ok
231 mStData.initialize();
232 static if (is(T == string)) {
233 name = filename;
234 } else {
235 name = filename.idup;
239 private void initialize () {
240 // and now... rock-a-rolla!
241 // actually, we shouldn't use malloc() here, 'cause we can have alot of
242 // free memory in GC and no memory for malloc(), but... let's be realistic:
243 // we aren't aiming at constrained systems
244 import core.exception : onOutOfMemoryErrorNoGC;
245 import core.memory : GC;
246 import core.stdc.stdlib : malloc;
247 import std.conv : emplace;
248 import std.traits : hasIndirections;
249 alias CT = InnerFileCookied; // i'm lazy
250 enum instSize = __traits(classInstanceSize, CT);
251 // let's hope that malloc() aligns returned memory right
252 auto mem = malloc(instSize);
253 if (mem is null) onOutOfMemoryErrorNoGC(); // oops
254 usize root = cast(usize)mem;
256 static if (hasIndirections!ST) {
257 // ouch, ST has some pointers; register it as gc root and range
258 // note that this approach is very simplictic; we might want to
259 // scan the type for pointers using typeinfo pointer bitmap and
260 // register only pointer containing areas.
261 GC.addRoot(cast(void*)root);
262 GC.addRange(cast(void*)root, instSize);
263 enum isrng = true;
264 } else {
265 enum isrng = false;
268 enum isrng = true;
269 mStData = emplace!CT(mem[0..instSize], root, isrng);
272 public:
273 this (this) @safe nothrow @nogc { if (isOpen) mStData.incRef(); }
274 ~this () { close(); }
276 void opAssign() (auto ref InnerFileStream src) {
277 if (isOpen) {
278 // assigning to opened stream
279 if (src.isOpen) {
280 // both streams are opened
281 // we don't care if internal streams are different, our rc scheme will take care of this
282 auto old = mStData; // decRef() can throw, so be on the safe side
283 mStData = src.mStData;
284 mStData.incRef(); // this can't throw
285 old.decRef(); // release old stream
286 } else {
287 // just close this one
288 close();
290 } else if (src.isOpen) {
291 // this stream is closed, but other is open; easy deal
292 mStData = src.mStData;
293 mStData.incRef();
295 name = src.name;
298 @property bool isOpen () const pure nothrow @safe @nogc { pragma(inline, true); return (mStData !is null); }
300 void close () {
301 if (isOpen) {
302 mStData.decRef();
303 mStData = null;
304 name = null;
308 @property long tell () const pure nothrow @safe @nogc { pragma(inline, true); return (isOpen ? mStData.pos : 0); }
309 @property long size () const pure nothrow @safe @nogc { pragma(inline, true); return (isOpen ? mStData.size : 0); }
310 @property bool eof () const pure nothrow @trusted @nogc { pragma(inline, true); return (isOpen ? mStData.pos >= mStData.size : true); }
312 //TODO: check for overflow
313 void seek (long offset, int origin=SEEK_SET) @trusted {
314 if (!isOpen) throw new NamedException!"ZipArchive"("can't seek in closed stream");
315 if (mStData.seek(offset, origin) < 0) throw new NamedException!"ZipArchive"("seek error");
318 private import std.traits : isMutable;
320 T[] rawRead(T) (T[] buf) @trusted if (isMutable!T) {
321 if (!isOpen) throw new NamedException!"ZipArchive"("can't read from closed stream");
322 if (buf.length > 0) {
323 auto vb = cast(void[])buf;
324 auto len = mStData.read(vb.ptr, vb.length);
325 return buf[0..cast(usize)len/T.sizeof];
326 } else {
327 return buf[0..0];
332 static assert(isReadableStream!InnerFileStream);
333 static assert(isSeekableStream!InnerFileStream);
334 static assert(streamHasEOF!InnerFileStream);
335 static assert(streamHasSeek!InnerFileStream);
336 static assert(streamHasTell!InnerFileStream);
337 static assert(streamHasName!InnerFileStream);
338 static assert(streamHasSize!InnerFileStream);
340 // ////////////////////////////////////////////////////////////////////// //
341 // "inner" file processor; processes both packed and unpacked files
342 // can be used as normal disk file processor too
343 static private final class InnerFileCookied {
344 private import etc.c.zlib;
345 private import core.sys.posix.sys.types : ssize, off64_t = off_t;
346 private import core.stdc.stdio : FILE;
348 uint rc = 1;
349 immutable bool gcrange; // do `GC.removeRange()`?
350 usize gcroot; // allocated memory that must be free()d
352 enum ibsize = 32768;
354 enum Mode { Raw, ZLib, Zip }
356 core.sync.mutex.Mutex lock;
357 bool killLock;
358 // note that either one of `fl` or `xfl` must be opened and operational
359 FILE* fl; // disk file, can be `null`
360 ArchiveFile za; // archive file, can be closed
361 Mode mode;
362 long stpos; // starting position
363 uint size; // unpacked size
364 uint pksize; // packed size
365 uint pos; // current file position
366 uint prpos; // previous file position
367 uint pkpos; // current position in DAT
368 ubyte[] pkb; // packed data
369 z_stream zs;
370 bool eoz;
372 this (usize agcroot, bool arange) nothrow @safe @nogc {
373 gcrange = arange;
374 gcroot = agcroot;
375 //{ import std.stdio; stderr.writefln("0x%08x: ctor, rc=%s", gcroot, rc); }
378 // this should never be called
379 ~this () nothrow @safe @nogc {
380 if (rc != 0) assert(0); // the thing that should not be
381 assert(0); // why we are here?!
384 void incRef () nothrow @safe @nogc {
385 if (++rc == 0) assert(0); // hey, this is definitely a bug!
386 //{ import std.stdio; stderr.writefln("0x%08x: incRef, rc=%s", gcroot, rc); }
389 // return true if this class is dead
390 bool decRef () {
391 if (rc-- == 0) assert(0); // hey, this is definitely a bug!
392 //{ import std.stdio; stderr.writefln("0x%08x: decRef, rc=%s", gcroot, rc); }
393 if (rc == 0) {
394 import core.memory : GC;
395 import core.stdc.stdlib : free;
396 close(); // finalize stream
397 if (gcroot == 0) assert(0); // the thing that should not be
398 // remove roots
399 if (gcrange) {
400 GC.removeRange(cast(void*)gcroot);
401 GC.removeRoot(cast(void*)gcroot);
403 // free allocated memory
404 free(cast(void*)gcroot);
405 // just in case
406 //{ import std.stdio; stderr.writefln("0x%08x: dead, rc=%s", gcroot, rc); }
407 gcroot = 0;
408 return true;
409 } else {
410 return false;
414 nothrow:
415 @property bool isOpen () { return (fl !is null || (za !is null && za.isOpen)); }
417 void initialize () {
418 if (lock is null) {
419 import core.memory : GC;
420 lock = new core.sync.mutex.Mutex;
421 GC.addRoot(*cast(void**)&lock);
422 killLock = true;
426 void close () {
427 import core.memory : GC;
428 import core.stdc.stdlib : free;
430 //if (lock !is null) { import iv.writer; writeln("CLOSING!"); }
431 if (lock !is null) lock.lock();
432 scope(exit) if (lock !is null) lock.unlock();
433 if (pkb.length) {
434 inflateEnd(&zs);
435 free(pkb.ptr);
436 pkb = null;
438 if (fl !is null) {
439 import core.stdc.stdio : fclose;
440 fclose(fl);
441 fl = null;
443 za = null;
445 eoz = true;
446 if (lock !is null && killLock) {
447 GC.removeRoot(*cast(void**)&lock);
448 delete lock;
449 lock = null;
450 killLock = false;
454 private bool initZStream () {
455 import core.stdc.stdlib : malloc, free;
456 if (mode == Mode.Raw || pkb.ptr !is null) return true;
457 // allocate buffer for packed data
458 auto pb = cast(ubyte*)malloc(ibsize);
459 if (pb is null) return false;
460 pkb = pb[0..ibsize];
461 zs.avail_in = 0;
462 zs.avail_out = 0;
463 // initialize unpacker
464 // -15 is a magic value used to decompress zip files:
465 // it has the effect of not requiring the 2 byte header and 4 byte trailer
466 if (inflateInit2(&zs, (mode == Mode.Zip ? -15 : 15)) != Z_OK) {
467 free(pb);
468 pkb = null;
469 return false;
471 // we are ready
472 return true;
475 private bool readPackedChunk () {
476 import core.stdc.stdio : fread;
477 import core.sys.posix.stdio : fseeko;
478 if (zs.avail_in > 0) return true;
479 if (pkpos >= pksize) return false;
480 zs.next_in = cast(typeof(zs.next_in))pkb.ptr;
481 zs.avail_in = cast(uint)(pksize-pkpos > ibsize ? ibsize : pksize-pkpos);
482 if (fl !is null) {
483 // `FILE*`
484 if (fseeko(fl, stpos+pkpos, 0) < 0) return false;
485 if (fread(pkb.ptr, zs.avail_in, 1, fl) != 1) return false;
486 } else if (za !is null) {
487 if (za.seek(stpos+pkpos, 0) == -1) return false;
488 auto rd = za.read(pkb.ptr, zs.avail_in);
489 if (rd != zs.avail_in) return false;
490 } else {
491 return false;
493 pkpos += zs.avail_in;
494 return true;
497 private bool unpackNextChunk () {
498 while (zs.avail_out > 0) {
499 if (eoz) return false;
500 if (!readPackedChunk()) return false;
501 auto err = inflate(&zs, Z_SYNC_FLUSH);
502 //if (err == Z_BUF_ERROR) { import iv.writer; writeln("*** OUT OF BUFFER!"); }
503 if (err != Z_STREAM_END && err != Z_OK) return false;
504 if (err == Z_STREAM_END) eoz = true;
506 return true;
509 ssize read (void* buf, usize count) nothrow {
510 if (buf is null) return -1;
511 if (count == 0 || size == 0) return 0;
512 lock.lock();
513 scope(exit) lock.unlock();
514 if (!isOpen) return -1; // read error
515 if (pos >= size) return 0; // EOF
516 if (mode == Mode.Raw) {
517 import core.stdc.stdio : ferror, fread;
518 import core.sys.posix.stdio : fseeko;
519 if (size-pos < count) count = cast(usize)(size-pos);
520 if (fl !is null) {
521 // `FILE*`
522 if (fseeko(fl, stpos+pos, 0) < 0) return -1;
523 auto rd = fread(buf, 1, count, fl);
524 if (rd != count && (rd < 0 || ferror(fl))) rd = -1;
525 if (rd > 0) pos += rd;
526 return rd;
527 } else if (za !is null) {
528 if (za.seek(stpos+pos, 0) == -1) return -1;
529 auto rd = za.read(buf, count);
530 if (rd < 1) return -1;
531 pos += rd;
532 return (rd == count ? rd : -1);
533 } else {
534 return -1;
536 } else {
537 if (pkb.ptr is null && !initZStream()) return -1;
538 // do we want to seek backward?
539 if (prpos > pos) {
540 // yes, rewind
541 inflateEnd(&zs);
542 zs = zs.init;
543 pkpos = 0;
544 if (!initZStream()) return -1;
545 prpos = 0;
547 // do we need to seek forward?
548 if (prpos < pos) {
549 // yes, skip data
550 ubyte[1024] tbuf = void;
551 uint skp = pos-prpos;
552 while (skp > 0) {
553 uint rd = cast(uint)(skp > tbuf.length ? tbuf.length : skp);
554 zs.next_out = cast(typeof(zs.next_out))tbuf.ptr;
555 zs.avail_out = rd;
556 if (!unpackNextChunk()) return -1;
557 skp -= rd;
559 prpos = pos;
561 // unpack data
562 if (size-pos < count) count = cast(usize)(size-pos);
563 zs.next_out = cast(typeof(zs.next_out))buf;
564 zs.avail_out = cast(uint)count;
565 if (!unpackNextChunk()) return -1;
566 prpos = (pos += count);
567 return count;
571 long seek (long ofs, int whence) nothrow {
572 lock.lock();
573 scope(exit) lock.unlock();
574 if (!isOpen) return -1;
575 //TODO: overflow checks
576 switch (whence) {
577 case 0: // SEEK_SET
578 break;
579 case 1: // SEEK_CUR
580 ofs += pos;
581 break;
582 case 2: // SEEK_END
583 if (ofs > 0) ofs = 0;
584 ofs += size;
585 break;
586 default:
587 return -1;
589 if (ofs < 0) return -1;
590 if (ofs > size) ofs = size;
591 pos = cast(uint)ofs;
592 return ofs;
596 static protected:
597 T[] xalloc(T) (usize len) {
598 import core.stdc.stdlib : malloc;
599 if (len < 1) return null;
600 auto res = cast(T*)malloc(len*T.sizeof);
601 if (res is null) {
602 import core.exception : onOutOfMemoryErrorNoGC;
603 onOutOfMemoryErrorNoGC();
605 res[0..len] = T.init;
606 return res[0..len];
609 void xfree(T) (ref T[] slc) {
610 if (slc.ptr !is null) {
611 import core.stdc.stdlib : free;
612 free(slc.ptr);
614 slc = null;
619 // ////////////////////////////////////////////////////////////////////////// //
620 private class ZipArchiveImpl(ST) : ArchiveFile if (isReadableStream!ST && isSeekableStream!ST && streamHasSize!ST) {
621 private final:
622 static if (!streamHasIsOpen!ST) bool flopened;
623 ST zfl;
625 public:
626 this () { assert(0); }
628 // it now owns the file (if no exception was thrown)
629 this() (auto ref ST fl, bool normNames) {
630 mNormNames = normNames;
631 initLock();
632 open(fl);
633 scope(success) {
634 zfl = fl;
635 static if (!streamHasIsOpen!ST) flopened = true;
639 override @property bool isOpen () nothrow {
640 static if (streamHasIsOpen!ST) {
641 try {
642 return zfl.isOpen;
643 } catch (Exception) {}
644 return false;
645 } else {
646 return flopened;
650 override ssize read (void* buf, usize count) nothrow {
651 if (!isOpen) return -1;
652 if (count == 0) return 0;
653 try {
654 auto res = zfl.rawRead(buf[0..count]);
655 return cast(ssize)res.length;
656 } catch (Exception) {
657 return -1;
661 override long seek (long ofs, int whence=0) nothrow {
662 if (!isOpen) return -1;
663 try {
664 zfl.seek(ofs, whence);
665 return zfl.tell;
666 } catch (Exception) {
667 return -1;
671 override int close () nothrow {
672 if (!isOpen) return 0;
673 try {
674 static if (isCloseableStream!ST) zfl.close();
675 return 0;
676 } catch (Exception) {
677 return -1;
681 private:
682 void cleanup () {
683 dir.length = 0;
686 void open() (auto ref ST fl) {
687 import core.stdc.stdio : SEEK_CUR, SEEK_END;
688 debug import std.stdio : writeln, writefln;
689 scope(failure) cleanup();
692 ushort readU16 () {
693 ubyte[2] data;
694 if (fl.rawRead(data[]).length != data.length) throw new NamedException!"ZipArchive"("reading error");
695 return cast(ushort)(data[0]+0x100*data[1]);
699 if (fl.size > 0xffff_ffffu) throw new NamedException!"ZipArchive"("file too big");
700 ulong flsize = fl.size;
701 if (flsize < EOCDHeader.sizeof) throw new NamedException!"ZipArchive"("file too small");
703 // search for "end of central dir"
704 auto cdbuf = xalloc!ubyte(65536+EOCDHeader.sizeof+Z64Locator.sizeof);
705 scope(exit) xfree(cdbuf);
706 ubyte[] buf;
707 ulong ubufpos;
708 if (flsize < cdbuf.length) {
709 fl.seek(0);
710 buf = fl.rawRead(cdbuf[0..cast(usize)flsize]);
711 if (buf.length != flsize) throw new NamedException!"ZipArchive"("reading error");
712 } else {
713 fl.seek(-cast(ulong)cdbuf.length, SEEK_END);
714 ubufpos = fl.tell;
715 buf = fl.rawRead(cdbuf[]);
716 if (buf.length != cdbuf.length) throw new NamedException!"ZipArchive"("reading error");
718 int pos;
719 for (pos = cast(int)(buf.length-EOCDHeader.sizeof); pos >= 0; --pos) {
720 if (buf[pos] == 'P' && buf[pos+1] == 'K' && buf[pos+2] == 5 && buf[pos+3] == 6) break;
722 if (pos < 0) throw new NamedException!"ZipArchive"("no central dir end marker found");
723 auto eocd = cast(EOCDHeader*)&buf[pos];
724 debug(ziparc) {
725 writeln("=== EOCD ===");
726 writeln("diskno: ", eocd.diskno);
727 writeln("diskcd: ", eocd.diskcd);
728 writeln("diskfileno: ", eocd.diskfileno);
729 writeln("fileno: ", eocd.fileno);
730 writeln("cdsize: ", eocd.cdsize);
731 writefln("cdofs: %s (0x%08x)", eocd.cdofs, eocd.cdofs);
732 writeln("cmtsize: ", eocd.cmtsize);
734 long cdofs = -1, cdsize = -1;
735 bool zip64 = false;
736 // zip64?
737 if (eocd.cdofs == 0xffff_ffffu) {
738 zip64 = true;
739 if (pos < Z64Locator.sizeof) throw new NamedException!"ZipArchive"("corrupted archive");
740 auto lt64 = cast(Z64Locator*)&buf[pos-Z64Locator.sizeof];
741 if (lt64.sign != "PK\x06\x07") throw new NamedException!"ZipArchive"("corrupted archive");
742 if (lt64.diskcd != 0 || lt64.diskno > 1) throw new NamedException!"ZipArchive"("multidisk archive");
743 debug writeln("ecd64ofs=", lt64.ecd64ofs);
744 if (lt64.ecd64ofs < 0 || lt64.ecd64ofs+EOCD64Header.sizeof > ubufpos+pos-Z64Locator.sizeof) throw new NamedException!"ZipArchive"("corrupted archive");
745 EOCD64Header e64 = void;
746 fl.seek(lt64.ecd64ofs);
747 if (fl.rawRead((&e64)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
748 if (e64.sign != "PK\x06\x06") throw new NamedException!"ZipArchive"("corrupted archive");
749 if (e64.diskno != 0 || e64.diskcd != 0) throw new NamedException!"ZipArchive"("multidisk archive");
750 if (e64.diskfileno != e64.fileno) throw new NamedException!"ZipArchive"("corrupted archive");
751 if (e64.cdsize >= lt64.ecd64ofs) throw new NamedException!"ZipArchive"("corrupted archive");
752 if (e64.cdofs >= lt64.ecd64ofs || e64.cdofs+e64.cdsize > lt64.ecd64ofs) throw new NamedException!"ZipArchive"("corrupted archive");
753 cdofs = e64.cdofs;
754 cdsize = e64.cdsize;
755 } else {
756 if (eocd.diskno != 0 || eocd.diskcd != 0) throw new NamedException!"ZipArchive"("multidisk archive");
757 if (eocd.diskfileno != eocd.fileno || ubufpos+pos+EOCDHeader.sizeof+eocd.cmtsize != flsize) throw new NamedException!"ZipArchive"("corrupted archive");
758 cdofs = eocd.cdofs;
759 cdsize = eocd.cdsize;
760 if (cdofs >= ubufpos+pos || flsize-cdofs < cdsize) throw new NamedException!"ZipArchive"("corrupted archive");
763 // now read central directory
764 auto namebuf = xalloc!char(0x10000);
765 scope(exit) xfree(namebuf);
767 uint[string] knownNames; // value is dir index
768 scope(exit) knownNames.destroy;
769 cleanup();
770 auto bleft = cdsize;
771 fl.seek(cdofs);
772 CDFileHeader cdfh = void;
773 char[4] sign;
774 dir.assumeSafeAppend; // yep
775 while (bleft > 0) {
776 if (bleft < 4) break;
777 if (fl.rawRead(sign[]).length != sign.length) throw new NamedException!"ZipArchive"("reading error");
778 bleft -= 4;
779 if (sign[0] != 'P' || sign[1] != 'K') throw new NamedException!"ZipArchive"("invalid central directory entry");
780 // digital signature?
781 if (sign[2] == 5 && sign[3] == 5) {
782 // yes, skip it
783 if (bleft < 2) throw new NamedException!"ZipArchive"("reading error");
784 auto sz = fl.readNum!ushort;
785 if (sz > bleft) throw new NamedException!"ZipArchive"("invalid central directory entry");
786 fl.seek(sz, SEEK_CUR);
787 bleft -= sz;
788 continue;
790 // file item?
791 if (sign[2] == 1 && sign[3] == 2) {
792 if (bleft < cdfh.sizeof) throw new NamedException!"ZipArchive"("reading error");
793 if (fl.rawRead((&cdfh)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
794 bleft -= cdfh.sizeof;
795 if (cdfh.disk != 0) throw new NamedException!"ZipArchive"("invalid central directory entry (disk number)");
796 if (bleft < cdfh.namelen+cdfh.extlen+cdfh.cmtlen) throw new NamedException!"ZipArchive"("invalid central directory entry");
797 // skip bad files
798 if ((cdfh.method != 0 && cdfh.method != 8) || cdfh.namelen == 0 || (cdfh.gflags&0b10_0000_0110_0001) != 0 || (cdfh.attr&0x58) != 0 ||
799 cast(long)cdfh.hdrofs+(cdfh.method ? cdfh.pksize : cdfh.size) >= ubufpos+pos)
801 // ignore this
802 fl.seek(cdfh.namelen+cdfh.extlen+cdfh.cmtlen, SEEK_CUR);
803 bleft -= cdfh.namelen+cdfh.extlen+cdfh.cmtlen;
804 continue;
806 FileInfo fi;
807 fi.packed = (cdfh.method != 0);
808 fi.pksize = cdfh.pksize;
809 fi.size = cdfh.size;
810 fi.hdrofs = cdfh.hdrofs;
811 if (!fi.packed) fi.pksize = fi.size;
812 // now, this is valid file, so read it's name
813 if (fl.rawRead(namebuf[0..cdfh.namelen]).length != cdfh.namelen) throw new NamedException!"ZipArchive"("reading error");
814 auto nb = new char[](cdfh.namelen);
815 uint nbpos = 0;
816 uint lastSlash = 0;
817 foreach (ref char ch; namebuf[0..cdfh.namelen]) {
818 if (ch == '\\') ch = '/'; // just in case
819 if (ch == '/' && (nbpos == 0 || (nbpos > 0 && nb[nbpos-1] == '/'))) continue;
820 if (ch == '/') lastSlash = nbpos+1;
821 if (mNormNames && ch >= 'A' && ch <= 'Z') ch += 32; // poor man's `toLower()`
822 nb[nbpos++] = ch;
824 bool doSkip = false;
825 // should we parse extra field?
826 debug writefln("size=0x%08x; pksize=0x%08x; packed=%s", fi.size, fi.pksize, (fi.packed ? "tan" : "ona"));
827 if (zip64 && (fi.size == 0xffff_ffffu || fi.pksize == 0xffff_ffffu || fi.hdrofs == 0xffff_ffffu)) {
828 // yep, do it
829 bool found = false;
830 //Z64Extra z64e = void;
831 debug writeln("extlen=", cdfh.extlen);
832 while (cdfh.extlen >= 4) {
833 auto eid = fl.readNum!ushort;
834 auto esize = fl.readNum!ushort;
835 debug writefln("0x%04x %s", eid, esize);
836 cdfh.extlen -= 4;
837 bleft -= 4;
838 if (cdfh.extlen < esize) break;
839 cdfh.extlen -= esize;
840 bleft -= esize;
841 // skip unknown info
842 if (eid != 1 || esize < /*Z64Extra.sizeof*/8) {
843 fl.seek(esize, SEEK_CUR);
844 } else {
845 // wow, Zip64 info
846 found = true;
847 if (fi.size == 0xffff_ffffu) {
848 if (fl.rawRead((&fi.size)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
849 esize -= 8;
850 //debug writeln(" size=", fi.size);
852 if (fi.pksize == 0xffff_ffffu) {
853 if (esize == 0) {
854 //fi.pksize = ulong.max; // this means "get from local header"
855 // read local file header; it's slow, but i don't care
857 if (fi.hdrofs == 0xffff_ffffu) throw new NamedException!"ZipArchive"("invalid zip64 archive (3)");
858 CDFileHeader lfh = void;
859 auto oldpos = fl.tell;
860 fl.seek(fi.hdrofs);
861 if (fl.rawRead((&lfh)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
862 assert(0);
864 throw new NamedException!"ZipArchive"("invalid zip64 archive (4)");
865 } else {
866 if (esize < 8) throw new NamedException!"ZipArchive"("invalid zip64 archive (1)");
867 if (fl.rawRead((&fi.pksize)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
868 esize -= 8;
871 if (fi.hdrofs == 0xffff_ffffu) {
872 if (esize < 8) throw new NamedException!"ZipArchive"("invalid zip64 archive (2)");
873 if (fl.rawRead((&fi.hdrofs)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
874 esize -= 8;
876 if (esize > 0) fl.seek(esize, SEEK_CUR); // skip possible extra data
877 //if (z64e.disk != 0) throw new NamedException!"ZipArchive"("invalid central directory entry (disk number)");
878 break;
881 if (!found) {
882 debug writeln("required zip64 record not found");
883 //throw new NamedException!"ZipArchive"("required zip64 record not found");
884 //fi.size = fi.pksize = 0x1_0000_0000Lu; // hack: skip it
885 doSkip = true;
888 if (!doSkip && nbpos > 0 && nb[nbpos-1] != '/') {
889 if (auto idx = nb[0..nbpos] in knownNames) {
890 // replace
891 auto fip = &dir[*idx];
892 fip.packed = fi.packed;
893 fip.pksize = fi.pksize;
894 fip.size = fi.size;
895 fip.hdrofs = fi.hdrofs;
896 } else {
897 // add new
898 if (dir.length == uint.max) throw new NamedException!"ZipArchive"("directory too long");
899 if (lastSlash) {
900 fi.path = cast(string)nb[0..lastSlash]; // this is safe
901 fi.name = cast(string)nb[lastSlash..nbpos]; // this is safe
902 } else {
903 fi.path = "";
904 fi.name = cast(string)nb[0..nbpos]; // this is safe
906 knownNames[fi.name] = cast(uint)dir.length;
907 dir ~= fi;
909 //debug writefln("%10s %10s %s %04s/%02s/%02s %02s:%02s:%02s %s", fi.pksize, fi.size, (fi.packed ? "P" : "."), cdfh.year, cdfh.month, cdfh.day, cdfh.hour, cdfh.min, cdfh.sec, fi.name);
911 // skip extra and comments
912 fl.seek(cdfh.extlen+cdfh.cmtlen, SEEK_CUR);
913 bleft -= cdfh.namelen+cdfh.extlen+cdfh.cmtlen;
914 continue;
916 // wtf?!
917 throw new NamedException!"ZipArchive"("unknown central directory entry");
919 debug writeln(dir.length, " files found");
924 // ////////////////////////////////////////////////////////////////////////// //
925 private:
926 align(1) static struct ZipFileHeader {
927 align(1):
928 char[4] sign; // "PK\x03\x04"
929 ushort extrver; // version needed to extract
930 ushort gflags; // general purpose bit flag
931 ushort method; // compression method
932 ushort mtime; // last mod file time
933 ushort mdate; // last mod file date
934 uint crc32;
935 uint pksize; // compressed size
936 uint size; // uncompressed size
937 ushort namelen; // file name length
938 ushort extlen; // extra field length
941 align(1) static struct CDFileHeader {
942 align(1):
943 //char[4] sign; // "PK\x01\x02"
944 ushort madebyver; // version made by
945 ushort extrver; // version needed to extract
946 ushort gflags; // general purpose bit flag
947 ushort method; // compression method
948 ushort mtime; // last mod file time
949 ushort mdate; // last mod file date
950 uint crc32;
951 uint pksize; // compressed size
952 uint size; // uncompressed size
953 ushort namelen; // file name length
954 ushort extlen; // extra field length
955 ushort cmtlen; // file comment length
956 ushort disk; // disk number start
957 ushort iattr; // internal file attributes
958 uint attr; // external file attributes
959 uint hdrofs; // relative offset of local header
961 @property pure const nothrow @safe @nogc:
962 ubyte hour () { return (mtime>>11); }
963 ubyte min () { return (mtime>>5)&0x3f; }
964 ubyte sec () { return (mtime&0x1f)*2; }
966 ushort year () { return cast(ushort)((mdate>>9)+1980); }
967 ubyte month () { return (mdate>>5)&0x0f; }
968 ubyte day () { return (mdate&0x1f); }
971 align(1) static struct EOCDHeader {
972 align(1):
973 char[4] sign; // "PK\x05\x06"
974 ushort diskno; // number of this disk
975 ushort diskcd; // number of the disk with the start of the central directory
976 ushort diskfileno; // total number of entries in the central directory on this disk
977 ushort fileno; // total number of entries in the central directory
978 uint cdsize; // size of the central directory
979 uint cdofs; // offset of start of central directory with respect to the starting disk number
980 ushort cmtsize; // .ZIP file comment length
983 align(1) static struct EOCD64Header {
984 align(1):
985 char[4] sign; // "PK\x06\x06"
986 ulong eocdsize; // size of zip64 end of central directory record
987 ushort madebyver; // version made by
988 ushort extrver; // version needed to extract
989 uint diskno; // number of this disk
990 uint diskcd; // number of the disk with the start of the central directory
991 ulong diskfileno; // total number of entries in the central directory
992 ulong fileno; // total number of entries in the central directory
993 ulong cdsize; // size of the central directory
994 ulong cdofs; // offset of start of central directory with respect to the starting disk number
997 align(1) static struct Z64Locator {
998 align(1):
999 char[4] sign; // "PK\x06\x07"
1000 uint diskcd; // number of the disk with the start of the zip64 end of central directory
1001 long ecd64ofs; // relative offset of the zip64 end of central directory record
1002 uint diskno; // total number of disks
1005 align(1) static struct Z64Extra {
1006 align(1):
1007 ulong size;
1008 ulong pksize;
1009 ulong hdrofs;
1010 uint disk; // number of the disk on which this file starts