1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495 |
- namespace SevenZip
- {
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.Diagnostics;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using SevenZip.Sdk.Compression.Lzma;
- /// <summary>
- /// Class to unpack data from archives supported by 7-Zip.
- /// </summary>
- /// <example>
- /// using (var extr = new SevenZipExtractor(@"C:\Test.7z"))
- /// {
- /// extr.ExtractArchive(@"C:\TestDirectory");
- /// }
- /// </example>
- public sealed partial class SevenZipExtractor
- #if UNMANAGED
- : SevenZipBase, IDisposable
- #endif
- {
- #if UNMANAGED
- private List<ArchiveFileInfo> _archiveFileData;
- private IInArchive _archive;
- private IInStream _archiveStream;
- private int _offset;
- private ArchiveOpenCallback _openCallback;
- private string _fileName;
- private Stream _inStream;
- private long? _packedSize;
- private long? _unpackedSize;
- private uint? _filesCount;
- private bool? _isSolid;
- private bool _opened;
- private bool _disposed;
- private InArchiveFormat _format = (InArchiveFormat)(-1);
- private ReadOnlyCollection<ArchiveFileInfo> _archiveFileInfoCollection;
- private ReadOnlyCollection<ArchiveProperty> _archiveProperties;
- private ReadOnlyCollection<string> _volumeFileNames;
- private bool _leaveOpen;
- /// <summary>
- /// This is used to lock possible Dispose() calls.
- /// </summary>
- private bool _asynchronousDisposeLock;
- #region Constructors
- /// <summary>
- /// General initialization function.
- /// </summary>
- /// <param name="archiveFullName">The archive file name.</param>
- private void Init(string archiveFullName)
- {
- _fileName = archiveFullName;
- var isExecutable = false;
- if ((int)_format == -1)
- {
- _format = FileChecker.CheckSignature(archiveFullName, out _offset, out isExecutable);
- }
- PreserveDirectoryStructure = true;
- SevenZipLibraryManager.LoadLibrary(this, _format);
- try
- {
- _archive = SevenZipLibraryManager.InArchive(_format, this);
- }
- catch (SevenZipLibraryException)
- {
- SevenZipLibraryManager.FreeLibrary(this, _format);
- throw;
- }
- if (isExecutable && _format != InArchiveFormat.PE)
- {
- if (!Check())
- {
- CommonDispose();
- _format = InArchiveFormat.PE;
- SevenZipLibraryManager.LoadLibrary(this, _format);
- try
- {
- _archive = SevenZipLibraryManager.InArchive(_format, this);
- }
- catch (SevenZipLibraryException)
- {
- SevenZipLibraryManager.FreeLibrary(this, _format);
- throw;
- }
- }
- }
- }
- /// <summary>
- /// General initialization function.
- /// </summary>
- /// <param name="stream">The stream to read the archive from.</param>
- private void Init(Stream stream)
- {
- ValidateStream(stream);
- var isExecutable = false;
- if ((int)_format == -1)
- {
- _format = FileChecker.CheckSignature(stream, out _offset, out isExecutable);
- }
- PreserveDirectoryStructure = true;
- SevenZipLibraryManager.LoadLibrary(this, _format);
- try
- {
- _inStream = new ArchiveEmulationStreamProxy(stream, _offset, _leaveOpen);
- _packedSize = stream.Length;
- _archive = SevenZipLibraryManager.InArchive(_format, this);
- }
- catch (SevenZipLibraryException)
- {
- SevenZipLibraryManager.FreeLibrary(this, _format);
- throw;
- }
- if (isExecutable && _format != InArchiveFormat.PE)
- {
- if (!Check())
- {
- CommonDispose();
- _format = InArchiveFormat.PE;
- try
- {
- _inStream = new ArchiveEmulationStreamProxy(stream, _offset, _leaveOpen);
- _packedSize = stream.Length;
- _archive = SevenZipLibraryManager.InArchive(_format, this);
- }
- catch (SevenZipLibraryException)
- {
- SevenZipLibraryManager.FreeLibrary(this, _format);
- throw;
- }
- }
- }
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveStream">The stream to read the archive from.
- /// Use SevenZipExtractor(string) to extract from disk, though it is not necessary.</param>
- /// <remarks>The archive format is guessed by the signature.</remarks>
- public SevenZipExtractor(Stream archiveStream) : this(archiveStream, false)
- {
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveStream">The stream to read the archive from.
- /// Use SevenZipExtractor(string) to extract from disk, though it is not necessary.</param>
- /// <param name="leaveOpen">Leaves the base stream open.</param>
- /// <remarks>The archive format is guessed by the signature.</remarks>
- public SevenZipExtractor(Stream archiveStream, bool leaveOpen) : this(archiveStream, leaveOpen, (InArchiveFormat)(-1))
- {
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveStream">The stream to read the archive from.
- /// Use SevenZipExtractor(string) to extract from disk, though it is not necessary.</param>
- /// <param name="leaveOpen">Leaves the base stream open.</param>
- /// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
- /// Instead, use SevenZipExtractor(Stream archiveStream), that constructor
- /// automatically detects the archive format.</param>
- public SevenZipExtractor(Stream archiveStream, bool leaveOpen, InArchiveFormat format)
- {
- _leaveOpen = leaveOpen;
- _format = format;
- Init(archiveStream);
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveFullName">The archive full file name.</param>
- public SevenZipExtractor(string archiveFullName)
- {
- Init(archiveFullName);
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveFullName">The archive full file name.</param>
- /// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
- /// Instead, use SevenZipExtractor(string archiveFullName), that constructor
- /// automatically detects the archive format.</param>
- public SevenZipExtractor(string archiveFullName, InArchiveFormat format)
- {
- _format = format;
- Init(archiveFullName);
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveFullName">The archive full file name.</param>
- /// <param name="password">Password for an encrypted archive.</param>
- public SevenZipExtractor(string archiveFullName, string password)
- : base(password)
- {
- Init(archiveFullName);
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveFullName">The archive full file name.</param>
- /// <param name="password">Password for an encrypted archive.</param>
- /// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
- /// Instead, use SevenZipExtractor(string archiveFullName, string password), that constructor
- /// automatically detects the archive format.</param>
- public SevenZipExtractor(string archiveFullName, string password, InArchiveFormat format)
- : base(password)
- {
- _format = format;
- Init(archiveFullName);
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveStream">The stream to read the archive from.</param>
- /// <param name="password">Password for an encrypted archive.</param>
- /// <remarks>The archive format is guessed by the signature.</remarks>
- public SevenZipExtractor(Stream archiveStream, string password) : this(archiveStream, password, false)
- {
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveStream">The stream to read the archive from.</param>
- /// <param name="password">Password for an encrypted archive.</param>
- /// <param name="leaveOpen">Leaves the base stream open.</param>
- /// <remarks>The archive format is guessed by the signature.</remarks>
- public SevenZipExtractor(Stream archiveStream, string password, bool leaveOpen) : this(archiveStream, password, leaveOpen, (InArchiveFormat)(-1))
- {
- }
- /// <summary>
- /// Initializes a new instance of SevenZipExtractor class.
- /// </summary>
- /// <param name="archiveStream">The stream to read the archive from.</param>
- /// <param name="password">Password for an encrypted archive.</param>
- /// <param name="leaveOpen">Leaves the base stream open.</param>
- /// <param name="format">Manual archive format setup. You SHOULD NOT normally specify it this way.
- /// Instead, use SevenZipExtractor(Stream archiveStream, string password), that constructor
- /// automatically detects the archive format.</param>
- public SevenZipExtractor(Stream archiveStream, string password, bool leaveOpen, InArchiveFormat format)
- : base(password)
- {
- _format = format;
- _leaveOpen = leaveOpen;
- Init(archiveStream);
- }
- #endregion
- #region Properties
- /// <summary>
- /// Gets or sets archive full file name
- /// </summary>
- public string FileName
- {
- get
- {
- DisposedCheck();
- return _fileName;
- }
- }
- /// <summary>
- /// Gets the size of the archive file
- /// </summary>
- public long PackedSize
- {
- get
- {
- DisposedCheck();
- return _packedSize ?? (_fileName != null ?
- new FileInfo(_fileName).Length :
- -1);
- }
- }
- /// <summary>
- /// Gets the size of unpacked archive data
- /// </summary>
- public long UnpackedSize
- {
- get
- {
- DisposedCheck();
- if (!_unpackedSize.HasValue)
- {
- return -1;
- }
- return _unpackedSize.Value;
- }
- }
- /// <summary>
- /// Gets a value indicating whether the archive is solid
- /// </summary>
- public bool IsSolid
- {
- get
- {
- DisposedCheck();
- if (!_isSolid.HasValue)
- {
- GetArchiveInfo(true);
- }
- Debug.Assert(_isSolid != null);
- return _isSolid.Value;
- }
- }
- /// <summary>
- /// Gets the number of files in the archive
- /// </summary>
- [CLSCompliant(false)]
- public uint FilesCount
- {
- get
- {
- DisposedCheck();
- if (!_filesCount.HasValue)
- {
- GetArchiveInfo(true);
- }
- Debug.Assert(_filesCount != null);
- return _filesCount.Value;
- }
- }
- /// <summary>
- /// Gets archive format
- /// </summary>
- public InArchiveFormat Format
- {
- get
- {
- DisposedCheck();
- return _format;
- }
- }
- /// <summary>
- /// Gets or sets the value indicating whether to preserve the directory structure of extracted files.
- /// </summary>
- public bool PreserveDirectoryStructure { get; set; }
- #endregion
- /// <summary>
- /// Checked whether the class was disposed.
- /// </summary>
- /// <exception cref="System.ObjectDisposedException" />
- private void DisposedCheck()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException("SevenZipExtractor");
- }
- RecreateInstanceIfNeeded();
- }
- #region Core private functions
- private ArchiveOpenCallback GetArchiveOpenCallback()
- {
- return _openCallback ?? (_openCallback = string.IsNullOrEmpty(Password)
- ? new ArchiveOpenCallback(_fileName)
- : new ArchiveOpenCallback(_fileName, Password));
- }
- /// <summary>
- /// Gets the archive input stream.
- /// </summary>
- /// <returns>The archive input wrapper stream.</returns>
- private IInStream GetArchiveStream(bool dispose)
- {
- if (_archiveStream != null)
- {
- if (_archiveStream is DisposeVariableWrapper wrapper)
- {
- wrapper.DisposeStream = dispose;
- }
- return _archiveStream;
- }
- if (_inStream != null)
- {
- _inStream.Seek(0, SeekOrigin.Begin);
- _archiveStream = new InStreamWrapper(_inStream, false);
- }
- else
- {
- if (!_fileName.EndsWith(".001", StringComparison.OrdinalIgnoreCase))
- {
- _archiveStream = new InStreamWrapper(
- new ArchiveEmulationStreamProxy(new FileStream(
- _fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite),
- _offset, _leaveOpen),
- dispose);
- }
- else
- {
- _archiveStream = new InMultiStreamWrapper(_fileName, dispose);
- _packedSize = (_archiveStream as InMultiStreamWrapper)?.Length;
- }
- }
- return _archiveStream;
- }
- /// <summary>
- /// Opens the archive and throws exceptions or returns OperationResult.DataError if any error occurs.
- /// </summary>
- /// <param name="archiveStream">The IInStream compliant class instance, that is, the input stream.</param>
- /// <param name="openCallback">The ArchiveOpenCallback instance.</param>
- /// <returns>OperationResult.Ok if Open() succeeds.</returns>
- private OperationResult OpenArchiveInner(IInStream archiveStream, IArchiveOpenCallback openCallback)
- {
- ulong checkPos = 1 << 23;
- var res = _archive.Open(archiveStream, ref checkPos, openCallback);
- return (OperationResult)res;
- }
- /// <summary>
- /// Opens the archive and throws exceptions or returns OperationResult.DataError if any error occurs.
- /// </summary>
- /// <param name="archiveStream">The IInStream compliant class instance, that is, the input stream.</param>
- /// <param name="openCallback">The ArchiveOpenCallback instance.</param>
- /// <returns>True if Open() succeeds; otherwise, false.</returns>
- private bool OpenArchive(IInStream archiveStream, ArchiveOpenCallback openCallback)
- {
- if (!_opened)
- {
- if (OpenArchiveInner(archiveStream, openCallback) != OperationResult.Ok)
- {
- if (!ThrowException(null, new SevenZipArchiveException()))
- {
- return false;
- }
- }
- _volumeFileNames = new ReadOnlyCollection<string>(openCallback.VolumeFileNames);
- _opened = true;
- }
- return true;
- }
- /// <summary>
- /// Retrieves all information about the archive.
- /// </summary>
- /// <exception cref="SevenZip.SevenZipArchiveException"/>
- private void GetArchiveInfo(bool disposeStream)
- {
- if (_archive == null)
- {
- if (!ThrowException(null, new SevenZipArchiveException()))
- {
- return;
- }
- }
- else
- {
- IInStream archiveStream;
- using ((archiveStream = GetArchiveStream(disposeStream)) as IDisposable)
- {
- var openCallback = GetArchiveOpenCallback();
- if (!_opened)
- {
- if (!OpenArchive(archiveStream, openCallback))
- {
- return;
- }
- _opened = !disposeStream;
- }
- _filesCount = _archive.GetNumberOfItems();
- _archiveFileData = new List<ArchiveFileInfo>((int)_filesCount);
- if (_filesCount != 0)
- {
- var data = new PropVariant();
- try
- {
- #region Getting archive items data
- for (uint i = 0; i < _filesCount; i++)
- {
- try
- {
- var fileInfo = new ArchiveFileInfo { Index = (int)i };
- _archive.GetProperty(i, ItemPropId.Path, ref data);
- fileInfo.FileName = NativeMethods.SafeCast(data, "[no name]");
- _archive.GetProperty(i, ItemPropId.LastWriteTime, ref data);
- fileInfo.LastWriteTime = NativeMethods.SafeCast(data, DateTime.Now);
- _archive.GetProperty(i, ItemPropId.CreationTime, ref data);
- fileInfo.CreationTime = NativeMethods.SafeCast(data, DateTime.Now);
- _archive.GetProperty(i, ItemPropId.LastAccessTime, ref data);
- fileInfo.LastAccessTime = NativeMethods.SafeCast(data, DateTime.Now);
- _archive.GetProperty(i, ItemPropId.Size, ref data);
- fileInfo.Size = NativeMethods.SafeCast<ulong>(data, 0);
- if (fileInfo.Size == 0)
- {
- fileInfo.Size = NativeMethods.SafeCast<uint>(data, 0);
- }
- _archive.GetProperty(i, ItemPropId.Attributes, ref data);
- fileInfo.Attributes = NativeMethods.SafeCast<uint>(data, 0);
- _archive.GetProperty(i, ItemPropId.IsDirectory, ref data);
- fileInfo.IsDirectory = NativeMethods.SafeCast(data, false);
- _archive.GetProperty(i, ItemPropId.Encrypted, ref data);
- fileInfo.Encrypted = NativeMethods.SafeCast(data, false);
- _archive.GetProperty(i, ItemPropId.Crc, ref data);
- fileInfo.Crc = NativeMethods.SafeCast<uint>(data, 0);
- _archive.GetProperty(i, ItemPropId.Comment, ref data);
- fileInfo.Comment = NativeMethods.SafeCast(data, "");
- _archive.GetProperty(i, ItemPropId.Method, ref data);
- fileInfo.Method = NativeMethods.SafeCast(data, "");
- _archiveFileData.Add(fileInfo);
- }
- catch (InvalidCastException)
- {
- ThrowException(null, new SevenZipArchiveException("probably archive is corrupted."));
- }
- }
- #endregion
- #region Getting archive properties
- var numProps = _archive.GetNumberOfArchiveProperties();
- var archProps = new List<ArchiveProperty>((int)numProps);
- for (uint i = 0; i < numProps; i++)
- {
- _archive.GetArchivePropertyInfo(i, out _, out var propId, out _);
- _archive.GetArchiveProperty(propId, ref data);
- if (propId == ItemPropId.Solid)
- {
- _isSolid = NativeMethods.SafeCast(data, true);
- }
- // TODO Add more archive properties
- if (PropIdToName.PropIdNames.ContainsKey(propId))
- {
- archProps.Add(new ArchiveProperty
- {
- Name = PropIdToName.PropIdNames[propId],
- Value = data.Object
- });
- }
- else
- {
- Debug.WriteLine($"An unknown archive property encountered (code {((int)propId).ToString(CultureInfo.InvariantCulture)})");
- }
- }
- _archiveProperties = new ReadOnlyCollection<ArchiveProperty>(archProps);
- if (!_isSolid.HasValue && _format == InArchiveFormat.Zip)
- {
- _isSolid = false;
- }
- if (!_isSolid.HasValue)
- {
- _isSolid = true;
- }
- #endregion
- }
- catch (Exception)
- {
- if (openCallback.ThrowException())
- {
- throw;
- }
- }
- }
- }
- if (disposeStream)
- {
- _archive.Close();
- _archiveStream = null;
- }
- _archiveFileInfoCollection = new ReadOnlyCollection<ArchiveFileInfo>(_archiveFileData);
- }
- }
- /// <summary>
- /// Ensure that _archiveFileData is loaded.
- /// </summary>
- /// <param name="disposeStream">Dispose the archive stream after this operation.</param>
- private void InitArchiveFileData(bool disposeStream)
- {
- if (_archiveFileData == null)
- {
- GetArchiveInfo(disposeStream);
- }
- }
- /// <summary>
- /// Produces an array of indexes from 0 to the maximum value in the specified array
- /// </summary>
- /// <param name="indexes">The source array</param>
- /// <returns>The array of indexes from 0 to the maximum value in the specified array</returns>
- private static uint[] SolidIndexes(uint[] indexes)
- {
- var max = indexes.Aggregate(0, (current, i) => Math.Max(current, (int)i));
- if (max > 0)
- {
- max++;
- var res = new uint[max];
- for (var i = 0; i < max; i++)
- {
- res[i] = (uint)i;
- }
- return res;
- }
- return indexes;
- }
- /// <summary>
- /// Checks whether all the indexes are valid.
- /// </summary>
- /// <param name="indexes">The indexes to check.</param>
- /// <returns>True is valid; otherwise, false.</returns>
- private static bool CheckIndexes(params int[] indexes)
- {
- return indexes.All(i => i >= 0);
- }
- private void ArchiveExtractCallbackCommonInit(ArchiveExtractCallback aec)
- {
- aec.Open += ((s, e) => { _unpackedSize = (long)e.TotalSize; });
- aec.FileExtractionStarted += FileExtractionStartedEventProxy;
- aec.FileExtractionFinished += FileExtractionFinishedEventProxy;
- aec.Extracting += ExtractingEventProxy;
- aec.FileExists += FileExistsEventProxy;
- }
- /// <summary>
- /// Gets the IArchiveExtractCallback callback
- /// </summary>
- /// <param name="directory">The directory where extract the files</param>
- /// <param name="filesCount">The number of files to be extracted</param>
- /// <param name="actualIndexes">The list of actual indexes (solid archives support)</param>
- /// <returns>The ArchiveExtractCallback callback</returns>
- private ArchiveExtractCallback GetArchiveExtractCallback(string directory, int filesCount, List<uint> actualIndexes)
- {
- var aec = string.IsNullOrEmpty(Password) ?
- new ArchiveExtractCallback(_archive, directory, filesCount, PreserveDirectoryStructure, actualIndexes, this) :
- new ArchiveExtractCallback(_archive, directory, filesCount, PreserveDirectoryStructure, actualIndexes, Password, this);
- ArchiveExtractCallbackCommonInit(aec);
- return aec;
- }
- /// <summary>
- /// Gets the IArchiveExtractCallback callback
- /// </summary>
- /// <param name="stream">The stream where extract the file</param>
- /// <param name="index">The file index</param>
- /// <param name="filesCount">The number of files to be extracted</param>
- /// <returns>The ArchiveExtractCallback callback</returns>
- private ArchiveExtractCallback GetArchiveExtractCallback(Stream stream, uint index, int filesCount)
- {
- var aec = string.IsNullOrEmpty(Password)
- ? new ArchiveExtractCallback(_archive, stream, filesCount, index, this)
- : new ArchiveExtractCallback(_archive, stream, filesCount, index, Password, this);
- ArchiveExtractCallbackCommonInit(aec);
- return aec;
- }
- private void FreeArchiveExtractCallback(ArchiveExtractCallback callback)
- {
- callback.Open -= ((s, e) => { _unpackedSize = (long)e.TotalSize; });
- callback.FileExtractionStarted -= FileExtractionStartedEventProxy;
- callback.FileExtractionFinished -= FileExtractionFinishedEventProxy;
- callback.Extracting -= ExtractingEventProxy;
- callback.FileExists -= FileExistsEventProxy;
- }
- #endregion
- #endif
- /// <summary>
- /// Checks if the specified stream supports extraction.
- /// </summary>
- /// <param name="stream">The stream to check.</param>
- private static void ValidateStream(Stream stream)
- {
- if (stream == null)
- {
- throw new ArgumentNullException(nameof(stream));
- }
- if (!stream.CanSeek || !stream.CanRead)
- {
- throw new ArgumentException("The specified stream can not seek or read.", nameof(stream));
- }
- if (stream.Length == 0)
- {
- throw new ArgumentException("The specified stream has zero length.", nameof(stream));
- }
- }
- #if UNMANAGED
- #region IDisposable Members
- private void CommonDispose()
- {
- if (_opened)
- {
- try
- {
- _archive?.Close();
- }
- catch (Exception) { }
- }
- _archive = null;
- _archiveFileData = null;
- _archiveProperties = null;
- _archiveFileInfoCollection = null;
- if (_inStream != null && !_leaveOpen)
- {
- _inStream.Dispose();
- _inStream = null;
- }
- if (_openCallback != null)
- {
- try
- {
- _openCallback.Dispose();
- }
- catch (ObjectDisposedException) { }
- _openCallback = null;
- }
- if (_archiveStream != null && !_leaveOpen)
- {
- if (_archiveStream is IDisposable disposable)
- {
- try
- {
- if (disposable is DisposeVariableWrapper wrapper)
- {
- wrapper.DisposeStream = true;
- }
- disposable.Dispose();
- }
- catch (ObjectDisposedException) { }
- _archiveStream = null;
- }
- }
- SevenZipLibraryManager.FreeLibrary(this, _format);
- }
- /// <summary>
- /// Releases the unmanaged resources used by SevenZipExtractor.
- /// </summary>
- public void Dispose()
- {
- if (_asynchronousDisposeLock)
- {
- throw new InvalidOperationException("SevenZipExtractor instance must not be disposed while making an asynchronous method call.");
- }
- if (!_disposed)
- {
- CommonDispose();
- }
- _disposed = true;
- }
- #endregion
- #region Core public Members
- #region Events
- /// <summary>
- /// Occurs when a new file is going to be unpacked.
- /// </summary>
- /// <remarks>Occurs when 7-zip engine requests for an output stream for a new file to unpack in.</remarks>
- public event EventHandler<FileInfoEventArgs> FileExtractionStarted;
- /// <summary>
- /// Occurs when a file has been successfully unpacked.
- /// </summary>
- public event EventHandler<FileInfoEventArgs> FileExtractionFinished;
- /// <summary>
- /// Occurs when the archive has been unpacked.
- /// </summary>
- public event EventHandler<EventArgs> ExtractionFinished;
- /// <summary>
- /// Occurs when data are being extracted.
- /// </summary>
- /// <remarks>Use this event for accurate progress handling and various ProgressBar.StepBy(e.PercentDelta) routines.</remarks>
- public event EventHandler<ProgressEventArgs> Extracting;
- /// <summary>
- /// Occurs during the extraction when a file already exists.
- /// </summary>
- public event EventHandler<FileOverwriteEventArgs> FileExists;
- #region Event proxies
- /// <summary>
- /// Event proxy for FileExtractionStarted.
- /// </summary>
- /// <param name="sender">The sender of the event.</param>
- /// <param name="e">The event arguments.</param>
- private void FileExtractionStartedEventProxy(object sender, FileInfoEventArgs e)
- {
- OnEvent(FileExtractionStarted, e, true);
- }
- /// <summary>
- /// Event proxy for FileExtractionFinished.
- /// </summary>
- /// <param name="sender">The sender of the event.</param>
- /// <param name="e">The event arguments.</param>
- private void FileExtractionFinishedEventProxy(object sender, FileInfoEventArgs e)
- {
- OnEvent(FileExtractionFinished, e, true);
- }
- /// <summary>
- /// Event proxy for Extracting.
- /// </summary>
- /// <param name="sender">The sender of the event.</param>
- /// <param name="e">The event arguments.</param>
- private void ExtractingEventProxy(object sender, ProgressEventArgs e)
- {
- OnEvent(Extracting, e, false);
- }
- /// <summary>
- /// Event proxy for FileExists.
- /// </summary>
- /// <param name="sender">The sender of the event.</param>
- /// <param name="e">The event arguments.</param>
- private void FileExistsEventProxy(object sender, FileOverwriteEventArgs e)
- {
- OnEvent(FileExists, e, true);
- }
- #endregion
- #endregion
- #region Properties
- /// <summary>
- /// Gets the collection of ArchiveFileInfo with all information about files in the archive
- /// </summary>
- public ReadOnlyCollection<ArchiveFileInfo> ArchiveFileData
- {
- get
- {
- DisposedCheck();
- InitArchiveFileData(true);
- return _archiveFileInfoCollection;
- }
- }
- /// <summary>
- /// Gets the properties for the current archive
- /// </summary>
- public ReadOnlyCollection<ArchiveProperty> ArchiveProperties
- {
- get
- {
- DisposedCheck();
- InitArchiveFileData(true);
- return _archiveProperties;
- }
- }
- /// <summary>
- /// Gets the collection of all file names contained in the archive.
- /// </summary>
- /// <remarks>
- /// Each get recreates the collection
- /// </remarks>
- public ReadOnlyCollection<string> ArchiveFileNames
- {
- get
- {
- DisposedCheck();
- InitArchiveFileData(true);
- var fileNames = new List<string>(_archiveFileData.Count);
- fileNames.AddRange(_archiveFileData.Select(afi => afi.FileName));
- return new ReadOnlyCollection<string>(fileNames);
- }
- }
- /// <summary>
- /// Gets the list of archive volume file names.
- /// </summary>
- public ReadOnlyCollection<string> VolumeFileNames
- {
- get
- {
- DisposedCheck();
- InitArchiveFileData(true);
- return _volumeFileNames;
- }
- }
- #endregion
- /// <summary>
- /// Performs the archive integrity test.
- /// </summary>
- /// <returns>True is the archive is ok; otherwise, false.</returns>
- public bool Check()
- {
- DisposedCheck();
- try
- {
- InitArchiveFileData(false);
- var archiveStream = GetArchiveStream(true);
- var openCallback = GetArchiveOpenCallback();
- if (!OpenArchive(archiveStream, openCallback))
- {
- return false;
- }
- using (var aec = GetArchiveExtractCallback("", (int)_filesCount, null))
- {
- try
- {
- CheckedExecute(
- _archive.Extract(null, uint.MaxValue, 1, aec),
- SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
- }
- finally
- {
- FreeArchiveExtractCallback(aec);
- }
- }
- }
- catch (Exception)
- {
- return false;
- }
- finally
- {
- _archive?.Close();
- if (_archiveStream is IDisposable disposable)
- {
- disposable.Dispose();
- }
- _archiveStream = null;
- _opened = false;
- }
- return true;
- }
- #region ExtractFile overloads
- /// <summary>
- /// Unpacks the file by its name to the specified stream.
- /// </summary>
- /// <param name="fileName">The file full name in the archive file table.</param>
- /// <param name="stream">The stream where the file is to be unpacked.</param>
- public void ExtractFile(string fileName, Stream stream)
- {
- DisposedCheck();
- InitArchiveFileData(false);
- var index = -1;
- foreach (var afi in _archiveFileData)
- {
- if (afi.FileName == fileName && !afi.IsDirectory)
- {
- index = afi.Index;
- break;
- }
- }
- if (index == -1)
- {
- if (!ThrowException(null, new ArgumentOutOfRangeException(
- nameof(fileName),
- "The specified file name was not found in the archive file table.")))
- {
- return;
- }
- }
- else
- {
- ExtractFile(index, stream);
- }
- }
- /// <summary>
- /// Unpacks the file by its index to the specified stream.
- /// </summary>
- /// <param name="index">Index in the archive file table.</param>
- /// <param name="stream">The stream where the file is to be unpacked.</param>
- public void ExtractFile(int index, Stream stream)
- {
- DisposedCheck();
- ClearExceptions();
- if (!CheckIndexes(index))
- {
- if (!ThrowException(null, new ArgumentException("The index must be more or equal to zero.", nameof(index))))
- {
- return;
- }
- }
- if (!stream.CanWrite)
- {
- if (!ThrowException(null, new ArgumentException("The specified stream can not be written.", nameof(stream))))
- {
- return;
- }
- }
- InitArchiveFileData(false);
- if (index > _filesCount - 1)
- {
- if (!ThrowException(null, new ArgumentOutOfRangeException(
- nameof(index), "The specified index is greater than the archive files count.")))
- {
- return;
- }
- }
- var archiveStream = GetArchiveStream(false);
- var openCallback = GetArchiveOpenCallback();
- if (!OpenArchive(archiveStream, openCallback))
- {
- return;
- }
- try
- {
- var indexes = new[] { (uint)index };
- var entry = _archiveFileData[index];
- if (_isSolid.Value && !entry.Method.Equals("Copy", StringComparison.InvariantCultureIgnoreCase))
- {
- indexes = SolidIndexes(indexes);
- }
- using (var aec = GetArchiveExtractCallback(stream, (uint)index, indexes.Length))
- {
- try
- {
- CheckedExecute(
- _archive.Extract(indexes, (uint)indexes.Length, 0, aec),
- SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
- }
- finally
- {
- FreeArchiveExtractCallback(aec);
- }
- }
- }
- catch (Exception)
- {
- if (openCallback.ThrowException())
- {
- throw;
- }
- }
- OnEvent(ExtractionFinished, EventArgs.Empty, false);
- ThrowUserException();
- }
- #endregion
- #region ExtractFiles overloads
- /// <summary>
- /// Unpacks files by their indices to the specified directory.
- /// </summary>
- /// <param name="indexes">indexes of the files in the archive file table.</param>
- /// <param name="directory">Directory where the files are to be unpacked.</param>
- public void ExtractFiles(string directory, params int[] indexes)
- {
- DisposedCheck();
- ClearExceptions();
- if (!CheckIndexes(indexes))
- {
- if (!ThrowException(null, new ArgumentException("The indexes must be more or equal to zero.", nameof(indexes))))
- {
- return;
- }
- }
- InitArchiveFileData(false);
- #region Indexes stuff
- var uIndexes = new uint[indexes.Length];
- for (var i = 0; i < indexes.Length; i++)
- {
- uIndexes[i] = (uint)indexes[i];
- }
- if (uIndexes.Where(i => i >= _filesCount).Any(
- i => !ThrowException(null,
- new ArgumentOutOfRangeException(nameof(indexes),
- $"Index must be less than {_filesCount.Value.ToString(CultureInfo.InvariantCulture)}!"))))
- {
- return;
- }
- var origIndexes = new List<uint>(uIndexes);
- origIndexes.Sort();
- uIndexes = origIndexes.ToArray();
- if (_isSolid.Value)
- {
- uIndexes = SolidIndexes(uIndexes);
- }
- #endregion
- try
- {
- IInStream archiveStream;
- using ((archiveStream = GetArchiveStream(origIndexes.Count != 1)) as IDisposable)
- {
- var openCallback = GetArchiveOpenCallback();
- if (!OpenArchive(archiveStream, openCallback))
- {
- return;
- }
- try
- {
- using (var aec = GetArchiveExtractCallback(directory, (int)_filesCount, origIndexes))
- {
- try
- {
- CheckedExecute(
- _archive.Extract(uIndexes, (uint)uIndexes.Length, 0, aec),
- SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
- }
- finally
- {
- FreeArchiveExtractCallback(aec);
- }
- }
- }
- catch (Exception)
- {
- if (openCallback.ThrowException())
- {
- throw;
- }
- }
- }
- OnEvent(ExtractionFinished, EventArgs.Empty, false);
- }
- finally
- {
- if (origIndexes.Count > 1)
- {
- _archive?.Close();
- _archiveStream = null;
- _opened = false;
- }
- }
- ThrowUserException();
- }
- /// <summary>
- /// Unpacks files by their full names to the specified directory.
- /// </summary>
- /// <param name="fileNames">Full file names in the archive file table.</param>
- /// <param name="directory">Directory where the files are to be unpacked.</param>
- public void ExtractFiles(string directory, params string[] fileNames)
- {
- DisposedCheck();
- InitArchiveFileData(false);
- var indexes = new List<int>(fileNames.Length);
- var archiveFileNames = new List<string>(ArchiveFileNames);
- foreach (var fn in fileNames)
- {
- if (!archiveFileNames.Contains(fn))
- {
- if (!ThrowException(null, new ArgumentOutOfRangeException(nameof(fileNames), $"File \"{fn}\" was not found in the archive file table.")))
- {
- return;
- }
- }
- else
- {
- foreach (var afi in _archiveFileData)
- {
- if (afi.FileName == fn && !afi.IsDirectory)
- {
- indexes.Add(afi.Index);
- break;
- }
- }
- }
- }
- ExtractFiles(directory, indexes.ToArray());
- }
- /// <summary>
- /// Extracts files from the archive, giving a callback the choice what
- /// to do with each file. The order of the files is given by the archive.
- /// 7-Zip (and any other solid) archives are NOT supported.
- /// </summary>
- /// <param name="extractFileCallback">The callback to call for each file in the archive.</param>
- /// <exception cref="SevenZipExtractionFailedException">Thrown when trying to extract from solid archives.</exception>
- public void ExtractFiles(ExtractFileCallback extractFileCallback)
- {
- DisposedCheck();
- InitArchiveFileData(false);
- if (IsSolid)
- {
- throw new SevenZipExtractionFailedException("Solid archives are not supported.");
- }
- foreach (var archiveFileInfo in ArchiveFileData)
- {
- var extractFileCallbackArgs = new ExtractFileCallbackArgs(archiveFileInfo);
- extractFileCallback(extractFileCallbackArgs);
- if (extractFileCallbackArgs.CancelExtraction)
- {
- break;
- }
- if (extractFileCallbackArgs.ExtractToStream != null || extractFileCallbackArgs.ExtractToFile != null)
- {
- var callDone = false;
- try
- {
- if (extractFileCallbackArgs.ExtractToStream != null)
- {
- ExtractFile(archiveFileInfo.Index, extractFileCallbackArgs.ExtractToStream);
- }
- else
- {
- using (var file = new FileStream(extractFileCallbackArgs.ExtractToFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, 8192))
- {
- ExtractFile(archiveFileInfo.Index, file);
- }
- }
- callDone = true;
- }
- catch (Exception ex)
- {
- extractFileCallbackArgs.Exception = ex;
- extractFileCallbackArgs.Reason = ExtractFileCallbackReason.Failure;
- extractFileCallback(extractFileCallbackArgs);
- if (!ThrowException(null, ex))
- {
- return;
- }
- }
- if (callDone)
- {
- extractFileCallbackArgs.Reason = ExtractFileCallbackReason.Done;
- extractFileCallback(extractFileCallbackArgs);
- }
- }
- }
- }
- #endregion
- /// <summary>
- /// Unpacks the whole archive to the specified directory.
- /// </summary>
- /// <param name="directory">The directory where the files are to be unpacked.</param>
- public void ExtractArchive(string directory)
- {
- DisposedCheck();
- ClearExceptions();
- InitArchiveFileData(false);
- try
- {
- IInStream archiveStream;
- using ((archiveStream = GetArchiveStream(true)) as IDisposable)
- {
- var openCallback = GetArchiveOpenCallback();
- if (!OpenArchive(archiveStream, openCallback))
- {
- return;
- }
- try
- {
- using (var aec = GetArchiveExtractCallback(directory, (int)_filesCount, null))
- {
- try
- {
- CheckedExecute(
- _archive.Extract(null, uint.MaxValue, 0, aec),
- SevenZipExtractionFailedException.DEFAULT_MESSAGE, aec);
- OnEvent(ExtractionFinished, EventArgs.Empty, false);
- }
- finally
- {
- FreeArchiveExtractCallback(aec);
- }
- }
- }
- catch (Exception)
- {
- if (openCallback.ThrowException())
- {
- throw;
- }
- }
- }
- }
- finally
- {
- _archive?.Close();
- _archiveStream = null;
- _opened = false;
- }
- ThrowUserException();
- }
- #endregion
- #endif
- #region LZMA SDK functions
- internal static byte[] GetLzmaProperties(Stream inStream, out long outSize)
- {
- var lzmAproperties = new byte[5];
- if (inStream.Read(lzmAproperties, 0, 5) != 5)
- {
- throw new LzmaException();
- }
- outSize = 0;
- for (var i = 0; i < 8; i++)
- {
- var b = inStream.ReadByte();
- if (b < 0)
- {
- throw new LzmaException();
- }
- outSize |= ((long)(byte)b) << (i << 3);
- }
- return lzmAproperties;
- }
- /// <summary>
- /// Decompress the specified stream (C# inside)
- /// </summary>
- /// <param name="inStream">The source compressed stream</param>
- /// <param name="outStream">The destination uncompressed stream</param>
- /// <param name="inLength">The length of compressed data (null for inStream.Length)</param>
- /// <param name="codeProgressEvent">The event for handling the code progress</param>
- public static void DecompressStream(Stream inStream, Stream outStream, int? inLength, EventHandler<ProgressEventArgs> codeProgressEvent)
- {
- if (!inStream.CanRead || !outStream.CanWrite)
- {
- throw new ArgumentException("The specified streams are invalid.");
- }
- var decoder = new Decoder();
- var inSize = (inLength ?? inStream.Length) - inStream.Position;
- decoder.SetDecoderProperties(GetLzmaProperties(inStream, out var outSize));
- decoder.Code(inStream, outStream, inSize, outSize, new LzmaProgressCallback(inSize, codeProgressEvent));
- }
- /// <summary>
- /// Decompress byte array compressed with LZMA algorithm (C# inside)
- /// </summary>
- /// <param name="data">Byte array to decompress</param>
- /// <returns>Decompressed byte array</returns>
- public static byte[] ExtractBytes(byte[] data)
- {
- using (var inStream = new MemoryStream(data))
- {
- var decoder = new Decoder();
- inStream.Seek(0, 0);
- using (var outStream = new MemoryStream())
- {
- decoder.SetDecoderProperties(GetLzmaProperties(inStream, out var outSize));
- decoder.Code(inStream, outStream, inStream.Length - inStream.Position, outSize, null);
- return outStream.ToArray();
- }
- }
- }
- #endregion
- }
- }
|