/// <summary> /// Reads the contents of the archive's Central Directory and returns an ArchiveInformation record with the /// CompressedSize and UncompressedSize fields populated from the contents of the Central Directory file header /// records. /// </summary> /// <param name="eocdRecord">The End of Central Directory record which gives us information about the Central Directory's location</param> /// <returns>The pre-populated ArchiveInformation record</returns> /// <exception cref="InvalidArchiveException"></exception> private ArchiveInformation ReadCentralDirectoryContents(ZipEndOfCentralDirectoryRecord eocdRecord) { // -- Get a new info record ArchiveInformation info = new ArchiveInformation(); info.UncompressedSize = 0; info.CompressedSize = 0; // -- Open the correct archive part and seek to the first Central Directory record Open(eocdRecord.CDDisk + 1); InputStream.Seek((long)eocdRecord.CDOffset, SeekOrigin.Begin); // -- Loop all entries for (int i = 0; i < eocdRecord.NumFilesInCD; i++) { ZipCentralDirectoryFileHeader cdHeader = new ZipCentralDirectoryFileHeader(); cdHeader.Signature = ReadULong(); if (cdHeader.Signature != BitConverter.ToUInt32(new byte[] { 0x50, 0x4b, 0x01, 0x02 }, 0)) { throw new InvalidArchiveException(String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_CD_HEADER_AT_POSITION"), CurrentPartNumber, InputStream.Position - 4)); } cdHeader.VersionMadeBy = ReadUShort(); cdHeader.VersionToExtract = ReadUShort(); cdHeader.Flags = ReadUShort(); cdHeader.CompressionMethod = ReadUShort(); cdHeader.LastModTime = ReadUShort(); cdHeader.LastModDate = ReadUShort(); cdHeader.CRC32 = ReadULong(); cdHeader.CompressedSize = ReadULong(); cdHeader.UncompressedSize = ReadULong(); cdHeader.FileNameLength = ReadUShort(); cdHeader.ExtraFieldLength = ReadUShort(); cdHeader.FileCommentLength = ReadUShort(); cdHeader.DiskNumberStart = ReadUShort(); cdHeader.InternalFileAttributes = ReadUShort(); cdHeader.ExternalFileAttributes = ReadULong(); cdHeader.RelativeOffset = ReadULong(); cdHeader.Filename = ReadUtf8String(cdHeader.FileNameLength); cdHeader.Comment = ""; if (cdHeader.FileCommentLength > 0) { cdHeader.Comment = ReadUtf8String(cdHeader.FileCommentLength); } info.CompressedSize += cdHeader.CompressedSize; info.UncompressedSize += cdHeader.UncompressedSize; } return(info); }
// すべてのファイルの書き込みが完了したのでセントラルディレクトリヘッダ等を集中的に書き出す protected override async Task FinishAsyncImpl(CancellationToken cancel = default) { checked { long sizeOfCentralDirectory = this.FooterBuffer.LongLength; long offsetOfCentralDirectory = this.Writer.CurrentPosition; // セントラルディレクトリヘッダ (一時バッファに溜めていたものを今こそ全部一気に書き出す) this.FooterBuffer.Seek(0, SeekOrigin.Begin); await this.FooterBuffer.CopyToSequentialWritableAsync(this.Writer, cancel); var memory = Sync(() => { checked { bool useZip64 = false; // ZIP64 形式のヘッダが必要かどうか判定する if (NumTotalFiles > ushort.MaxValue) { useZip64 = true; } if (sizeOfCentralDirectory > uint.MaxValue) { useZip64 = true; } if (offsetOfCentralDirectory > uint.MaxValue) { useZip64 = true; } Packet p = new Packet(); long offsetStartZip64CentralDirectoryRecord = this.Writer.CurrentPosition; if (useZip64) { // ZIP64 エンドオブセントラルディレクトリレコードの追記 ref Zip64EndOfCentralDirectoryRecord endZip64Record = ref p.AppendSpan <Zip64EndOfCentralDirectoryRecord>(); endZip64Record.Signature = ZipConsts.Zip64EndOfCentralDirectorySignature._LE_Endian32_U(); endZip64Record.SizeOfZip64EndOfCentralDirectoryRecord = ((ulong)(Util.SizeOfStruct <Zip64EndOfCentralDirectoryRecord>() - 12))._LE_Endian64_U(); endZip64Record.MadeVersion = ZipFileVersion.Ver4_5; endZip64Record.MadeFileSystemType = ZipFileSystemType.Ntfs; endZip64Record.NeedVersion = ZipFileVersion.Ver2_0; endZip64Record.Reserved = 0; endZip64Record.NumberOfThisDisk = 0; endZip64Record.DiskNumberStart = 0; endZip64Record.TotalNumberOfCentralDirectory = ((ulong)NumTotalFiles)._LE_Endian64_U(); endZip64Record.TotalNumberOfEntriesOnCentralDirectory = ((ulong)NumTotalFiles)._LE_Endian64_U(); endZip64Record.SizeOfCentralDirectory = ((ulong)sizeOfCentralDirectory)._LE_Endian64_U(); endZip64Record.OffsetStartCentralDirectory = ((ulong)offsetOfCentralDirectory)._LE_Endian64_U(); // ZIP64 エンドオブセントラルディレクトリロケータの追記 ref Zip64EndOfCentralDirectoryLocator zip64Locator = ref p.AppendSpan <Zip64EndOfCentralDirectoryLocator>(); zip64Locator.Signature = ZipConsts.Zip64EndOfCentralDirectoryLocatorSignature; zip64Locator.NumberOfThisDisk = 0; zip64Locator.OffsetStartZip64CentralDirectoryRecord = offsetStartZip64CentralDirectoryRecord._LE_Endian64_U(); zip64Locator.TotalNumberOfDisk = 1._LE_Endian32_U(); } // エンドオブセントラルディレクトリレコード ref ZipEndOfCentralDirectoryRecord endRecord = ref p.AppendSpan <ZipEndOfCentralDirectoryRecord>(); endRecord.Signature = ZipConsts.EndOfCentralDirectorySignature._LE_Endian32_U(); if (useZip64 == false) { endRecord.NumberOfThisDisk = 0; endRecord.NumberOfCentralDirectoryOnThisDisk = NumTotalFiles._LE_Endian16_U(); endRecord.TotalNumberOfCentralDirectory = NumTotalFiles._LE_Endian16_U(); endRecord.SizeOfCentralDirectory = ((uint)sizeOfCentralDirectory)._LE_Endian32_U(); endRecord.OffsetStartCentralDirectory = ((uint)offsetOfCentralDirectory)._LE_Endian32_U(); } else { endRecord.NumberOfThisDisk = 0xFFFF; endRecord.NumberOfCentralDirectoryOnThisDisk = 0xFFFF; endRecord.TotalNumberOfCentralDirectory = 0xFFFF; endRecord.SizeOfCentralDirectory = 0xFFFFFFFF; endRecord.OffsetStartCentralDirectory = 0xFFFFFFFF; } endRecord.CommentLength = 0; return(p.ToMemory()); } });
/// <summary> /// Locates and reads the End of Central Directory record. It also reads through the entire central directory. /// This must be called at the beginning of extraction. /// </summary> /// <returns>The EOCD record of the archive</returns> /// <exception cref="InvalidArchiveException"></exception> private ZipEndOfCentralDirectoryRecord ReadEndOfCentralDirectory() { Open(Parts); long localOffset = InputStream.Length - 22; /** * The EOCD record is 22 to infinity bytes long. Its first 22 bytes are a pre-defined data record, whereas * the rest are the ZIP file comment. In order to determine its location relative to the archive's EOF I * chose to implement an inneficient backwards sliding window algorithm. We start by reading the last 22 * bytes of the archive. If the header is not found, we keep sliding backwards, one byte at a time until * we either locate the header or reach the BOF. The latter case means we don't have a valid archive. This * shouldn't happen, unless the archive was truncated in transit. */ try { do { InputStream.Seek(localOffset, SeekOrigin.Begin); byte[] buffer = ReadBytes(4); if (isEOCD(buffer)) { break; } localOffset--; } while (localOffset > 0); } catch (Exception) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_ZIP_EOCD_NOT_FOUND")); } // EOCD not found within the last part. That's a violation of the ZIP standard. if (localOffset < 0) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_ZIP_EOCD_NOT_FOUND")); } // Go back to the EOCD offset and let's read the contents ZipEndOfCentralDirectoryRecord eocdRecord = new ZipEndOfCentralDirectoryRecord(); InputStream.Seek(localOffset, SeekOrigin.Begin); eocdRecord.Signature = ReadULong(); eocdRecord.DiskNumber = ReadUShort(); eocdRecord.CDDisk = ReadUShort(); eocdRecord.DiskCDEntries = ReadUShort(); eocdRecord.NumFilesInCD = ReadUShort(); eocdRecord.CDLength = ReadULong(); eocdRecord.CDOffset = ReadULong(); eocdRecord.CommentLength = ReadUShort(); eocdRecord.Comment = ""; if (eocdRecord.CommentLength > 0) { eocdRecord.Comment = ReadUtf8String(eocdRecord.CommentLength); } // Now we can go to the beginning of the Central Directory and read its contents. We need to do that to get // the comrpessed and uncompressed size counts. var info = ReadCentralDirectoryContents(eocdRecord); // Invoke the archiveInformation event. We need to do some work to get there, through... // -- Get the total archive size by looping all of its parts info.ArchiveSize = 0; for (int i = 1; i <= Parts; i++) { FileInfo fi = new FileInfo(ArchivePath); info.ArchiveSize += (ulong)fi.Length; } eocdRecord.TotalSize = info.ArchiveSize; // -- Incorporate bits from the file header info.FileCount = eocdRecord.NumFilesInCD; // -- Create the event arguments object ArchiveInformationEventArgs args = new ArchiveInformationEventArgs(info); // -- Finally, invoke the event OnArchiveInformationEvent(args); return(eocdRecord); }