From 3f583916719eeef598d10a5d4e14ef14f008243b Mon Sep 17 00:00:00 2001 From: Rob Mensching Date: Tue, 11 May 2021 07:36:37 -0700 Subject: Merge Dtf --- .../WixToolset.Dtf.Compression.Zip/ZipEngine.cs | 478 +++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs (limited to 'src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs') diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs new file mode 100644 index 00000000..36b4db89 --- /dev/null +++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs @@ -0,0 +1,478 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Dtf.Compression.Zip +{ + using System; + using System.IO; + using System.IO.Compression; + using System.Collections.Generic; + using System.Reflection; + using System.Diagnostics.CodeAnalysis; + + /// + /// Engine capable of packing and unpacking archives in the zip format. + /// + public partial class ZipEngine : CompressionEngine + { + private static Dictionary> + compressionStreamCreators; + private static Dictionary> + decompressionStreamCreators; + + private static void InitCompressionStreamCreators() + { + if (ZipEngine.compressionStreamCreators == null) + { + ZipEngine.compressionStreamCreators = new + Dictionary>(); + ZipEngine.decompressionStreamCreators = new + Dictionary>(); + + ZipEngine.RegisterCompressionStreamCreator( + ZipCompressionMethod.Store, + CompressionMode.Compress, + delegate(Stream stream) { + return stream; + }); + ZipEngine.RegisterCompressionStreamCreator( + ZipCompressionMethod.Deflate, + CompressionMode.Compress, + delegate(Stream stream) { + return new DeflateStream(stream, CompressionMode.Compress, true); + }); + ZipEngine.RegisterCompressionStreamCreator( + ZipCompressionMethod.Store, + CompressionMode.Decompress, + delegate(Stream stream) { + return stream; + }); + ZipEngine.RegisterCompressionStreamCreator( + ZipCompressionMethod.Deflate, + CompressionMode.Decompress, + delegate(Stream stream) { + return new DeflateStream(stream, CompressionMode.Decompress, true); + }); + } + } + + /// + /// Registers a delegate that can create a warpper stream for + /// compressing or uncompressing the data of a source stream. + /// + /// Compression method being registered. + /// Indicates registration for ether + /// compress or decompress mode. + /// Delegate being registered. + /// + /// For compression, the delegate accepts a stream that writes to the archive + /// and returns a wrapper stream that compresses bytes as they are written. + /// For decompression, the delegate accepts a stream that reads from the archive + /// and returns a wrapper stream that decompresses bytes as they are read. + /// This wrapper stream model follows the design used by + /// System.IO.Compression.DeflateStream, and indeed that class is used + /// to implement the Deflate compression method by default. + /// To unregister a delegate, call this method again and pass + /// null for the delegate parameter. + /// + /// + /// When the ZipEngine class is initialized, the Deflate compression method + /// is automatically registered like this: + /// + /// ZipEngine.RegisterCompressionStreamCreator( + /// ZipCompressionMethod.Deflate, + /// CompressionMode.Compress, + /// delegate(Stream stream) { + /// return new DeflateStream(stream, CompressionMode.Compress, true); + /// }); + /// ZipEngine.RegisterCompressionStreamCreator( + /// ZipCompressionMethod.Deflate, + /// CompressionMode.Decompress, + /// delegate(Stream stream) { + /// return new DeflateStream(stream, CompressionMode.Decompress, true); + /// }); + /// + public static void RegisterCompressionStreamCreator( + ZipCompressionMethod compressionMethod, + CompressionMode compressionMode, + Converter creator) + { + ZipEngine.InitCompressionStreamCreators(); + if (compressionMode == CompressionMode.Compress) + { + ZipEngine.compressionStreamCreators[compressionMethod] = creator; + } + else + { + ZipEngine.decompressionStreamCreators[compressionMethod] = creator; + } + } + + // Progress data + private string currentFileName; + private int currentFileNumber; + private int totalFiles; + private long currentFileBytesProcessed; + private long currentFileTotalBytes; + private string mainArchiveName; + private string currentArchiveName; + private short currentArchiveNumber; + private short totalArchives; + private long currentArchiveBytesProcessed; + private long currentArchiveTotalBytes; + private long fileBytesProcessed; + private long totalFileBytes; + private string comment; + + /// + /// Creates a new instance of the zip engine. + /// + public ZipEngine() + : base() + { + ZipEngine.InitCompressionStreamCreators(); + } + + /// + /// Gets the comment from the last-examined archive, + /// or sets the comment to be added to any created archives. + /// + public string ArchiveComment + { + get + { + return this.comment; + } + set + { + this.comment = value; + } + } + + /// + /// Checks whether a Stream begins with a header that indicates + /// it is a valid archive file. + /// + /// Stream for reading the archive file. + /// True if the stream is a valid zip archive + /// (with no offset); false otherwise. + public override bool IsArchive(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (stream.Length - stream.Position < 4) + { + return false; + } + + BinaryReader reader = new BinaryReader(stream); + uint sig = reader.ReadUInt32(); + switch (sig) + { + case ZipFileHeader.LFHSIG: + case ZipEndOfCentralDirectory.EOCDSIG: + case ZipEndOfCentralDirectory.EOCD64SIG: + case ZipFileHeader.SPANSIG: + case ZipFileHeader.SPANSIG2: + return true; + default: + return false; + } + } + + /// + /// Gets the offset of an archive that is positioned 0 or more bytes + /// from the start of the Stream. + /// + /// A stream for reading the archive. + /// The offset in bytes of the archive, + /// or -1 if no archive is found in the Stream. + /// The archive must begin on a 4-byte boundary. + public override long FindArchiveOffset(Stream stream) + { + long offset = base.FindArchiveOffset(stream); + if (offset > 0) + { + // Some self-extract packages include the exe stub in file offset calculations. + // Check the first header directory offset to decide whether the entire + // archive needs to be offset or not. + + ZipEndOfCentralDirectory eocd = this.GetEOCD(null, stream); + if (eocd != null && eocd.totalEntries > 0) + { + stream.Seek(eocd.dirOffset, SeekOrigin.Begin); + + ZipFileHeader header = new ZipFileHeader(); + if (header.Read(stream, true) && header.localHeaderOffset < stream.Length) + { + stream.Seek(header.localHeaderOffset, SeekOrigin.Begin); + if (header.Read(stream, false)) + { + return 0; + } + } + } + } + + return offset; + } + + /// + /// Gets information about files in a zip archive or archive chain. + /// + /// A context interface to handle opening + /// and closing of archive and file streams. + /// A predicate that can determine + /// which files to process, optional. + /// Information about files in the archive stream. + /// The archive provided + /// by the stream context is not valid. + /// + /// The predicate takes an internal file + /// path and returns true to include the file or false to exclude it. + /// + public override IList GetFileInfo( + IUnpackStreamContext streamContext, + Predicate fileFilter) + { + if (streamContext == null) + { + throw new ArgumentNullException("streamContext"); + } + + lock (this) + { + IList headers = this.GetCentralDirectory(streamContext); + if (headers == null) + { + throw new ZipException("Zip central directory not found."); + } + + List files = new List(headers.Count); + foreach (ZipFileHeader header in headers) + { + if (!header.IsDirectory && + (fileFilter == null || fileFilter(header.fileName))) + { + files.Add(header.ToZipFileInfo()); + } + } + + return files.AsReadOnly(); + } + } + + /// + /// Reads all the file headers from the central directory in the main archive. + /// + private IList GetCentralDirectory(IUnpackStreamContext streamContext) + { + Stream archiveStream = null; + this.currentArchiveNumber = 0; + try + { + List headers = new List(); + archiveStream = this.OpenArchive(streamContext, 0); + + ZipEndOfCentralDirectory eocd = this.GetEOCD(streamContext, archiveStream); + if (eocd == null) + { + return null; + } + else if (eocd.totalEntries == 0) + { + return headers; + } + + headers.Capacity = (int) eocd.totalEntries; + + if (eocd.dirOffset > archiveStream.Length - ZipFileHeader.CFH_FIXEDSIZE) + { + streamContext.CloseArchiveReadStream( + this.currentArchiveNumber, String.Empty, archiveStream); + archiveStream = null; + } + else + { + archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin); + uint sig = new BinaryReader(archiveStream).ReadUInt32(); + if (sig != ZipFileHeader.CFHSIG) + { + streamContext.CloseArchiveReadStream( + this.currentArchiveNumber, String.Empty, archiveStream); + archiveStream = null; + } + } + + if (archiveStream == null) + { + this.currentArchiveNumber = (short) (eocd.dirStartDiskNumber + 1); + archiveStream = streamContext.OpenArchiveReadStream( + this.currentArchiveNumber, String.Empty, this); + + if (archiveStream == null) + { + return null; + } + } + + archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin); + + while (headers.Count < eocd.totalEntries) + { + ZipFileHeader header = new ZipFileHeader(); + if (!header.Read(archiveStream, true)) + { + throw new ZipException( + "Missing or invalid central directory file header"); + } + + headers.Add(header); + + if (headers.Count < eocd.totalEntries && + archiveStream.Position == archiveStream.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); + } + } + } + + return headers; + } + finally + { + if (archiveStream != null) + { + streamContext.CloseArchiveReadStream( + this.currentArchiveNumber, String.Empty, archiveStream); + } + } + } + + /// + /// Locates and reads the end of central directory record near the + /// end of the archive. + /// + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "streamContext")] + 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; + } + + private void ResetProgressData() + { + this.currentFileName = null; + this.currentFileNumber = 0; + this.totalFiles = 0; + this.currentFileBytesProcessed = 0; + this.currentFileTotalBytes = 0; + this.currentArchiveName = null; + this.currentArchiveNumber = 0; + this.totalArchives = 0; + this.currentArchiveBytesProcessed = 0; + this.currentArchiveTotalBytes = 0; + this.fileBytesProcessed = 0; + this.totalFileBytes = 0; + } + + private void OnProgress(ArchiveProgressType progressType) + { + ArchiveProgressEventArgs e = new ArchiveProgressEventArgs( + progressType, + this.currentFileName, + this.currentFileNumber >= 0 ? this.currentFileNumber : 0, + this.totalFiles, + this.currentFileBytesProcessed, + this.currentFileTotalBytes, + this.currentArchiveName, + this.currentArchiveNumber, + this.totalArchives, + this.currentArchiveBytesProcessed, + this.currentArchiveTotalBytes, + this.fileBytesProcessed, + this.totalFileBytes); + this.OnProgress(e); + } + } +} -- cgit v1.2.3-55-g6feb