Skip to content

Commit

Permalink
Fixed RAR extractor throwing archive corrupt errors
Browse files Browse the repository at this point in the history
Fixed the RAR extractor throwing archive corrupt errors and added tests and updated FakeRAR based on new findings.

If the current mode is Extract, then a Test, Extract, or Skip must be called after calling ReadHeader(), otherwise "Archive is corrupt" will be the exception thrown.
  • Loading branch information
siblount committed Dec 29, 2023
1 parent 845004c commit ef21ff5
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/DAZ_Installer.Core/Extraction/DPRARExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public override DPExtractionReport Extract(DPExtractSettings settings)
{
if (arcHasFile && file!.AssociatedArchive != arc)
handleError(arc, string.Format(DPArchiveErrorArgs.FileNotPartOfArchiveErrorFormat, file.Path), report, file, null);
RARHandler.Skip();
i--;
}

Expand Down
26 changes: 23 additions & 3 deletions src/DAZ_Installer.CoreTests/Extraction/Fakes/FakeRAR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ internal class FakeRAR : IRAR
public IEnumerator<RARFileInfo> FilesEnumerable;
public virtual bool Disposed { get; set; } = false;
public virtual bool Closed { get; set; } = true;
/// <summary>
/// ActionCalled is a variable used to determine if after a call to <see cref="ReadHeader"/>,
/// that either <see cref="Skip"/>, <see cref="Extract(string)"/>, or <see cref="Test"/> was called.
/// </summary>
public virtual bool ActionCalled { get; set; } = true;
public virtual RAR.OpenMode Mode { get; set; } = RAR.OpenMode.List;

public virtual event RAR.MissingVolumeHandler? MissingVolume;
Expand Down Expand Up @@ -53,6 +58,7 @@ public virtual void Extract(string destinationName)
if (CurrentFile is null) throw new InvalidOperationException("No file is selected.");
if (CurrentFile.encrypted) throw new IOException("File could not be opened."); // do not change this err message or type.
DestinationPath = Path.GetDirectoryName(destinationName) ?? string.Empty;
ActionCalled = true;
}
/// <summary>
/// Resets the enumerator and sets the <see cref="Closed"/> flag to false. Throws if <see cref="Disposed"/> is true or <see cref="Closed"/> is false
Expand All @@ -65,6 +71,7 @@ public virtual void Open(RAR.OpenMode mode)
throwIfDisposed();
if (!Closed) throw new InvalidOperationException("Archive is already open.");
Closed = false;
ActionCalled = true;
Mode = mode;
FilesEnumerable.Reset();
}
Expand All @@ -78,10 +85,11 @@ public virtual void Open(RAR.OpenMode mode)
/// <exception cref="ObjectDisposedException"/>
public virtual bool ReadHeader()
{
var a = throwIfDisposed() || throwIfClosed() || FilesEnumerable.MoveNext();
var a = throwIfDisposed() || throwIfClosed() || throwIfActionNotCalled() || FilesEnumerable.MoveNext();
if (!a) return a;
if (Mode == RAR.OpenMode.List)
NewFile?.Invoke(this, new NewFileEventArgs(FilesEnumerable.Current));
ActionCalled = false;
return true;

}
Expand All @@ -90,7 +98,11 @@ public virtual bool ReadHeader()
/// </summary>
/// <exception cref="InvalidOperationException"/>
/// <exception cref="ObjectDisposedException"/>
public virtual void Skip() => _ = throwIfDisposed() || throwIfClosed();
public virtual void Skip()
{
_ = throwIfDisposed() || throwIfClosed();
ActionCalled = true;
}
/// <summary>
/// Throws an exception if <see cref="CurrentFile"/> is null or <see cref="Disposed"/> or <see cref="Closed"/> is true.
/// </summary>
Expand All @@ -100,6 +112,7 @@ public virtual void Test()
{
_ = throwIfDisposed() && throwIfClosed();
ArgumentNullException.ThrowIfNull(FilesEnumerable.Current);
ActionCalled = true;
}

/// <summary>
Expand All @@ -108,11 +121,18 @@ public virtual void Test()
/// <exception cref="ObjectDisposedException"/>
private bool throwIfDisposed() => Disposed ? throw new ObjectDisposedException(nameof(FakeRAR)) : false;
/// <summary>
/// Throws an exception if <see cref="Cloosed"/> is true. Always returns false.
/// Throws an exception if <see cref="Closed"/> is true. Always returns false.
/// </summary>
/// <exception cref="InvalidOperationException"/>
private bool throwIfClosed() => Closed ? throw new InvalidOperationException("Archive is closed.") : false;

/// <summary>
/// Throws an exception if <see cref="ActionCalled"/> is false and the current mode is set to <see cref="RAR.OpenMode.Extract"/>. Always returns false.
/// </summary>
/// <exception cref="InvalidOperationException"/>
private bool throwIfActionNotCalled() => ActionCalled || Mode != RAR.OpenMode.Extract ? false :
throw new InvalidOperationException("Archive is corrupt.");

internal static RARFileInfo CreateFileInfoForEntity(string path) => new()
{
UnpackedSize = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,29 @@ public void ExtractTest(string path)
Assert.AreEqual(arc.FileSystem, e.FileSystem);
}

[TestMethod]
[DataRow("Test.rar")]
public void ExtractTest_PartialExtract(string path)
{
var e = new DPRARExtractor();
var fi = FileSystem.CreateFileInfo(Path.Combine(TestSubjectsPath, path));
var arc = new DPArchive(fi);
arc.PeekContents();
var skippedFile = arc.Contents.Values.ElementAt(1);
var successFiles = arc.Contents.Values.Except(new[] { skippedFile }).ToList();
var settings = new DPExtractSettings(ExtractPath, successFiles);
var expectedReport = new DPExtractionReport() { ExtractedFiles = successFiles, ErroredFiles = new(0), Settings = settings };
DPArchiveTestHelpers.SetupTargetPaths(arc, ExtractPath);

//// Testing Extract() here:
var report = DPArchiveTestHelpers.RunAndAssertExtractEvents(e, settings);

DPArchiveTestHelpers.AssertReport(expectedReport, report);
DPArchiveTestHelpers.AssertExtractFileInfosCorrectlySet(successFiles);

Assert.AreEqual(arc.FileSystem, e.FileSystem);
}

[TestMethod]
public void ExtractTest_AfterExtract()
{
Expand Down

0 comments on commit ef21ff5

Please sign in to comment.