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
19 module iv
.ziparcstm
/*is aliced*/;
25 // ////////////////////////////////////////////////////////////////////////// //
26 abstract class ArchiveFile
{
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
);
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
);
46 import core
.sys
.posix
.sys
.types
: off64_t
= off_t
;
48 static struct FileInfo
{
49 bool packed
; // only "store" and "deflate" are supported
58 public static struct DirEntry
{
66 bool mNormNames
; // true: convert names to lower case, do case-insensitive comparison (ASCII only)
69 // ////////////////////////////////////////////////////////////////////// //
70 static import core
.sync
.mutex
;
72 core
.sync
.mutex
.Mutex
lock;
75 lock = new core
.sync
.mutex
.Mutex
;
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 () {
91 this (ArchiveFile ame
, ulong aidx
=0) { me
= ame
; curindex
= aidx
; }
94 @property bool empty () const { return (curindex
>= me
.dir
.length
); }
95 @property DirEntry
front () const {
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; }
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
) {
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;
123 foreach (immutable idx
, ref fi
; dir
) {
125 if (strequ(fi
.path
, de.path
) && strequ(fi
.name
, de.name
)) return InnerFileStream(this, idx
, fi
.name
);
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
) {
136 auto pos
= fname
.length
;
137 while (pos
> 0 && fname
[pos
-1] != '/') --pos
;
139 de.path
= cast(string
)fname
[0..pos
]; // it's safe here
140 de.name
= cast(string
)fname
[pos
..$]; // it's safe here
142 de.name
= cast(string
)fname
; // it's safe here
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
); }
151 // ////////////////////////////////////////////////////////////////////// //
152 static private struct InnerFileStream
{
154 import core
.stdc
.stdio
: SEEK_SET
, SEEK_CUR
, SEEK_END
;
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
;
167 import std
.internal
.cstring
: tempCString
;
168 import core
.memory
: GC
;
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
);
175 if (fseek(fl
, 0, SEEK_END
) < 0) throw new NamedException
!"ZipArchive"("can't get file size for '"~filename
.idup
~"'");
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
182 mStData
.size
= cast(uint)sz
;
184 mStData
.mode
= InnerFileCookied
.Mode
.Raw
;
187 mStData
.initialize();
188 static if (is(T
== string
)) {
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
;
201 import std
.internal
.cstring
: tempCString
;
202 import core
.memory
: GC
;
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");
211 scope(exit
) za
.lock.unlock();
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
);
229 mStData
.lock = za
.lock;
231 mStData
.initialize();
232 static if (is(T
== string
)) {
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);
269 mStData
= emplace
!CT(mem
[0..instSize
], root
, isrng
);
273 this (this) @safe nothrow @nogc { if (isOpen
) mStData
.incRef(); }
274 ~this () { close(); }
276 void opAssign() (auto ref InnerFileStream src
) {
278 // assigning to opened stream
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
287 // just close this one
290 } else if (src
.isOpen
) {
291 // this stream is closed, but other is open; easy deal
292 mStData
= src
.mStData
;
298 @property bool isOpen () const pure nothrow @safe @nogc { pragma(inline
, true); return (mStData
!is 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
];
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
;
349 immutable bool gcrange
; // do `GC.removeRange()`?
350 usize gcroot
; // allocated memory that must be free()d
354 enum Mode
{ Raw
, ZLib
, Zip
}
356 core
.sync
.mutex
.Mutex
lock;
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
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
372 this (usize agcroot
, bool arange
) nothrow @safe @nogc {
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
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); }
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
400 GC
.removeRange(cast(void*)gcroot
);
401 GC
.removeRoot(cast(void*)gcroot
);
403 // free allocated memory
404 free(cast(void*)gcroot
);
406 //{ import std.stdio; stderr.writefln("0x%08x: dead, rc=%s", gcroot, rc); }
415 @property bool isOpen () { return (fl
!is null ||
(za
!is null && za
.isOpen
)); }
419 import core
.memory
: GC
;
420 lock = new core
.sync
.mutex
.Mutex
;
421 GC
.addRoot(*cast(void**)&lock);
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();
439 import core
.stdc
.stdio
: fclose
;
446 if (lock !is null && killLock
) {
447 GC
.removeRoot(*cast(void**)&lock);
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;
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
) {
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
);
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;
493 pkpos
+= zs
.avail_in
;
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;
509 ssize
read (void* buf
, usize count
) nothrow {
510 if (buf
is null) return -1;
511 if (count
== 0 || size
== 0) return 0;
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
);
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
;
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;
532 return (rd
== count ? rd
: -1);
537 if (pkb
.ptr
is null && !initZStream()) return -1;
538 // do we want to seek backward?
544 if (!initZStream()) return -1;
547 // do we need to seek forward?
550 ubyte[1024] tbuf
= void;
551 uint skp
= pos
-prpos
;
553 uint rd
= cast(uint)(skp
> tbuf
.length ? tbuf
.length
: skp
);
554 zs
.next_out
= cast(typeof(zs
.next_out
))tbuf
.ptr
;
556 if (!unpackNextChunk()) return -1;
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
);
571 long seek (long ofs
, int whence
) nothrow {
573 scope(exit
) lock.unlock();
574 if (!isOpen
) return -1;
575 //TODO: overflow checks
583 if (ofs
> 0) ofs
= 0;
589 if (ofs
< 0) return -1;
590 if (ofs
> size
) ofs
= size
;
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
);
602 import core
.exception
: onOutOfMemoryErrorNoGC
;
603 onOutOfMemoryErrorNoGC();
605 res
[0..len
] = T
.init
;
609 void xfree(T
) (ref T
[] slc
) {
610 if (slc
.ptr
!is null) {
611 import core
.stdc
.stdlib
: free
;
619 // ////////////////////////////////////////////////////////////////////////// //
620 private class ZipArchiveImpl(ST
) : ArchiveFile
if (isReadableStream
!ST
&& isSeekableStream
!ST
&& streamHasSize
!ST
) {
622 static if (!streamHasIsOpen
!ST
) bool flopened
;
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
;
635 static if (!streamHasIsOpen
!ST
) flopened
= true;
639 override @property bool isOpen () nothrow {
640 static if (streamHasIsOpen
!ST
) {
643 } catch (Exception
) {}
650 override ssize
read (void* buf
, usize count
) nothrow {
651 if (!isOpen
) return -1;
652 if (count
== 0) return 0;
654 auto res
= zfl
.rawRead(buf
[0..count
]);
655 return cast(ssize
)res
.length
;
656 } catch (Exception
) {
661 override long seek (long ofs
, int whence
=0) nothrow {
662 if (!isOpen
) return -1;
664 zfl
.seek(ofs
, whence
);
666 } catch (Exception
) {
671 override int close () nothrow {
672 if (!isOpen
) return 0;
674 static if (isCloseableStream
!ST
) zfl
.close();
676 } catch (Exception
) {
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();
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
);
708 if (flsize
< cdbuf
.length
) {
710 buf
= fl
.rawRead(cdbuf
[0..cast(usize
)flsize
]);
711 if (buf
.length
!= flsize
) throw new NamedException
!"ZipArchive"("reading error");
713 fl
.seek(-cast(ulong)cdbuf
.length
, SEEK_END
);
715 buf
= fl
.rawRead(cdbuf
[]);
716 if (buf
.length
!= cdbuf
.length
) throw new NamedException
!"ZipArchive"("reading error");
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
];
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;
737 if (eocd
.cdofs
== 0xffff_ffffu
) {
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");
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");
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
;
772 CDFileHeader cdfh
= void;
774 dir
.assumeSafeAppend
; // yep
776 if (bleft
< 4) break;
777 if (fl
.rawRead(sign
[]).length
!= sign
.length
) throw new NamedException
!"ZipArchive"("reading error");
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) {
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
);
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");
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
)
802 fl
.seek(cdfh
.namelen
+cdfh
.extlen
+cdfh
.cmtlen
, SEEK_CUR
);
803 bleft
-= cdfh
.namelen
+cdfh
.extlen
+cdfh
.cmtlen
;
807 fi
.packed
= (cdfh
.method
!= 0);
808 fi
.pksize
= cdfh
.pksize
;
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
);
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()`
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
)) {
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
);
838 if (cdfh
.extlen
< esize
) break;
839 cdfh
.extlen
-= esize
;
842 if (eid
!= 1 || esize
< /*Z64Extra.sizeof*/8) {
843 fl
.seek(esize
, SEEK_CUR
);
847 if (fi
.size
== 0xffff_ffffu
) {
848 if (fl
.rawRead((&fi
.size
)[0..1]).length
!= 1) throw new NamedException
!"ZipArchive"("reading error");
850 //debug writeln(" size=", fi.size);
852 if (fi
.pksize
== 0xffff_ffffu
) {
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;
861 if (fl.rawRead((&lfh)[0..1]).length != 1) throw new NamedException!"ZipArchive"("reading error");
864 throw new NamedException
!"ZipArchive"("invalid zip64 archive (4)");
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");
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");
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)");
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
888 if (!doSkip
&& nbpos
> 0 && nb
[nbpos
-1] != '/') {
889 if (auto idx
= nb
[0..nbpos
] in knownNames
) {
891 auto fip
= &dir
[*idx
];
892 fip
.packed
= fi
.packed
;
893 fip
.pksize
= fi
.pksize
;
895 fip
.hdrofs
= fi
.hdrofs
;
898 if (dir
.length
== uint.max
) throw new NamedException
!"ZipArchive"("directory too long");
900 fi
.path
= cast(string
)nb
[0..lastSlash
]; // this is safe
901 fi
.name
= cast(string
)nb
[lastSlash
..nbpos
]; // this is safe
904 fi
.name
= cast(string
)nb
[0..nbpos
]; // this is safe
906 knownNames
[fi
.name
] = cast(uint)dir
.length
;
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
;
917 throw new NamedException
!"ZipArchive"("unknown central directory entry");
919 debug writeln(dir
.length
, " files found");
924 // ////////////////////////////////////////////////////////////////////////// //
926 align(1) static struct ZipFileHeader
{
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
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
{
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
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
{
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
{
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
{
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
{
1010 uint disk
; // number of the disk on which this file starts