/// <summary>
/// Search for and read the central directory of a zip file filling the entries array.
/// </summary>
/// <exception cref="System.IO.IOException">
/// An i/o error occurs.
/// </exception>
/// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
/// The central directory is malformed or cannot be found
/// </exception>
void ReadEntries()
{
// Search for the End Of Central Directory. When a zip comment is
// present the directory will start earlier
//
// The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
// This should be compatible with both SFX and ZIP files but has only been tested for Zip files
// If a SFX file has the Zip data attached as a resource and there are other resources occuring later then
// this could be invalid.
// Could also speed this up by reading memory in larger blocks.
if (baseStream_.CanSeek == false) {
throw new ZipException("ZipFile stream must be seekable");
}
long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
if (locatedEndOfCentralDir < 0) {
throw new ZipException("Cannot find central directory");
}
// Read end of central directory record
ushort thisDiskNumber = ReadLEUshort();
ushort startCentralDirDisk = ReadLEUshort();
ulong entriesForThisDisk = ReadLEUshort();
ulong entriesForWholeCentralDir = ReadLEUshort();
ulong centralDirSize = ReadLEUint();
long offsetOfCentralDir = ReadLEUint();
uint commentSize = ReadLEUshort();
if ( commentSize > 0 ) {
byte[] comment = new byte[commentSize];
StreamUtils.ReadFully(baseStream_, comment);
comment_ = ZipConstants.ConvertToString(comment);
}
else {
comment_ = string.Empty;
}
bool isZip64 = false;
// Check if zip64 header information is required.
if ( (thisDiskNumber == 0xffff) ||
(startCentralDirDisk == 0xffff) ||
(entriesForThisDisk == 0xffff) ||
(entriesForWholeCentralDir == 0xffff) ||
(centralDirSize == 0xffffffff) ||
(offsetOfCentralDir == 0xffffffff) ) {
isZip64 = true;
long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, 0x1000);
if ( offset < 0 ) {
throw new ZipException("Cannot find Zip64 locator");
}
// number of the disk with the start of the zip64 end of central directory 4 bytes
// relative offset of the zip64 end of central directory record 8 bytes
// total number of disks 4 bytes
ReadLEUint(); // startDisk64 is not currently used
ulong offset64 = ReadLEUlong();
uint totalDisks = ReadLEUint();
baseStream_.Position = (long)offset64;
long sig64 = ReadLEUint();
if ( sig64 != ZipConstants.Zip64CentralFileHeaderSignature ) {
throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
}
// NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
ulong recordSize = ReadLEUlong();
int versionMadeBy = ReadLEUshort();
int versionToExtract = ReadLEUshort();
uint thisDisk = ReadLEUint();
uint centralDirDisk = ReadLEUint();
entriesForThisDisk = ReadLEUlong();
entriesForWholeCentralDir = ReadLEUlong();
centralDirSize = ReadLEUlong();
offsetOfCentralDir = (long)ReadLEUlong();
// NOTE: zip64 extensible data sector (variable size) is ignored.
}
entries_ = new ZipEntry[entriesForThisDisk];
// SFX/embedded support, find the offset of the first entry vis the start of the stream
// This applies to Zip files that are appended to the end of an SFX stub.
// Or are appended as a resource to an executable.
// Zip files created by some archivers have the offsets altered to reflect the true offsets
// and so dont require any adjustment here...
// TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths?
if ( !isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize)) ) {
offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir);
if (offsetOfFirstEntry <= 0) {
throw new ZipException("Invalid embedded zip archive");
}
}
baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
for (ulong i = 0; i < entriesForThisDisk; i++) {
if (ReadLEUint() != ZipConstants.CentralHeaderSignature) {
throw new ZipException("Wrong Central Directory signature");
}
int versionMadeBy = ReadLEUshort();
int versionToExtract = ReadLEUshort();
int bitFlags = ReadLEUshort();
int method = ReadLEUshort();
uint dostime = ReadLEUint();
uint crc = ReadLEUint();
long csize = (long)ReadLEUint();
long size = (long)ReadLEUint();
int nameLen = ReadLEUshort();
int extraLen = ReadLEUshort();
int commentLen = ReadLEUshort();
int diskStartNo = ReadLEUshort(); // Not currently used
int internalAttributes = ReadLEUshort(); // Not currently used
uint externalAttributes = ReadLEUint();
long offset = ReadLEUint();
byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen);
string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen);
ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method);
entry.Crc = crc & 0xffffffffL;
entry.Size = size & 0xffffffffL;
entry.CompressedSize = csize & 0xffffffffL;
entry.Flags = bitFlags;
entry.DosTime = (uint)dostime;
entry.ZipFileIndex = (long)i;
entry.Offset = offset;
entry.ExternalFileAttributes = (int)externalAttributes;
if ((bitFlags & 8) == 0) {
entry.CryptoCheckValue = (byte)(crc >> 24);
}
else {
entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
}
if (extraLen > 0) {
byte[] extra = new byte[extraLen];
StreamUtils.ReadFully(baseStream_, extra);
entry.ExtraData = extra;
}
entry.ProcessExtraData(false);
if (commentLen > 0) {
StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen);
entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen);
}
entries_[i] = entry;
}
}