mirror of
https://github.com/dlang/phobos.git
synced 2025-04-29 14:40:30 +03:00
446 lines
11 KiB
D
446 lines
11 KiB
D
|
|
module std.zip;
|
|
|
|
private import std.zlib;
|
|
private import std.date;
|
|
private import std.intrinsic;
|
|
|
|
//debug=print;
|
|
|
|
/* Throw this on errors.
|
|
*/
|
|
|
|
class ZipException : Exception
|
|
{
|
|
this(char[] msg)
|
|
{
|
|
super("ZipException: " ~ msg);
|
|
}
|
|
}
|
|
|
|
class ArchiveMember
|
|
{
|
|
ushort madeVersion = 20;
|
|
ushort extractVersion = 20;
|
|
ushort flags;
|
|
ushort compressionMethod;
|
|
std.date.DosFileTime time;
|
|
uint crc32;
|
|
uint compressedSize;
|
|
uint expandedSize;
|
|
ushort diskNumber;
|
|
ushort internalAttributes;
|
|
uint externalAttributes;
|
|
|
|
private uint offset;
|
|
|
|
char[] name;
|
|
ubyte[] extra;
|
|
char[] comment;
|
|
ubyte[] compressedData;
|
|
ubyte[] expandedData;
|
|
|
|
debug(print)
|
|
{
|
|
void print()
|
|
{
|
|
printf("name = '%.*s'\n", name);
|
|
printf("\tcomment = '%.*s'\n", comment);
|
|
printf("\tmadeVersion = x%04x\n", madeVersion);
|
|
printf("\textractVersion = x%04x\n", extractVersion);
|
|
printf("\tflags = x%04x\n", flags);
|
|
printf("\tcompressionMethod = %d\n", compressionMethod);
|
|
printf("\ttime = %d\n", time);
|
|
printf("\tcrc32 = x%08x\n", crc32);
|
|
printf("\texpandedSize = %d\n", expandedSize);
|
|
printf("\tcompressedSize = %d\n", compressedSize);
|
|
printf("\tinternalAttributes = x%04x\n", internalAttributes);
|
|
printf("\texternalAttributes = x%08x\n", externalAttributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
class ZipArchive
|
|
{
|
|
ubyte[] data; // representing the entire Zip contents
|
|
uint endrecOffset;
|
|
|
|
uint diskNumber;
|
|
uint diskStartDir;
|
|
uint numEntries;
|
|
uint totalEntries;
|
|
char[] comment;
|
|
ArchiveMember[char[]] directory;
|
|
|
|
debug (print)
|
|
{
|
|
void print()
|
|
{
|
|
printf("\tdiskNumber = %u\n", diskNumber);
|
|
printf("\tdiskStartDir = %u\n", diskStartDir);
|
|
printf("\tnumEntries = %u\n", numEntries);
|
|
printf("\ttotalEntries = %u\n", totalEntries);
|
|
printf("\tcomment = '%.*s'\n", comment);
|
|
}
|
|
}
|
|
|
|
/* ============ Creating a new archive =================== */
|
|
|
|
/* Constructor to use when creating a new archive.
|
|
*/
|
|
|
|
this()
|
|
{
|
|
}
|
|
|
|
void addMember(ArchiveMember de)
|
|
{
|
|
directory[de.name] = de;
|
|
}
|
|
|
|
void deleteMember(ArchiveMember de)
|
|
{
|
|
delete directory[de.name];
|
|
}
|
|
|
|
void[] build()
|
|
{ uint i;
|
|
uint directoryOffset;
|
|
|
|
if (comment.length > 0xFFFF)
|
|
throw new ZipException("archive comment longer than 65535");
|
|
|
|
// Compress each member; compute size
|
|
uint archiveSize = 0;
|
|
uint directorySize = 0;
|
|
foreach (ArchiveMember de; directory)
|
|
{
|
|
de.expandedSize = de.expandedData.length;
|
|
switch (de.compressionMethod)
|
|
{
|
|
case 0:
|
|
de.compressedData = de.expandedData;
|
|
break;
|
|
|
|
case 8:
|
|
de.compressedData = cast(ubyte[])std.zlib.compress(cast(void[])de.expandedData);
|
|
de.compressedData = de.compressedData[2 .. de.compressedData.length - 4];
|
|
break;
|
|
|
|
default:
|
|
throw new ZipException("unsupported compression method");
|
|
}
|
|
de.compressedSize = de.compressedData.length;
|
|
de.crc32 = std.zlib.crc32(0, cast(void[])de.expandedData);
|
|
|
|
archiveSize += 30 + de.name.length +
|
|
de.extra.length +
|
|
de.compressedSize;
|
|
directorySize += 46 + de.name.length +
|
|
de.extra.length +
|
|
de.comment.length;
|
|
}
|
|
|
|
data = new ubyte[archiveSize + directorySize + 22 + comment.length];
|
|
|
|
// Populate the data[]
|
|
|
|
// Store each archive member
|
|
i = 0;
|
|
foreach (ArchiveMember de; directory)
|
|
{
|
|
de.offset = i;
|
|
data[i .. i + 4] = cast(ubyte[])"PK\x03\x04";
|
|
putUshort(i + 4, de.extractVersion);
|
|
putUshort(i + 6, de.flags);
|
|
putUshort(i + 8, de.compressionMethod);
|
|
putUint (i + 10, cast(uint)de.time);
|
|
putUint (i + 14, de.crc32);
|
|
putUint (i + 18, de.compressedSize);
|
|
putUint (i + 22, de.expandedData.length);
|
|
putUshort(i + 26, de.name.length);
|
|
putUshort(i + 28, de.extra.length);
|
|
i += 30;
|
|
|
|
data[i .. i + de.name.length] = cast(ubyte[])de.name[];
|
|
i += de.name.length;
|
|
data[i .. i + de.extra.length] = cast(ubyte[])de.extra[];
|
|
i += de.extra.length;
|
|
data[i .. i + de.compressedSize] = de.compressedData[];
|
|
i += de.compressedSize;
|
|
}
|
|
|
|
// Write directory
|
|
directoryOffset = i;
|
|
numEntries = 0;
|
|
foreach (ArchiveMember de; directory)
|
|
{
|
|
data[i .. i + 4] = cast(ubyte[])"PK\x01\x02";
|
|
putUshort(i + 4, de.madeVersion);
|
|
putUshort(i + 6, de.extractVersion);
|
|
putUshort(i + 8, de.flags);
|
|
putUshort(i + 10, de.compressionMethod);
|
|
putUint (i + 12, cast(uint)de.time);
|
|
putUint (i + 16, de.crc32);
|
|
putUint (i + 20, de.compressedSize);
|
|
putUint (i + 24, de.expandedSize);
|
|
putUshort(i + 28, de.name.length);
|
|
putUshort(i + 30, de.extra.length);
|
|
putUshort(i + 32, de.comment.length);
|
|
putUshort(i + 34, de.diskNumber);
|
|
putUshort(i + 36, de.internalAttributes);
|
|
putUint (i + 38, de.externalAttributes);
|
|
putUint (i + 42, de.offset);
|
|
i += 46;
|
|
|
|
data[i .. i + de.name.length] = cast(ubyte[])de.name[];
|
|
i += de.name.length;
|
|
data[i .. i + de.extra.length] = cast(ubyte[])de.extra[];
|
|
i += de.extra.length;
|
|
data[i .. i + de.comment.length] = cast(ubyte[])de.comment[];
|
|
i += de.comment.length;
|
|
numEntries++;
|
|
}
|
|
totalEntries = numEntries;
|
|
|
|
// Write end record
|
|
endrecOffset = i;
|
|
data[i .. i + 4] = cast(ubyte[])"PK\x05\x06";
|
|
putUshort(i + 4, diskNumber);
|
|
putUshort(i + 6, diskStartDir);
|
|
putUshort(i + 8, numEntries);
|
|
putUshort(i + 10, totalEntries);
|
|
putUint (i + 12, directorySize);
|
|
putUint (i + 16, directoryOffset);
|
|
putUshort(i + 20, comment.length);
|
|
i += 22;
|
|
|
|
// Write archive comment
|
|
assert(i + comment.length == data.length);
|
|
data[i .. data.length] = cast(ubyte[])comment[];
|
|
|
|
return cast(void[])data;
|
|
}
|
|
|
|
/* ============ Reading an existing archive =================== */
|
|
|
|
/* Constructor to use when reading an existing archive.
|
|
*/
|
|
|
|
this(void[] buffer)
|
|
{ int iend;
|
|
int i;
|
|
int endcommentlength;
|
|
uint directorySize;
|
|
uint directoryOffset;
|
|
|
|
this.data = cast(ubyte[]) buffer;
|
|
|
|
// Find 'end record index' by searching backwards for signature
|
|
iend = data.length - 66000;
|
|
if (iend < 0)
|
|
iend = 0;
|
|
for (i = data.length - 22; 1; i--)
|
|
{
|
|
if (i < iend)
|
|
throw new ZipException("no end record");
|
|
|
|
if (data[i .. i + 4] == cast(ubyte[])"PK\x05\x06")
|
|
{
|
|
endcommentlength = getUshort(i + 20);
|
|
if (i + 22 + endcommentlength > data.length)
|
|
continue;
|
|
comment = cast(char[])data[i + 22 .. i + 22 + endcommentlength];
|
|
endrecOffset = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Read end record data
|
|
diskNumber = getUshort(i + 4);
|
|
diskStartDir = getUshort(i + 6);
|
|
|
|
numEntries = getUshort(i + 8);
|
|
totalEntries = getUshort(i + 10);
|
|
|
|
if (numEntries != totalEntries)
|
|
throw new ZipException("multiple disk zips not supported");
|
|
|
|
directorySize = getUint(i + 12);
|
|
directoryOffset = getUint(i + 16);
|
|
|
|
if (directoryOffset + directorySize > i)
|
|
throw new ZipException("corrupted directory");
|
|
|
|
i = directoryOffset;
|
|
for (int n = 0; n < numEntries; n++)
|
|
{
|
|
/* The format of an entry is:
|
|
* 'PK' 1, 2
|
|
* directory info
|
|
* path
|
|
* extra data
|
|
* comment
|
|
*/
|
|
|
|
uint offset;
|
|
uint namelen;
|
|
uint extralen;
|
|
uint commentlen;
|
|
|
|
if (data[i .. i + 4] != cast(ubyte[])"PK\x01\x02")
|
|
throw new ZipException("invalid directory entry 1");
|
|
ArchiveMember de = new ArchiveMember();
|
|
de.madeVersion = getUshort(i + 4);
|
|
de.extractVersion = getUshort(i + 6);
|
|
de.flags = getUshort(i + 8);
|
|
de.compressionMethod = getUshort(i + 10);
|
|
de.time = cast(DosFileTime)getUint(i + 12);
|
|
de.crc32 = getUint(i + 16);
|
|
de.compressedSize = getUint(i + 20);
|
|
de.expandedSize = getUint(i + 24);
|
|
namelen = getUshort(i + 28);
|
|
extralen = getUshort(i + 30);
|
|
commentlen = getUshort(i + 32);
|
|
de.diskNumber = getUshort(i + 34);
|
|
de.internalAttributes = getUshort(i + 36);
|
|
de.externalAttributes = getUint(i + 38);
|
|
de.offset = getUint(i + 42);
|
|
i += 46;
|
|
|
|
if (i + namelen + extralen + commentlen > directoryOffset + directorySize)
|
|
throw new ZipException("invalid directory entry 2");
|
|
|
|
de.name = cast(char[])data[i .. i + namelen];
|
|
i += namelen;
|
|
de.extra = data[i .. i + extralen];
|
|
i += extralen;
|
|
de.comment = cast(char[])data[i .. i + commentlen];
|
|
i += commentlen;
|
|
|
|
directory[de.name] = de;
|
|
}
|
|
if (i != directoryOffset + directorySize)
|
|
throw new ZipException("invalid directory entry 3");
|
|
}
|
|
|
|
ubyte[] expand(ArchiveMember de)
|
|
{ uint namelen;
|
|
uint extralen;
|
|
|
|
if (data[de.offset .. de.offset + 4] != cast(ubyte[])"PK\x03\x04")
|
|
throw new ZipException("invalid directory entry 4");
|
|
|
|
// These values should match what is in the main zip archive directory
|
|
de.extractVersion = getUshort(de.offset + 4);
|
|
de.flags = getUshort(de.offset + 6);
|
|
de.compressionMethod = getUshort(de.offset + 8);
|
|
de.time = cast(DosFileTime)getUint(de.offset + 10);
|
|
de.crc32 = getUint(de.offset + 14);
|
|
de.compressedSize = getUint(de.offset + 18);
|
|
de.expandedSize = getUint(de.offset + 22);
|
|
namelen = getUshort(de.offset + 26);
|
|
extralen = getUshort(de.offset + 28);
|
|
|
|
debug(print)
|
|
{
|
|
printf("\t\texpandedSize = %d\n", de.expandedSize);
|
|
printf("\t\tcompressedSize = %d\n", de.compressedSize);
|
|
printf("\t\tnamelen = %d\n", namelen);
|
|
printf("\t\textralen = %d\n", extralen);
|
|
}
|
|
|
|
if (de.flags & 1)
|
|
throw new ZipException("encryption not supported");
|
|
|
|
int i;
|
|
i = de.offset + 30 + namelen + extralen;
|
|
if (i + de.compressedSize > endrecOffset)
|
|
throw new ZipException("invalid directory entry 5");
|
|
|
|
de.compressedData = data[i .. i + de.compressedSize];
|
|
debug(print) arrayPrint(de.compressedData);
|
|
|
|
switch (de.compressionMethod)
|
|
{
|
|
case 0:
|
|
de.expandedData = de.compressedData;
|
|
return de.expandedData;
|
|
|
|
case 8:
|
|
// -15 is a magic value used to decompress zip files.
|
|
// It has the effect of not requiring the 2 byte header
|
|
// and 4 byte trailer.
|
|
de.expandedData = cast(ubyte[])std.zlib.uncompress(cast(void[])de.compressedData, de.expandedSize, -15);
|
|
return de.expandedData;
|
|
|
|
default:
|
|
throw new ZipException("unsupported compression method");
|
|
}
|
|
}
|
|
|
|
/* ============ Utility =================== */
|
|
|
|
ushort getUshort(int i)
|
|
{
|
|
version (LittleEndian)
|
|
{
|
|
return *cast(ushort *)&data[i];
|
|
}
|
|
else
|
|
{
|
|
ubyte b0 = data[i];
|
|
ubyte b1 = data[i + 1];
|
|
return (b1 << 8) | b0;
|
|
}
|
|
}
|
|
|
|
uint getUint(int i)
|
|
{
|
|
version (LittleEndian)
|
|
{
|
|
return *cast(uint *)&data[i];
|
|
}
|
|
else
|
|
{
|
|
return bswap(*cast(uint *)&data[i]);
|
|
}
|
|
}
|
|
|
|
void putUshort(int i, ushort us)
|
|
{
|
|
version (LittleEndian)
|
|
{
|
|
*cast(ushort *)&data[i] = us;
|
|
}
|
|
else
|
|
{
|
|
data[0] = cast(ubyte)us;
|
|
data[1] = cast(ubyte)(us >> 8);
|
|
}
|
|
}
|
|
|
|
void putUint(int i, uint ui)
|
|
{
|
|
version (BigEndian)
|
|
{
|
|
ui = bswap(ui);
|
|
}
|
|
*cast(uint *)&data[i] = ui;
|
|
}
|
|
}
|
|
|
|
debug(print)
|
|
{
|
|
void arrayPrint(ubyte[] array)
|
|
{
|
|
printf("array %p,%d\n", cast(void*)array, array.length);
|
|
for (int i = 0; i < array.length; i++)
|
|
{
|
|
printf("%02x ", array[i]);
|
|
if (((i + 1) & 15) == 0)
|
|
printf("\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|