Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/SharpCompress/Archives/Zip/ZipArchiveVolumeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal static class ZipArchiveVolumeFactory
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
Regex.Replace(m.Groups[2].Value, @"[^xz]", ""),
Regex.Replace(m.Groups[2].Value.ToLowerInvariant(), @"[^xz]", ""),
index.ToString(Constants.DefaultCultureInfo).PadLeft(2, '0')
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public partial class RarHeaderFactory
{
public async IAsyncEnumerable<IRarHeader> ReadHeadersAsync(Stream stream)
{
ResetHeaderScanState();
var markHeader = await MarkHeader
.ReadAsync(
stream,
Expand Down
12 changes: 12 additions & 0 deletions src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,20 @@ public RarHeaderFactory(StreamingMode mode, ReaderOptions options)
public StreamingMode StreamingMode { get; }
public bool IsEncrypted { get; private set; }

private void ResetHeaderScanState()
{
// Metadata probes rewind the stream. Each new scan must rediscover encryption
// from the plaintext archive headers, rather than reuse the previous scan's state.
IsEncrypted = false;
_cryptInfo = null;
_headerKey = null;
_headerPassword = null;
// Keep the password/salt/iteration-keyed KDF cache across scans.
}

public IEnumerable<IRarHeader> ReadHeaders(Stream stream)
{
ResetHeaderScanState();
var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader);
_isRar5 = markHeader.IsRar5;
yield return markHeader;
Expand Down
4 changes: 2 additions & 2 deletions src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ private int _ReadAndValidateGzipHeader()
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
totalBytesRead += n;

var extraLength = (short)(header[0] + header[1] * 256);
var extraLength = header[0] + (header[1] * 256);
var extra = new byte[extraLength];
n = _stream.Read(extra, 0, extra.Length);
if (n != extraLength)
Expand Down Expand Up @@ -815,7 +815,7 @@ CancellationToken cancellationToken
n = await _stream.ReadAsync(header, 0, 2, cancellationToken).ConfigureAwait(false); // 2-byte length field
totalBytesRead += n;

var extraLength = (short)(header[0] + header[1] * 256);
var extraLength = header[0] + (header[1] * 256);
var extra = new byte[extraLength];
n = await _stream
.ReadAsync(extra, 0, extra.Length, cancellationToken)
Expand Down
78 changes: 78 additions & 0 deletions tests/SharpCompress.Test/GZip/GZipExtraFieldTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.IO;
using System.Threading.Tasks;
using AwesomeAssertions;
using SharpCompress.Test.Mocks;
using Xunit;
using SharpCompressionMode = SharpCompress.Compressors.CompressionMode;
using SystemCompressionMode = System.IO.Compression.CompressionMode;

namespace SharpCompress.Test.GZip;

public class GZipExtraFieldTests
{
private static readonly byte[] EXPECTED_PAYLOAD = [.. "gzip payload"u8];

[Theory]
[InlineData(32767)]
[InlineData(32768)]
[InlineData(65535)]
public void GZipStream_Read_WithLargeExtraField_ShouldReturnPayload(int extraLength)
{
using var input = new MemoryStream(CreateGZipWithExtraField(extraLength));
using var gzip = new SharpCompress.Compressors.Deflate.GZipStream(
input,
SharpCompressionMode.Decompress
);
using var output = new MemoryStream();

gzip.CopyTo(output);

_ = output.ToArray().Should().Equal(EXPECTED_PAYLOAD);
}

[Theory]
[InlineData(32767)]
[InlineData(32768)]
[InlineData(65535)]
public async ValueTask GZipStream_ReadAsync_WithLargeExtraField_ShouldReturnPayload(
int extraLength
)
{
using var rawInput = new MemoryStream(CreateGZipWithExtraField(extraLength));
using var input = new AsyncOnlyStream(rawInput, disposeStream: false);
await using var gzip = new SharpCompress.Compressors.Deflate.GZipStream(
input,
SharpCompressionMode.Decompress
);
using var output = new MemoryStream();

await gzip.CopyToAsync(output);

_ = output.ToArray().Should().Equal(EXPECTED_PAYLOAD);
}

private static byte[] CreateGZipWithExtraField(int extraLength)
{
using var compressed = new MemoryStream();
using (
var gzip = new System.IO.Compression.GZipStream(
compressed,
SystemCompressionMode.Compress,
leaveOpen: true
)
)
{
gzip.Write(EXPECTED_PAYLOAD, 0, EXPECTED_PAYLOAD.Length);
}

var original = compressed.ToArray();
var result = new byte[original.Length + extraLength + 2];
Buffer.BlockCopy(original, 0, result, 0, 10);
result[3] |= 0x04;
result[10] = (byte)extraLength;
result[11] = (byte)(extraLength >> 8);
Buffer.BlockCopy(original, 10, result, 12 + extraLength, original.Length - 10);
return result;
}
}
140 changes: 140 additions & 0 deletions tests/SharpCompress.Test/Rar/RarEncryptedHeaderScanTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Archives.Rar;
using SharpCompress.Common;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
using SharpCompress.Readers;
using Xunit;

namespace SharpCompress.Test.Rar;

public class RarEncryptedHeaderScanTests : TestBase
{
[Theory]
[InlineData("Rar.encrypted_filesAndHeader.rar", false)]
[InlineData("Rar.encrypted_filesAndHeader.rar", true)]
[InlineData("Rar5.encrypted_filesAndHeader.rar", false)]
[InlineData("Rar5.encrypted_filesAndHeader.rar", true)]
public async Task Rar_EncryptedHeaders_Rescan(string filename, bool useAsync)
{
using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename));
var factory = new RarHeaderFactory(
StreamingMode.Seekable,
ReaderOptions.ForExternalStream.WithPassword("test")
);
var expected = await ReadNames(false);
Assert.Equal(6, expected.Count);
stream.Position = 0;
Assert.Single(await ReadNames(true));
stream.Position = 0;
Assert.Equal(expected, await ReadNames(false));

async Task<List<string>> ReadNames(bool stopAfterFirst)
{
var names = new List<string>();
if (useAsync)
{
await foreach (var header in factory.ReadHeadersAsync(stream))
{
if (header is FileHeader file && header.HeaderType == HeaderType.File)
{
Assert.NotNull(file.FileName);
names.Add(file.FileName);
if (stopAfterFirst)
{
break;
}
}
}
}
else
{
foreach (var header in factory.ReadHeaders(stream))
{
if (header is FileHeader file && header.HeaderType == HeaderType.File)
{
Assert.NotNull(file.FileName);
names.Add(file.FileName);
if (stopAfterFirst)
{
break;
}
}
}
}
return names;
}
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void Rar5_Solid_EncryptedHeaders_ExtractAll(bool readMetadataFirst)
{
using var stream = File.OpenRead(
Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.encrypted_headers.rar")
);
using var archive = RarArchive.OpenArchive(
stream,
ReaderOptions.ForExternalStream.WithPassword("test")
);
if (readMetadataFirst)
{
Assert.True(archive.IsSolid);
Assert.Equal(3, archive.Entries.Count());
}
using var reader = archive.ExtractAllEntries();
var count = 0;
while (reader.MoveToNextEntry())
{
Assert.Equal($"f{count:D3}.txt", reader.Entry.Key);
using var entryStream = reader.OpenEntryStream();
using var textReader = new StreamReader(entryStream);
Assert.Equal("Solid encrypted header regression test.\n", textReader.ReadToEnd());
count++;
}
Assert.Equal(3, count);
Assert.Equal(3, archive.Entries.Count());
Assert.True(stream.CanRead);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Rar5_Solid_EncryptedHeaders_ExtractAll_Async(bool readMetadataFirst)
{
using var stream = File.OpenRead(
Path.Combine(TEST_ARCHIVES_PATH, "Rar5.solid.encrypted_headers.rar")
);
await using var archive = await RarArchive.OpenAsyncArchive(
stream,
ReaderOptions.ForExternalStream.WithPassword("test"),
CancellationToken.None
);
if (readMetadataFirst)
{
Assert.True(await archive.IsSolidAsync());
Assert.Equal(3, (await archive.EntriesAsync.ToListAsync()).Count);
}
await using var reader = await archive.ExtractAllEntriesAsync();
var count = 0;
while (await reader.MoveToNextEntryAsync(CancellationToken.None))
{
Assert.Equal($"f{count:D3}.txt", reader.Entry.Key);
using var entryStream = await reader.OpenEntryStreamAsync(CancellationToken.None);
using var textReader = new StreamReader(entryStream);
Assert.Equal(
"Solid encrypted header regression test.\n",
await textReader.ReadToEndAsync()
);
count++;
}
Assert.Equal(3, count);
Assert.Equal(3, (await archive.EntriesAsync.ToListAsync()).Count);
Assert.True(stream.CanRead);
}
}
11 changes: 11 additions & 0 deletions tests/SharpCompress.Test/Zip/ZipArchiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ public void ZipX_GetParts() =>
//uses first part to search for all parts and compares against this array
ArchiveGetParts(new[] { "WinZip26.nocomp.multi.zipx", "WinZip26.nocomp.multi.zx01" });

[Fact]
public void Zip_GetParts_UpperCaseZip()
{
var zip = Path.Combine(SCRATCH2_FILES_PATH, "MULTI.ZIP");
var z01 = Path.Combine(SCRATCH2_FILES_PATH, "MULTI.z01");
File.Copy(Path.Combine(TEST_ARCHIVES_PATH, "Infozip.nocomp.multi.zip"), zip);
File.Copy(Path.Combine(TEST_ARCHIVES_PATH, "Infozip.nocomp.multi.z01"), z01);

Assert.Equal(new[] { zip, z01 }, ArchiveFactory.GetFileParts(zip));
}

[Fact]
public void Zip_GetPartsSplit() =>
//uses first part to search for all parts and compares against this array
Expand Down
Binary file not shown.
Loading