Skip to content

Fix archive extraction to preserve directory structure when options is null#1191

Merged
6 commits merged intomasterfrom
copilot/fix-rar-extraction-issues
Feb 10, 2026
Merged

Fix archive extraction to preserve directory structure when options is null#1191
6 commits merged intomasterfrom
copilot/fix-rar-extraction-issues

Conversation

Copy link
Contributor

Copilot AI commented Feb 8, 2026

WriteToDirectory() and WriteToDirectoryAsync() flatten directory structures when called without explicit options, extracting all files to the root instead of their subdirectories.

Changes

  • Set ExtractFullPath = true by default in WriteToDirectoryInternal and WriteToDirectoryAsyncInternal
  • Add test coverage for RAR archives with subdirectories

Example

// Before: files extracted to root, subdirectories created but empty
archive.WriteToDirectory("/output");
// /output/file1.txt
// /output/file2.txt
// /output/subdir/  (empty)

// After: files extracted to correct subdirectories
archive.WriteToDirectory("/output");
// /output/subdir/file1.txt
// /output/subdir/file2.txt

Note

The async file size issue mentioned in the original report is a separate bug in RAR async stream reading, not the extraction logic. Sync extraction produces correct sizes.

Original prompt

This section details on the original issue you should resolve

<issue_title>Incorrect RAR extraction</issue_title>
<issue_description>Given the attached .rar (packaged in .zip only for uploading to GitHub):

  • IArchive.WriteToDirectory() creates the archive's folders but places all the files at the top level (not in the folders they belong to).
  • IAsyncArchive.WriteToDirectoryAsync() additionally extracts 766832.tr11dtp with the wrong file size (5MB instead of 4MB).

Using the latest prerelease version of SharpCompress (0.45.0-beta.378) on Windows, .NET 8.

sample.zip

(My workaround is to downgrade to 0.40.0 and extract using IArchive.ExtractAllEntries() -> IReader.MoveToNextEntry() + IReader.WriteEntryToDirectory().)</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits February 8, 2026 12:52
- Set default ExtractFullPath=true in WriteToDirectoryInternal methods
- Add test case with sample RAR archive containing subdirectories
- Tests verify files are extracted to correct subdirectories, not root

Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incorrect RAR extraction for folders and file sizes Fix archive extraction to preserve directory structure when options is null Feb 8, 2026
Copilot AI requested a review from adamhathcock February 8, 2026 12:57
@adamhathcock adamhathcock marked this pull request as ready for review February 9, 2026 09:05
Copilot AI review requested due to automatic review settings February 9, 2026 09:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes IArchive.WriteToDirectory() / IAsyncArchive.WriteToDirectoryAsync() default extraction behavior so omitting ExtractionOptions preserves the archive’s internal directory structure instead of flattening files into the destination root.

Changes:

  • Default ExtractionOptions to ExtractFullPath = true (and Overwrite = true) when options is null in sync/async archive extraction internals.
  • Add sync + async RAR regression tests ensuring files are extracted into subdirectories when no options are provided.
  • Update packages.lock.json (includes Microsoft.NET.ILLink.Tasks version changes).

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/SharpCompress/Archives/IArchiveExtensions.cs Sets default extraction options when options is null during non-solid/non-7z archive extraction.
src/SharpCompress/Archives/IAsyncArchiveExtensions.cs Async equivalent defaulting of extraction options during non-solid/non-7z extraction.
tests/SharpCompress.Test/Rar/RarArchiveTests.cs Adds regression test for WriteToDirectory() path preservation (and a sync size assertion).
tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs Adds regression test for WriteToDirectoryAsync() path preservation.
src/SharpCompress/packages.lock.json Lockfile changes affecting Microsoft.NET.ILLink.Tasks versions for net8.0/net10.0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +46 to +48
// When extracting an entire archive, default to extracting with full paths
options ??= new ExtractionOptions { ExtractFullPath = true, Overwrite = true };

Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new default options ??= ...ExtractFullPath=true... is only applied in WriteToDirectoryInternal, which is skipped for solid archives and SevenZip (those go through ExtractAllEntries() + reader.WriteAllToDirectory(...)). As a result, calling archive.WriteToDirectory(dest) without options can still flatten paths for solid/7z archives. Consider applying the same defaulting in the public WriteToDirectory(...) method (before the solid/7z branch), so behavior is consistent across archive types.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +63
// When extracting an entire archive, default to extracting with full paths
options ??= new ExtractionOptions { ExtractFullPath = true, Overwrite = true };

Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the sync extension: the default ExtractFullPath=true is only applied in WriteToDirectoryAsyncInternal, but solid archives and SevenZip use ExtractAllEntriesAsync() + reader.WriteAllToDirectoryAsync(...) and still receive options=null. To fully fix path flattening when callers omit options, apply the same defaulting in WriteToDirectoryAsync(...) before choosing the solid/7z path.

Copilot uses AI. Check for mistakes.
Comment on lines +725 to +731
/// <summary>
/// Tests for Issue #1050 - RAR extraction with WriteToDirectory creates folders
/// but places all files at the top level instead of in their subdirectories.
/// </summary>
[Fact]
public void Rar_Issue1050_WriteToDirectory_ExtractsToSubdirectories()
{
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR metadata references sharpcompress#1190, but this new test’s XML doc + method name refer to “Issue #1050”. This makes it hard to trace why the test exists. Update the issue number (and potentially the test name/archive filename) to match the actual tracked issue for this change.

Copilot uses AI. Check for mistakes.
Comment on lines +761 to +764
// Verify the exact file size of 766832.tr11dtp matches the archive entry size
var fileInfo = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp"));
Assert.Equal(4867620, fileInfo.Length); // Expected: 4,867,620 bytes
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says the extracted file size is being validated against the archive entry size, but the test currently asserts a hard-coded byte count. This is brittle if the test archive is ever regenerated. Prefer reading the expected size from the corresponding archive entry (or otherwise deriving it from the test data) and asserting equality against that value.

Copilot uses AI. Check for mistakes.
Comment on lines +721 to +727
/// <summary>
/// Tests for Issue #1050 - RAR extraction with WriteToDirectoryAsync creates folders
/// but places all files at the top level instead of in their subdirectories.
/// </summary>
[Fact]
public async ValueTask Rar_Issue1050_WriteToDirectoryAsync_ExtractsToSubdirectories()
{
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same mismatch as the sync test: PR metadata references #1190, but this new async test refers to “Issue #1050” in the XML doc and method name. Align the issue reference so the test is discoverable and correctly attributed.

Copilot uses AI. Check for mistakes.
adamhathcock and others added 2 commits February 9, 2026 09:10
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
public void Rar_Issue1050_WriteToDirectory_ExtractsToSubdirectories()
{
var testFile = "Rar.issue1050.rar";
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Using File.OpenRead() instead of File.Open(..., FileMode.Open) avoids the unnecessary read/write permissions. The current implementation opens the file with read/write access which is unnecessary for reading an archive.

Suggested change
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));

public async ValueTask Rar_Issue1050_WriteToDirectoryAsync_ExtractsToSubdirectories()
{
var testFile = "Rar.issue1050.rar";
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Using File.OpenRead() instead of File.Open(..., FileMode.Open) avoids the unnecessary read/write permissions. The current implementation opens the file with read/write access which is unnecessary for reading an archive.

Suggested change
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));
using var fileStream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testFile));


// Verify the exact file size of 766832.tr11dtp matches the archive entry size
var fileInfo = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Braid", "766832.tr11dtp"));
Assert.Equal(4867620, fileInfo.Length); // Expected: 4,867,620 bytes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The comment says the extracted file size is being validated, but the assertion doesn't match the comment's stated purpose. The comment says it validates "exact file size...matches the archive entry size" but actually hardcodes 4867620. Consider also verifying against entry.Size to ensure the test validates what the comment describes.

/// </summary>
[Fact]
public async ValueTask Rar_Issue1050_WriteToDirectoryAsync_ExtractsToSubdirectories()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The PR metadata references sharpcompress#1190, but this new test references Issue #1050. Consider clarifying in the test comment whether this is testing the same issue or a different one, as the mismatch may confuse future maintainers.

/// </summary>
[Fact]
public void Rar_Issue1050_WriteToDirectory_ExtractsToSubdirectories()
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The PR metadata references sharpcompress#1190, but this new test references Issue #1050. Consider clarifying in the test comment whether this is testing the same issue or a different one, as the mismatch may confuse future maintainers.

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Feb 9, 2026

Code Review Summary

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
tests/SharpCompress.Test/Rar/RarArchiveTests.cs 733 Using File.Open(..., FileMode.Open) opens with read/write access unnecessarily
tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs 729 Using File.Open(..., FileMode.Open) opens with read/write access unnecessarily

SUGGESTION

File Line Issue
tests/SharpCompress.Test/Rar/RarArchiveTests.cs 731 PR references #1190 but test references #1050 - clarify mismatch
tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs 727 PR references #1190 but test references #1050 - clarify mismatch
tests/SharpCompress.Test/Rar/RarArchiveTests.cs 763 File size assertion hardcodes value instead of comparing to entry.Size
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
src/SharpCompress/Archives/IArchiveExtensions.cs 25-33 Solid/7Zip archives use WriteAllToDirectory which doesn't get the new default options - this path may still have the bug
src/SharpCompress/Archives/IAsyncArchiveExtensions.cs 30-38 Same issue: solid/7Zip archives bypass the new default options

The fix only applies to WriteToDirectoryInternal but the solid/7Zip code path calls reader.WriteAllToDirectory(destinationDirectory, options) which passes the original options parameter (potentially null) without applying the new defaults.

Files Reviewed (6 files)
  • src/SharpCompress/Archives/IArchiveExtensions.cs - 1 observation
  • src/SharpCompress/Archives/IAsyncArchiveExtensions.cs - 1 observation
  • src/SharpCompress/packages.lock.json - package downgrades (pre-existing comment)
  • tests/SharpCompress.Test/Rar/RarArchiveTests.cs - 3 issues
  • tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs - 2 issues
  • tests/TestArchives/Archives/Rar.issue1050.rar - binary test data

Fix these issues in Kilo Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect RAR extraction

3 participants