public void Write64(Stream stream) { if (!this.zip64) { throw new InvalidOperationException("Can only write 64-bit headers for 64-bit archives."); } Zip64EndOfCentralDirectoryLocator eocdl = new Zip64EndOfCentralDirectoryLocator(stream.Position); BinaryWriter writer = new BinaryWriter(stream); writer.Write(EOCD64SIG); writer.Write((long)EOCD64_RECORD_FIXEDSIZE); writer.Write(this.versionMadeBy); writer.Write(this.versionNeeded); writer.Write(this.diskNumber); writer.Write(this.dirStartDiskNumber); writer.Write(this.entriesOnDisk); writer.Write(this.totalEntries); writer.Write(this.dirSize); writer.Write(this.dirOffset); eocdl.Write(stream); }
/// <summary> /// Creates a zip archive or chain of zip archives. /// </summary> /// <param name="streamContext">A context interface to handle opening /// and closing of archive and file streams.</param> /// <param name="files">An array of file lists. Each list is /// compressed into one stream in the archive.</param> /// <param name="maxArchiveSize">The maximum number of bytes for one archive /// before the contents are chained to the next archive, or zero for unlimited /// archive size.</param> /// <exception cref="ArchiveException">The archive could not be /// created.</exception> /// <remarks> /// The stream context implementation may provide a mapping from the file /// paths within the archive to the external file paths. /// </remarks> public override void Pack( IPackStreamContext streamContext, IEnumerable<string> files, long maxArchiveSize) { if (streamContext == null) { throw new ArgumentNullException("streamContext"); } if (files == null) { throw new ArgumentNullException("files"); } lock (this) { Stream archiveStream = null; try { this.ResetProgressData(); this.totalArchives = 1; object forceZip64Value = streamContext.GetOption("forceZip64", null); bool forceZip64 = Convert.ToBoolean( forceZip64Value, CultureInfo.InvariantCulture); // Count the total number of files and bytes to be compressed. foreach (string file in files) { FileAttributes attributes; DateTime lastWriteTime; Stream fileStream = streamContext.OpenFileReadStream( file, out attributes, out lastWriteTime); if (fileStream != null) { this.totalFileBytes += fileStream.Length; this.totalFiles++; streamContext.CloseFileReadStream(file, fileStream); } } List<ZipFileHeader> fileHeaders = new List<ZipFileHeader>(); this.currentFileNumber = -1; if (this.currentArchiveName == null) { this.mainArchiveName = streamContext.GetArchiveName(0); this.currentArchiveName = this.mainArchiveName; if (string.IsNullOrWhiteSpace(this.currentArchiveName)) { throw new FileNotFoundException("No name provided for archive."); } } this.OnProgress(ArchiveProgressType.StartArchive); // Compress files one by one, saving header info for each. foreach (string file in files) { ZipFileHeader fileHeader = this.PackOneFile( streamContext, file, maxArchiveSize, forceZip64, ref archiveStream); if (fileHeader != null) { fileHeaders.Add(fileHeader); } this.currentArchiveTotalBytes = (archiveStream != null ? archiveStream.Position : 0); this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; } bool zip64 = forceZip64 || this.totalFiles > UInt16.MaxValue; // Write the central directory composed of all the file headers. uint centralDirStartArchiveNumber = 0; long centralDirStartPosition = 0; long centralDirSize = 0; for (int i = 0; i < fileHeaders.Count; i++) { ZipFileHeader fileHeader = fileHeaders[i]; int headerSize = fileHeader.GetSize(true); centralDirSize += headerSize; this.CheckArchiveWriteStream( streamContext, maxArchiveSize, headerSize, ref archiveStream); if (i == 0) { centralDirStartArchiveNumber = (uint) this.currentArchiveNumber; centralDirStartPosition = archiveStream.Position; } fileHeader.Write(archiveStream, true); if (fileHeader.zip64) { zip64 = true; } } this.currentArchiveTotalBytes = (archiveStream != null ? archiveStream.Position : 0); this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory(); eocd.dirStartDiskNumber = centralDirStartArchiveNumber; eocd.entriesOnDisk = fileHeaders.Count; eocd.totalEntries = fileHeaders.Count; eocd.dirSize = centralDirSize; eocd.dirOffset = centralDirStartPosition; eocd.comment = this.comment; Zip64EndOfCentralDirectoryLocator eocdl = new Zip64EndOfCentralDirectoryLocator(); int maxFooterSize = eocd.GetSize(false); if (archiveStream != null && (zip64 || archiveStream.Position > ((long) UInt32.MaxValue) - eocd.GetSize(false))) { maxFooterSize += eocd.GetSize(true) + (int) Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE; zip64 = true; } this.CheckArchiveWriteStream( streamContext, maxArchiveSize, maxFooterSize, ref archiveStream); eocd.diskNumber = (uint) this.currentArchiveNumber; if (zip64) { eocd.versionMadeBy = 45; eocd.versionNeeded = 45; eocd.zip64 = true; eocdl.dirOffset = archiveStream.Position; eocdl.dirStartDiskNumber = (uint) this.currentArchiveNumber; eocdl.totalDisks = (uint) this.currentArchiveNumber + 1; eocd.Write(archiveStream); eocdl.Write(archiveStream); eocd.dirOffset = UInt32.MaxValue; eocd.dirStartDiskNumber = UInt16.MaxValue; } eocd.zip64 = false; eocd.Write(archiveStream); this.currentArchiveTotalBytes = archiveStream.Position; this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes; } finally { if (archiveStream != null) { streamContext.CloseArchiveWriteStream( this.currentArchiveNumber, this.mainArchiveName, archiveStream); this.OnProgress(ArchiveProgressType.FinishArchive); } } } }
private ZipEndOfCentralDirectory GetEOCD( IUnpackStreamContext streamContext, Stream archiveStream) { BinaryReader reader = new BinaryReader(archiveStream); long offset = archiveStream.Length - ZipEndOfCentralDirectory.EOCD_RECORD_FIXEDSIZE; while (offset >= 0) { archiveStream.Seek(offset, SeekOrigin.Begin); uint sig = reader.ReadUInt32(); if (sig == ZipEndOfCentralDirectory.EOCDSIG) { break; } offset--; } if (offset < 0) { return null; } ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory(); archiveStream.Seek(offset, SeekOrigin.Begin); if (!eocd.Read(archiveStream)) { throw new ZipException("Invalid end of central directory record"); } if (eocd.dirOffset == (long) UInt32.MaxValue) { string saveComment = eocd.comment; archiveStream.Seek( offset - Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE, SeekOrigin.Begin); Zip64EndOfCentralDirectoryLocator eocdl = new Zip64EndOfCentralDirectoryLocator(); if (!eocdl.Read(archiveStream)) { throw new ZipException("Missing or invalid end of " + "central directory record locator"); } if (eocdl.dirStartDiskNumber == eocdl.totalDisks - 1) { // ZIP64 eocd is entirely in current stream. archiveStream.Seek(eocdl.dirOffset, SeekOrigin.Begin); if (!eocd.Read(archiveStream)) { throw new ZipException("Missing or invalid ZIP64 end of " + "central directory record"); } } else if (streamContext == null) { return null; } else { // TODO: handle EOCD64 spanning archives! throw new NotImplementedException("Zip implementation does not " + "handle end of central directory record that spans archives."); } eocd.comment = saveComment; } return eocd; }
public ZipEndOfCentralDirectory(Stream stream) { BinaryReader reader = new BinaryReader(stream); // Start searching for the Zip End of Central Directory signature from the end // of the file backwards (towards the beginning of the file). This allows us to // find Zip archives with extra data appended to the archive (for whatever reason // someone might want to do that). long offset = stream.Length - ZipEndOfCentralDirectory.EOCD_RECORD_FIXEDSIZE; for (; offset >= 0; --offset) { stream.Seek(offset, SeekOrigin.Begin); uint sig = reader.ReadUInt32(); if (sig == ZipEndOfCentralDirectory.EOCDSIG) { break; } } if (offset < 0) { throw new InvalidDataException("Failed to find end of central directory record."); } stream.Seek(offset, SeekOrigin.Begin); this.Read(stream); // If the offset to the central directory is DWORD_MAX then this must be a 64-bit // archive. if (this.dirOffset == (long)UInt32.MaxValue) { string saveComment = this.comment; stream.Seek(offset - Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE, SeekOrigin.Begin); Zip64EndOfCentralDirectoryLocator eocdl = new Zip64EndOfCentralDirectoryLocator(stream); if (eocdl.dirStartDiskNumber == eocdl.totalDisks - 1) { // ZIP64 eocd is entirely in current stream. stream.Seek(eocdl.dirOffset, SeekOrigin.Begin); this.Read64(stream); } else { // TODO: handle EOCD64 spanning archives! throw new NotImplementedException("Zip implementation does not handle end of central directory record that spans archives."); } this.comment = saveComment; } // Read the central directory for the archive. stream.Seek(this.dirOffset, SeekOrigin.Begin); while (this.headers.Count < this.totalEntries) { ZipFileHeader header = new ZipFileHeader(true); if (!header.Read(stream)) { throw new InvalidDataException("Missing or invalid central directory file header"); } this.headers.Add(header); if (this.headers.Count < this.totalEntries && stream.Position == stream.Length) { //streamContext.CloseArchiveReadStream(this.currentArchiveNumber, String.Empty, archiveStream); //this.currentArchiveNumber++; //archiveStream = streamContext.OpenArchiveReadStream(this.currentArchiveNumber, String.Empty, this); //if (archiveStream == null) //{ // this.currentArchiveNumber = 0; // archiveStream = streamContext.OpenArchiveReadStream(this.currentArchiveNumber, String.Empty, this); //} } } }
// すべてのファイルの書き込みが完了したのでセントラルディレクトリヘッダ等を集中的に書き出す 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()); } });