using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace ScreenConnect; public class ZipFile : IStreamSave { private class ZipEntry : IStreamSaveLength, IStreamSave { public string Path; public uint Crc32; public uint CompressedSize; public uint UncompressedSize; public ushort CompressionMethod; public ushort VersionMadeBy; public ushort VersionNeededToExtract; public uint ExternalFileAttributes; public ushort LastModificationTime; public ushort LastModificationDate; public ushort DiskNumberWhereFileStarts; public ushort InternalFileAttributes; public Stream SeekableStream; public long SeekableStreamLocalFileOffset; public ArraySegment CompressedData; public void Save(Stream stream) { Save(stream, forceRaw: false); } public void Save(Stream stream, bool forceRaw) { Stream stream2 = null; if (SeekableStream != null) { LocalFileRecord structure = default(LocalFileRecord); SeekableStream.Seek(SeekableStreamLocalFileOffset, SeekOrigin.Begin); SeekableStream.ReadStructure(ref structure); SeekableStream.Seek(structure.PathLength, SeekOrigin.Current); SeekableStream.Seek(structure.ExtraFieldLength, SeekOrigin.Current); stream2 = new PartialStreamWrapper(SeekableStream, CompressedSize); } else { stream2 = CompressedData.ToMemoryStream(); } if (forceRaw || CompressionMethod != 8) { stream2.WriteTo(stream, 1048576L); return; } using DeflateDecoder coder = new DeflateDecoder(DeflateWrapper.None); using CoderStream coderStream = CoderStream.Create(stream2, coder, encodeOrDecode: false); coderStream.WriteTo(stream, 1048576L); } public long GetSaveLength() { return UncompressedSize; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] private struct EndOfCentralDirectoryRecord { public uint RecordSignature; public ushort u1; public ushort u2; public ushort CentralDirectoryRecordCountOnThisDisk; public ushort CentralDirectoryRecordCount; public int CentralDirectoryByteSize; public int CentralDirectoryFileOffset; public ushort CommentLength; } [StructLayout(LayoutKind.Sequential, Pack = 1)] private struct CentralDirectoryFileRecord { public uint RecordSignature; public ushort VersionMadeBy; public ushort VersionNeededToExtract; public ushort GeneralPurposeBitField; public ushort CompressionMethod; public ushort LastModificationTime; public ushort LastModificationDate; public uint Crc32; public uint CompressedSize; public uint UncompressedSize; public ushort PathLength; public ushort ExtraFieldLength; public ushort CommentLength; public ushort DiskNumberWhereFileStarts; public ushort InternalFileAttributes; public uint ExternalFileAttributes; public uint LocalFileOffset; } [StructLayout(LayoutKind.Sequential, Pack = 1)] private struct LocalFileRecord { public uint RecordSignature; public ushort VersionNeededToExtract; public ushort GeneralPurposeBitField; public ushort CompressionMethod; public ushort LastModificationTime; public ushort LastModificationDate; public uint Crc32; public uint CompressedSize; public uint UncompressedSize; public ushort PathLength; public ushort ExtraFieldLength; } private const uint EndOfCentralDirectoryRecordSignature = 101010256u; private const uint CentralDirectoryFileRecordSignature = 33639248u; private const uint LocalFileRecordSignature = 67324752u; private List entries; public ZipFile() : this(null) { } public ZipFile(Stream seekableStream, params ArchiveFileEntry[] entries) : this(seekableStream, null, entries) { } public ZipFile(Stream seekableStream, Func pathModifier = null, params ArchiveFileEntry[] entries) : this(seekableStream, pathModifier, (IEnumerable)entries) { } public ZipFile(Stream seekableStream, Func pathModifier = null, IEnumerable entries = null) { this.entries = new List(); if (seekableStream != null) { AddEntriesFromStream(seekableStream); } if (entries != null) { foreach (ArchiveFileEntry entry in entries) { AddEntry(entry); } } if (pathModifier != null) { ModifyFilePaths(pathModifier); } } public void Save(Stream stream) { int[] array = new int[entries.Count]; byte[][] array2 = new byte[entries.Count][]; int num = 0; for (int i = 0; i < entries.Count; i++) { array[i] = num; array2[i] = Encoding.Default.GetBytes(entries[i].Path); LocalFileRecord structure = new LocalFileRecord { RecordSignature = 67324752u, VersionNeededToExtract = entries[i].VersionNeededToExtract, CompressionMethod = entries[i].CompressionMethod, LastModificationTime = entries[i].LastModificationTime, LastModificationDate = entries[i].LastModificationDate, PathLength = (ushort)array2[i].Length, ExtraFieldLength = 0, Crc32 = entries[i].Crc32, CompressedSize = entries[i].CompressedSize, UncompressedSize = entries[i].UncompressedSize }; stream.WriteStructure(ref structure); stream.WriteAllBytes(array2[i]); num += Marshal.SizeOf(typeof(LocalFileRecord)) + array2[i].Length; entries[i].Save(stream, forceRaw: true); num += (int)entries[i].CompressedSize; } int num2 = num; for (int j = 0; j < entries.Count; j++) { CentralDirectoryFileRecord structure2 = new CentralDirectoryFileRecord { RecordSignature = 33639248u, VersionMadeBy = entries[j].VersionMadeBy, VersionNeededToExtract = entries[j].VersionNeededToExtract, CompressionMethod = entries[j].CompressionMethod, LastModificationTime = entries[j].LastModificationTime, LastModificationDate = entries[j].LastModificationDate, Crc32 = entries[j].Crc32, CompressedSize = entries[j].CompressedSize, UncompressedSize = entries[j].UncompressedSize, PathLength = (ushort)array2[j].Length, ExtraFieldLength = 0, CommentLength = 0, DiskNumberWhereFileStarts = entries[j].DiskNumberWhereFileStarts, InternalFileAttributes = entries[j].InternalFileAttributes, ExternalFileAttributes = entries[j].ExternalFileAttributes, LocalFileOffset = (uint)array[j] }; stream.WriteStructure(ref structure2); stream.WriteAllBytes(array2[j]); num += Marshal.SizeOf(typeof(CentralDirectoryFileRecord)) + array2[j].Length; } EndOfCentralDirectoryRecord structure3 = new EndOfCentralDirectoryRecord { RecordSignature = 101010256u, CentralDirectoryFileOffset = num2, CentralDirectoryByteSize = num - num2, CentralDirectoryRecordCountOnThisDisk = (ushort)entries.Count, CentralDirectoryRecordCount = (ushort)entries.Count }; stream.WriteStructure(ref structure3); } public IEnumerable GetPaths() { return entries.Select((ZipEntry e) => e.Path); } public IEnumerable GetEntries() { foreach (ZipEntry entry in entries) { int num = (int)(entry.ExternalFileAttributes >> 16); string linkPath = null; object content = null; if ((num & 0xA000) == 40960) { MemoryStream stream = new MemoryStream(); entry.Save(stream); ArraySegment allBytes = stream.GetAllBytes(); linkPath = Encoding.Default.GetString(allBytes.Array, allBytes.Offset, allBytes.Count); } else { content = entry; } yield return new ArchiveFileEntry(num, entry.Path, content, linkPath); } } public void ExtractEntryContent(string entryPath, Stream destinationStream) { entries.First((ZipEntry e) => e.Path == entryPath).Save(destinationStream); } public void ExtractAllEntries(string basePath, string requireEntriesInMoreStringentPath = null) { foreach (ZipEntry entry in entries) { string text = Path.Combine(basePath, entry.Path); FileSystemExtensions.DemandInParentPath(requireEntriesInMoreStringentPath ?? basePath, text); if (((entry.ExternalFileAttributes >> 16) & 0x4000) != 0 || entry.Path.EndsWith("/") || entry.Path.EndsWith("\\")) { Directory.CreateDirectory(text); continue; } FileSystemExtensions.EnsureDirectoryExists(Path.GetDirectoryName(text)); using FileStream stream = File.Create(text); entry.Save(stream); } } public void ModifyFilePaths(Func pathModifier) { foreach (ZipEntry entry in entries) { entry.Path = pathModifier(entry.Path).Replace('\\', '/'); } } public void ReplaceFilePaths(string findText, string replaceWithText) { ModifyFilePaths((string _) => _.Replace(findText, replaceWithText)); } public void AddEntriesFromStream(Stream seekableStream) { seekableStream.Seek(-Marshal.SizeOf(typeof(EndOfCentralDirectoryRecord)), SeekOrigin.End); EndOfCentralDirectoryRecord structure = default(EndOfCentralDirectoryRecord); seekableStream.ReadStructure(ref structure); if (structure.RecordSignature != 101010256) { throw new FormatException("Bad central end signature: Zip file cannot include a comment and has a fairly narrow format"); } seekableStream.Seek(structure.CentralDirectoryFileOffset, SeekOrigin.Begin); for (int i = 0; i < structure.CentralDirectoryRecordCount; i++) { CentralDirectoryFileRecord structure2 = default(CentralDirectoryFileRecord); seekableStream.ReadStructure(ref structure2); if (structure2.RecordSignature != 33639248) { throw new FormatException("Bad central file signature"); } if ((structure2.GeneralPurposeBitField & 1) != 0) { throw new FormatException("Entry cannot be encrypted"); } byte[] bytes = seekableStream.ReadBytes(structure2.PathLength); seekableStream.Seek(structure2.ExtraFieldLength, SeekOrigin.Current); seekableStream.Seek(structure2.CommentLength, SeekOrigin.Current); entries.Add(new ZipEntry { Path = Encoding.Default.GetString(bytes), Crc32 = structure2.Crc32, CompressedSize = structure2.CompressedSize, UncompressedSize = structure2.UncompressedSize, CompressionMethod = structure2.CompressionMethod, VersionMadeBy = structure2.VersionMadeBy, VersionNeededToExtract = structure2.VersionNeededToExtract, ExternalFileAttributes = structure2.ExternalFileAttributes, LastModificationDate = structure2.LastModificationDate, LastModificationTime = structure2.LastModificationTime, DiskNumberWhereFileStarts = structure2.DiskNumberWhereFileStarts, InternalFileAttributes = structure2.InternalFileAttributes, SeekableStream = seekableStream, SeekableStreamLocalFileOffset = structure2.LocalFileOffset }); } } public void AddEntry(string filePath, object content) { AddEntry(new ArchiveFileEntry(33279, filePath, content)); } public void AddEntry(ArchiveFileEntry entry) { string s = entry.FilePath.SafeNav((string p) => p.Replace('\\', '/')) ?? string.Empty; if ((entry.UnixMode & 0x4000) != 0) { Extensions.EnsureEndsWithChar(ref s, '/'); } ZipEntry zipEntry = new ZipEntry { Path = s, ExternalFileAttributes = (uint)(entry.UnixMode << 16) }; ReplaceEntryContent(zipEntry, entry.Content); entries.Add(zipEntry); } private void ReplaceEntryContent(ZipEntry entry, object content) { ArraySegment arraySegment = Extensions.ExtractContent(content); entry.SeekableStream = null; entry.CompressedData = new DeflatedFile(DeflateWrapper.None, arraySegment).Save(); entry.Crc32 = Extensions.CalculateCrc32Zip(arraySegment); entry.CompressedSize = (uint)entry.CompressedData.Count; entry.UncompressedSize = (uint)arraySegment.Count; entry.CompressionMethod = 8; entry.VersionMadeBy = 20; entry.VersionNeededToExtract = 20; } public void ReplaceEntryContent(string entryPath, object content) { ZipEntry entry = entries.First((ZipEntry e) => e.Path == entryPath); ReplaceEntryContent(entry, content); } }