using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; namespace SevenZipExtractor { public class ArchiveFile : IDisposable { private SevenZipHandle sevenZipHandle; private readonly IInArchive archive; private readonly InStreamWrapper archiveStream; private IList entries; private string libraryFilePath; public ArchiveFile(string archiveFilePath, string libraryFilePath = null) { this.libraryFilePath = libraryFilePath; this.InitializeAndValidateLibrary(); if (!File.Exists(archiveFilePath)) { throw new SevenZipException("Archive file not found"); } SevenZipFormat format; string extension = Path.GetExtension(archiveFilePath); if (this.GuessFormatFromExtension(extension, out format)) { // great } else if (this.GuessFormatFromSignature(archiveFilePath, out format)) { // success } else { throw new SevenZipException(Path.GetFileName(archiveFilePath) + " is not a known archive type"); } this.archive = this.sevenZipHandle.CreateInArchive(Formats.FormatGuidMapping[format]); this.archiveStream = new InStreamWrapper(File.OpenRead(archiveFilePath)); } public ArchiveFile(Stream archiveStream, SevenZipFormat? format = null, string libraryFilePath = null) { this.libraryFilePath = libraryFilePath; this.InitializeAndValidateLibrary(); if (archiveStream == null) { throw new SevenZipException("archiveStream is null"); } if (format == null) { SevenZipFormat guessedFormat; if (this.GuessFormatFromSignature(archiveStream, out guessedFormat)) { format = guessedFormat; } else { throw new SevenZipException("Unable to guess format automatically"); } } this.archive = this.sevenZipHandle.CreateInArchive(Formats.FormatGuidMapping[format.Value]); this.archiveStream = new InStreamWrapper(archiveStream); } public void Extract(string outputFolder, bool overwrite = false) { this.Extract(entry => { string fileName = Path.Combine(outputFolder, entry.FileName); if (entry.IsFolder) { return fileName; } if (!File.Exists(fileName) || overwrite) { return fileName; } return null; }); } public void Extract(Func getOutputStream, bool leave_open = false) { IList fileStreams = new List(); try { foreach (Entry entry in Entries) { Stream outputStream = getOutputStream(entry); if (outputStream == null) // outputStream = null means SKIP { fileStreams.Add(null); continue; } if (entry.IsFolder) { fileStreams.Add(null); continue; } fileStreams.Add(outputStream); } this.archive.Extract(null, 0xFFFFFFFF, 0, new ArchiveStreamsCallback(fileStreams)); } finally { if (!leave_open) { foreach (Stream stream in fileStreams) { if (stream != null) { stream.Dispose(); } } } } } public void Extract(Func getOutputPath) { IList fileStreams = new List(); try { foreach (Entry entry in Entries) { string outputPath = getOutputPath(entry); if (outputPath == null) // getOutputPath = null means SKIP { fileStreams.Add(null); continue; } if (entry.IsFolder) { Directory.CreateDirectory(outputPath); fileStreams.Add(null); continue; } string directoryName = Path.GetDirectoryName(outputPath); if (!string.IsNullOrWhiteSpace(directoryName)) { Directory.CreateDirectory(directoryName); } fileStreams.Add(File.Create(outputPath)); } this.archive.Extract(null, 0xFFFFFFFF, 0, new ArchiveStreamsCallback(fileStreams)); } finally { foreach (Stream stream in fileStreams) { if (stream != null) { stream.Dispose(); } } } } public IList Entries { get { if (this.entries != null) { return this.entries; } ulong checkPos = 32 * 1024; int open = this.archive.Open(this.archiveStream, ref checkPos, null); if (open != 0) { throw new SevenZipException("Unable to open archive"); } uint itemsCount = this.archive.GetNumberOfItems(); this.entries = new List(); for (uint fileIndex = 0; fileIndex < itemsCount; fileIndex++) { string fileName = this.GetProperty(fileIndex, ItemPropId.kpidPath); bool isFolder = this.GetProperty(fileIndex, ItemPropId.kpidIsFolder); bool isEncrypted = this.GetProperty(fileIndex, ItemPropId.kpidEncrypted); ulong size = this.GetProperty(fileIndex, ItemPropId.kpidSize); ulong packedSize = this.GetProperty(fileIndex, ItemPropId.kpidPackedSize); DateTime creationTime = this.GetProperty(fileIndex, ItemPropId.kpidCreationTime); DateTime lastWriteTime = this.GetProperty(fileIndex, ItemPropId.kpidLastWriteTime); DateTime lastAccessTime = this.GetProperty(fileIndex, ItemPropId.kpidLastAccessTime); UInt32 crc = this.GetProperty(fileIndex, ItemPropId.kpidCRC); UInt32 attributes = this.GetProperty(fileIndex, ItemPropId.kpidAttributes); string comment = this.GetProperty(fileIndex, ItemPropId.kpidComment); string hostOS = this.GetProperty(fileIndex, ItemPropId.kpidHostOS); string method = this.GetProperty(fileIndex, ItemPropId.kpidMethod); bool isSplitBefore = this.GetProperty(fileIndex, ItemPropId.kpidSplitBefore); bool isSplitAfter = this.GetProperty(fileIndex, ItemPropId.kpidSplitAfter); this.entries.Add(new Entry(this.archive, fileIndex) { FileName = fileName, IsFolder = isFolder, IsEncrypted = isEncrypted, Size = size, PackedSize = packedSize, CreationTime = creationTime, LastWriteTime = lastWriteTime, LastAccessTime = lastAccessTime, CRC = crc, Attributes = attributes, Comment = comment, HostOS = hostOS, Method = method, IsSplitBefore = isSplitBefore, IsSplitAfter = isSplitAfter }); } return this.entries; } } private T GetProperty(uint fileIndex, ItemPropId name) { PropVariant propVariant = new PropVariant(); this.archive.GetProperty(fileIndex, name, ref propVariant); T result = propVariant.VarType != VarEnum.VT_EMPTY ? (T)(dynamic) propVariant.GetObject() : default(T); propVariant.Clear(); return result; } private void InitializeAndValidateLibrary() { if (string.IsNullOrWhiteSpace(this.libraryFilePath)) { string currentArchitecture = IntPtr.Size == 4 ? "x86" : "x64"; // magic check if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "7z-" + currentArchitecture + ".dll"))) { this.libraryFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "7z-" + currentArchitecture + ".dll"); } else if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin", "7z-" + currentArchitecture + ".dll"))) { this.libraryFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin", "7z-" + currentArchitecture + ".dll"); } else if (File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, currentArchitecture, "7z.dll"))) { this.libraryFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, currentArchitecture, "7z.dll"); } else if (File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "7-Zip", "7z.dll"))) { this.libraryFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "7-Zip", "7z.dll"); } } if (string.IsNullOrWhiteSpace(this.libraryFilePath)) { throw new SevenZipException("libraryFilePath not set"); } if (!File.Exists(this.libraryFilePath)) { throw new SevenZipException("7z.dll not found"); } try { this.sevenZipHandle = new SevenZipHandle(this.libraryFilePath); } catch (Exception e) { throw new SevenZipException("Unable to initialize SevenZipHandle", e); } } private bool GuessFormatFromExtension(string fileExtension, out SevenZipFormat format) { if (string.IsNullOrWhiteSpace(fileExtension)) { format = SevenZipFormat.Undefined; return false; } fileExtension = fileExtension.TrimStart('.').Trim().ToLowerInvariant(); if (fileExtension.Equals("rar")) { // 7z has different GUID for Pre-RAR5 and RAR5, but they have both same extension (.rar) // If it is [0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00] then file is RAR5 otherwise RAR. // https://www.rarlab.com/technote.htm // We are unable to guess right format just by looking at extension and have to check signature format = SevenZipFormat.Undefined; return false; } if (!Formats.ExtensionFormatMapping.ContainsKey(fileExtension)) { format = SevenZipFormat.Undefined; return false; } format = Formats.ExtensionFormatMapping[fileExtension]; return true; } private bool GuessFormatFromSignature(string filePath, out SevenZipFormat format) { using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { return GuessFormatFromSignature(fileStream, out format); } } private bool GuessFormatFromSignature(Stream stream, out SevenZipFormat format) { int longestSignature = Formats.FileSignatures.Values.OrderByDescending(v => v.Length).First().Length; byte[] archiveFileSignature = new byte[longestSignature]; int bytesRead = stream.Read(archiveFileSignature, 0, longestSignature); stream.Position -= bytesRead; // go back o beginning if (bytesRead != longestSignature) { format = SevenZipFormat.Undefined; return false; } foreach (KeyValuePair pair in Formats.FileSignatures) { if (archiveFileSignature.Take(pair.Value.Length).SequenceEqual(pair.Value)) { format = pair.Key; return true; } } format = SevenZipFormat.Undefined; return false; } ~ArchiveFile() { this.Dispose(false); } protected void Dispose(bool disposing) { if (this.archiveStream != null) { this.archiveStream.Dispose(); } if (this.archive != null) { Marshal.ReleaseComObject(this.archive); } if (this.sevenZipHandle != null) { this.sevenZipHandle.Dispose(); } } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } } }