From 2f3b1ef57d9728b7dcca2062e1f32d7bb37b0b84 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Wed, 6 Jun 2018 10:02:39 -0400 Subject: [PATCH] feat: Adding methods for inspecting contents of a ZIP file --- .../ICD.Common.Utils_SimplSharp.csproj | 3 +- ICD.Common.Utils/IO/Compression/IcdZip.cs | 163 ++++++++++++++++++ .../IO/Compression/IcdZipEntry.cs | 59 +++++++ ICD.Common.Utils/IcdZip.cs | 43 ----- 4 files changed, 224 insertions(+), 44 deletions(-) create mode 100644 ICD.Common.Utils/IO/Compression/IcdZip.cs create mode 100644 ICD.Common.Utils/IO/Compression/IcdZipEntry.cs delete mode 100644 ICD.Common.Utils/IcdZip.cs diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index ab52575..b199d67 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -98,6 +98,7 @@ + @@ -124,7 +125,7 @@ - + diff --git a/ICD.Common.Utils/IO/Compression/IcdZip.cs b/ICD.Common.Utils/IO/Compression/IcdZip.cs new file mode 100644 index 0000000..b9701db --- /dev/null +++ b/ICD.Common.Utils/IO/Compression/IcdZip.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +#if SIMPLSHARP + +#else +using System.IO.Compression; +#endif + +namespace ICD.Common.Utils.IO.Compression +{ + /// + /// Utils for managing archives. + /// + public static class IcdZip + { + private const int DIRECTORY_SIGNATURE = 0x06054B50; + private const int ENTRY_SIGNATURE = 0x02014B50; + + /// + /// Unzips the archive at the given path. + /// + /// + /// + /// + public static bool Unzip(string path, string outputPath, out string message) + { + try + { +#if SIMPLSHARP + CrestronZIP.ResultCode result = CrestronZIP.Unzip(path, outputPath); + message = result.ToString(); + return result == CrestronZIP.ResultCode.ZR_OK; +#else + using (ZipArchive archive = ZipFile.Open(path, ZipArchiveMode.Read)) + archive.ExtractToDirectory(outputPath); + message = "Success"; + return true; +#endif + } + catch (Exception e) + { + message = e.Message; + return false; + } + } + + /// + /// Gets the sequence of files names in the archive at the given path. + /// + public static IEnumerable GetFileNames(string path) + { + return GetEntries(path).Select(e => e.Name) + .Where(f => !f.EndsWith("/")) + .OrderBy(f => f); + } + + /// + /// Gets sequence of zip file entries in the archive at the given path. + /// + public static IEnumerable GetEntries(string path) + { + using (IcdStream stream = IcdFile.OpenRead(path)) + { + using (IcdBinaryReader reader = new IcdBinaryReader(stream)) + { + if (stream.Length < 22) + yield break; + + stream.Seek(-22, eSeekOrigin.End.ToSeekOrigin()); + + // find directory signature + while (reader.ReadInt32() != DIRECTORY_SIGNATURE) + { + if (stream.Position <= 5) + yield break; + + // move 1 byte back + stream.Seek(-5, eSeekOrigin.Current.ToSeekOrigin()); + } + + // read directory properties + stream.Seek(6, eSeekOrigin.Current.ToSeekOrigin()); + ushort entries = reader.ReadUInt16(); + int difSize = reader.ReadInt32(); + uint dirOffset = reader.ReadUInt32(); + stream.Seek(dirOffset, eSeekOrigin.Begin.ToSeekOrigin()); + + // read directory entries + for (int i = 0; i < entries; i++) + { + if (reader.ReadInt32() != ENTRY_SIGNATURE) + continue; + + // read file properties + reader.ReadInt32(); + bool utf8 = (reader.ReadInt16() & 0x0800) != 0; + short method = reader.ReadInt16(); + int timestamp = reader.ReadInt32(); + uint crc32 = reader.ReadUInt32(); + int compressedSize = reader.ReadInt32(); + int fileSize = reader.ReadInt32(); + short fileNameSize = reader.ReadInt16(); + short extraSize = reader.ReadInt16(); + short commentSize = reader.ReadInt16(); + int headerOffset = reader.ReadInt32(); + reader.ReadInt32(); + int fileHeaderOffset = reader.ReadInt32(); + byte[] fileNameBytes = reader.ReadBytes(fileNameSize); + stream.Seek(extraSize, eSeekOrigin.Current.ToSeekOrigin()); + byte[] fileCommentBytes = reader.ReadBytes(commentSize); + int fileDataOffset = CalculateFileDataOffset(stream, reader, fileHeaderOffset); + + // decode zip file entry + Encoding encoder = utf8 ? Encoding.UTF8 : Encoding.Default; + + yield return new IcdZipEntry + { + Name = encoder.GetString(fileNameBytes, 0, fileNameBytes.Length), + Comment = encoder.GetString(fileCommentBytes, 0, fileCommentBytes.Length), + Crc32 = crc32, + CompressedSize = compressedSize, + OriginalSize = fileSize, + HeaderOffset = fileHeaderOffset, + DataOffset = fileDataOffset, + Deflated = method == 8, + Timestamp = ConvertToDateTime(timestamp) + }; + } + } + } + } + + private static int CalculateFileDataOffset(IcdStream stream, IcdBinaryReader reader, int fileHeaderOffset) + { + long position = stream.Position; + stream.Seek(fileHeaderOffset + 26, eSeekOrigin.Begin.ToSeekOrigin()); + short fileNameSize = reader.ReadInt16(); + short extraSize = reader.ReadInt16(); + + int fileOffset = (int)stream.Position + fileNameSize + extraSize; + stream.Seek(position, eSeekOrigin.Begin.ToSeekOrigin()); + return fileOffset; + } + + /// + /// Converts DOS timestamp to a instance. + /// + /// The DOS timestamp. + /// The instance. + private static DateTime ConvertToDateTime(int dosTimestamp) + { + return new DateTime((dosTimestamp >> 25) + 1980, + (dosTimestamp >> 21) & 15, + (dosTimestamp >> 16) & 31, + (dosTimestamp >> 11) & 31, + (dosTimestamp >> 5) & 63, + (dosTimestamp & 31) * 2); + } + } +} diff --git a/ICD.Common.Utils/IO/Compression/IcdZipEntry.cs b/ICD.Common.Utils/IO/Compression/IcdZipEntry.cs new file mode 100644 index 0000000..1646ab0 --- /dev/null +++ b/ICD.Common.Utils/IO/Compression/IcdZipEntry.cs @@ -0,0 +1,59 @@ +using System; + +namespace ICD.Common.Utils.IO.Compression +{ + /// + /// Zip archive entry. + /// + public sealed class IcdZipEntry + { + /// + /// Gets or sets the name of a file or a directory. + /// + public string Name { get; set; } + + /// + /// Gets or sets the comment. + /// + public string Comment { get; set; } + + /// + /// Gets or sets the CRC32. + /// + public uint Crc32 { get; set; } + + /// + /// Gets or sets the compressed size of the file. + /// + public int CompressedSize { get; set; } + + /// + /// Gets or sets the original size of the file. + /// + public int OriginalSize { get; set; } + + /// + /// Gets or sets a value indicating whether this is deflated. + /// + public bool Deflated { get; set; } + + /// + /// Gets a value indicating whether this is a directory. + /// + public bool IsDirectory { get { return Name.EndsWith("/"); } } + + /// + /// Gets or sets the timestamp. + /// + public DateTime Timestamp { get; set; } + + /// + /// Gets a value indicating whether this is a file. + /// + public bool IsFile { get { return !IsDirectory; } } + + public int HeaderOffset { get; set; } + + public int DataOffset { get; set; } + } +} diff --git a/ICD.Common.Utils/IcdZip.cs b/ICD.Common.Utils/IcdZip.cs deleted file mode 100644 index b7f1c51..0000000 --- a/ICD.Common.Utils/IcdZip.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -#if SIMPLSHARP -using Crestron.SimplSharp; -#else -using System.IO.Compression; -#endif - -namespace ICD.Common.Utils -{ - /// - /// Utils for managing archives. - /// - public static class IcdZip - { - /// - /// Unzips the archive at the given path. - /// - /// - /// - /// - public static bool Unzip(string path, string outputPath, out string message) - { - try - { -#if SIMPLSHARP - CrestronZIP.ResultCode result = CrestronZIP.Unzip(path, outputPath); - message = result.ToString(); - return result == CrestronZIP.ResultCode.ZR_OK; -#else - using (ZipArchive archive = ZipFile.Open(path, ZipArchiveMode.Read)) - archive.ExtractToDirectory(outputPath); - message = "Success"; - return true; -#endif - } - catch (Exception e) - { - message = e.Message; - return false; - } - } - } -}