This commit is contained in:
Adam Hathcock
2025-10-14 13:24:18 +01:00
parent 8b5e87837d
commit bc7a9684ba
409 changed files with 63266 additions and 64026 deletions

View File

@@ -14,80 +14,80 @@ const string Format = "format";
const string Publish = "publish";
Target(
Clean,
["**/bin", "**/obj"],
dir =>
Clean,
["**/bin", "**/obj"],
dir =>
{
IEnumerable<string> GetDirectories(string d)
{
IEnumerable<string> GetDirectories(string d)
{
return Glob.Directories(".", d);
}
void RemoveDirectory(string d)
{
if (Directory.Exists(d))
{
Console.WriteLine(d);
Directory.Delete(d, true);
}
}
foreach (var d in GetDirectories(dir))
{
RemoveDirectory(d);
}
return Glob.Directories(".", d);
}
void RemoveDirectory(string d)
{
if (Directory.Exists(d))
{
Console.WriteLine(d);
Directory.Delete(d, true);
}
}
foreach (var d in GetDirectories(dir))
{
RemoveDirectory(d);
}
}
);
Target(
Format,
() =>
{
Run("dotnet", "tool restore");
Run("dotnet", "csharpier check .");
}
Format,
() =>
{
Run("dotnet", "tool restore");
Run("dotnet", "csharpier check .");
}
);
Target(Restore, [Format], () => Run("dotnet", "restore"));
Target(
Build,
[Restore],
() =>
{
Run("dotnet", "build src/SharpCompress/SharpCompress.csproj -c Release --no-restore");
}
Build,
[Restore],
() =>
{
Run("dotnet", "build src/SharpCompress/SharpCompress.csproj -c Release --no-restore");
}
);
Target(
Test,
[Build],
["net8.0", "net48"],
framework =>
Test,
[Build],
["net8.0", "net48"],
framework =>
{
IEnumerable<string> GetFiles(string d)
{
IEnumerable<string> GetFiles(string d)
{
return Glob.Files(".", d);
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && framework == "net48")
{
return;
}
foreach (var file in GetFiles("**/*.Test.csproj"))
{
Run("dotnet", $"test {file} -c Release -f {framework} --no-restore --verbosity=normal");
}
return Glob.Files(".", d);
}
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && framework == "net48")
{
return;
}
foreach (var file in GetFiles("**/*.Test.csproj"))
{
Run("dotnet", $"test {file} -c Release -f {framework} --no-restore --verbosity=normal");
}
}
);
Target(
Publish,
[Test],
() =>
{
Run("dotnet", "pack src/SharpCompress/SharpCompress.csproj -c Release -o artifacts/");
}
Publish,
[Test],
() =>
{
Run("dotnet", "pack src/SharpCompress/SharpCompress.csproj -c Release -o artifacts/");
}
);
Target("default", [Publish], () => Console.WriteLine("Done!"));

View File

@@ -23,463 +23,463 @@ namespace SharpCompress.Algorithms;
/// </summary>
internal static class Adler32 // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Compression/Zlib/Adler32.cs
{
/// <summary>
/// Global inlining options. Helps temporarily disable inlining for better profiler output.
/// </summary>
private static class InliningOptions // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/InliningOptions.cs
{
/// <summary>
/// Global inlining options. Helps temporarily disable inlining for better profiler output.
/// <see cref="MethodImplOptions.AggressiveInlining"/> regardless of the build conditions.
/// </summary>
private static class InliningOptions // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/InliningOptions.cs
{
/// <summary>
/// <see cref="MethodImplOptions.AggressiveInlining"/> regardless of the build conditions.
/// </summary>
public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining;
public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining;
#if PROFILING
public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;
public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;
#else
#if SUPPORTS_HOTPATH
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization;
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveOptimization;
#else
public const MethodImplOptions HotPath = MethodImplOptions.AggressiveInlining;
#endif
public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.AggressiveInlining;
#endif
public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining;
}
public const MethodImplOptions ColdPath = MethodImplOptions.NoInlining;
}
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Provides optimized static methods for trigonometric, logarithmic,
/// and other common mathematical functions.
/// </summary>
private static class Numerics // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/Numerics.cs
{
/// <summary>
/// Provides optimized static methods for trigonometric, logarithmic,
/// and other common mathematical functions.
/// Reduces elements of the vector into one sum.
/// </summary>
private static class Numerics // From https://github.com/SixLabors/ImageSharp/blob/main/src/ImageSharp/Common/Helpers/Numerics.cs
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of all elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReduceSum(Vector256<int> accumulator)
{
/// <summary>
/// Reduces elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of all elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReduceSum(Vector256<int> accumulator)
{
// Add upper lane to lower lane.
var vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper());
// Add upper lane to lower lane.
var vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper());
// Add odd to even.
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01));
// Add odd to even.
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01));
// Add high to low.
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10));
// Add high to low.
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10));
return Sse2.ConvertToInt32(vsum);
}
/// <summary>
/// Reduces even elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of even elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EvenReduceSum(Vector256<int> accumulator)
{
var vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(vsum);
}
return Sse2.ConvertToInt32(vsum);
}
#endif
/// <summary>
/// The default initial seed value of a Adler32 checksum calculation.
/// Reduces even elements of the vector into one sum.
/// </summary>
public const uint SeedValue = 1U;
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of even elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EvenReduceSum(Vector256<int> accumulator)
{
var vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
// Largest prime smaller than 65536
private const uint BASE = 65521;
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(vsum);
}
}
#endif
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
private const uint NMAX = 5552;
/// <summary>
/// The default initial seed value of a Adler32 checksum calculation.
/// </summary>
public const uint SeedValue = 1U;
// Largest prime smaller than 65536
private const uint BASE = 65521;
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
private const uint NMAX = 5552;
#if SUPPORTS_RUNTIME_INTRINSICS
private const int MinBufferSize = 64;
private const int MinBufferSize = 64;
private const int BlockSize = 1 << 5;
private const int BlockSize = 1 << 5;
// The C# compiler emits this as a compile-time constant embedded in the PE file.
private static ReadOnlySpan<byte> Tap1Tap2 =>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
private static ReadOnlySpan<byte> Tap1Tap2 =>
[
32,
31,
30,
29,
28,
27,
26,
25,
24,
23,
22,
21,
20,
19,
18,
17, // tap1
16,
15,
14,
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1 // tap2
31,
30,
29,
28,
27,
26,
25,
24,
23,
22,
21,
20,
19,
18,
17, // tap1
16,
15,
14,
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1, // tap2
];
#endif
/// <summary>
/// Calculates the Adler32 checksum with the bytes taken from the span.
/// </summary>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer) => Calculate(SeedValue, buffer);
/// <summary>
/// Calculates the Adler32 checksum with the bytes taken from the span.
/// </summary>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer) => Calculate(SeedValue, buffer);
/// <summary>
/// Calculates the Adler32 checksum with the bytes taken from the span and seed.
/// </summary>
/// <param name="adler">The input Adler32 value.</param>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public static uint Calculate(uint adler, ReadOnlySpan<byte> buffer)
/// <summary>
/// Calculates the Adler32 checksum with the bytes taken from the span and seed.
/// </summary>
/// <param name="adler">The input Adler32 value.</param>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public static uint Calculate(uint adler, ReadOnlySpan<byte> buffer)
{
if (buffer.IsEmpty)
{
if (buffer.IsEmpty)
{
return adler;
}
return adler;
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateAvx2(adler, buffer);
}
if (Avx2.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateAvx2(adler, buffer);
}
if (Ssse3.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateSse(adler, buffer);
}
if (Ssse3.IsSupported && buffer.Length >= MinBufferSize)
{
return CalculateSse(adler, buffer);
}
return CalculateScalar(adler, buffer);
return CalculateScalar(adler, buffer);
#else
return CalculateScalar(adler, buffer);
#endif
}
}
// Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
// Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
#if SUPPORTS_RUNTIME_INTRINSICS
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint adler, ReadOnlySpan<byte> buffer)
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint adler, ReadOnlySpan<byte> buffer)
{
var s1 = adler & 0xFFFF;
var s2 = (adler >> 16) & 0xFFFF;
// Process the data in blocks.
var length = (uint)buffer.Length;
var blocks = length / BlockSize;
length -= blocks * BlockSize;
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
var s1 = adler & 0xFFFF;
var s2 = (adler >> 16) & 0xFFFF;
fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2))
{
var localBufferPtr = bufferPtr;
// Process the data in blocks.
var length = (uint)buffer.Length;
var blocks = length / BlockSize;
length -= blocks * BlockSize;
// _mm_setr_epi8 on x86
var tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
var tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
var zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
while (blocks > 0)
{
fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2))
{
var localBufferPtr = bufferPtr;
var n = NMAX / BlockSize; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
}
// _mm_setr_epi8 on x86
var tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
var tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
var zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
blocks -= n;
while (blocks > 0)
{
var n = NMAX / BlockSize; /* The NMAX constraint. */
if (n > blocks)
{
n = blocks;
}
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
var v_ps = Vector128.CreateScalar(s1 * n);
var v_s2 = Vector128.CreateScalar(s2);
var v_s1 = Vector128<uint>.Zero;
blocks -= n;
do
{
// Load 32 input bytes.
var bytes1 = Sse3.LoadDquVector128(localBufferPtr);
var bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
var v_ps = Vector128.CreateScalar(s1 * n);
var v_s2 = Vector128.CreateScalar(s2);
var v_s1 = Vector128<uint>.Zero;
// Add previous block byte sum to v_ps.
v_ps = Sse2.Add(v_ps, v_s1);
do
{
// Load 32 input bytes.
var bytes1 = Sse3.LoadDquVector128(localBufferPtr);
var bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
// Horizontally add the bytes for s1, multiply-adds the
// bytes by [ 32, 31, 30, ... ] for s2.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
var mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
// Add previous block byte sum to v_ps.
v_ps = Sse2.Add(v_ps, v_s1);
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
var mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
// Horizontally add the bytes for s1, multiply-adds the
// bytes by [ 32, 31, 30, ... ] for s2.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
var mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
localBufferPtr += BlockSize;
} while (--n > 0);
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
var mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
localBufferPtr += BlockSize;
} while (--n > 0);
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
const byte S2301 = 0b1011_0001; // A B C D -> B A D C
const byte S1032 = 0b0100_1110; // A B C D -> C D A B
v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032));
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
const byte S2301 = 0b1011_0001; // A B C D -> B A D C
const byte S1032 = 0b0100_1110; // A B C D -> C D A B
s1 += v_s1.ToScalar();
v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032));
s1 += v_s1.ToScalar();
s2 = v_s2.ToScalar();
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032));
s2 = v_s2.ToScalar();
// Reduce.
s1 %= BASE;
s2 %= BASE;
}
if (length > 0)
{
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
return s1 | (s2 << 16);
}
// Reduce.
s1 %= BASE;
s2 %= BASE;
}
if (length > 0)
{
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
return s1 | (s2 << 16);
}
}
}
// Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan<byte> buffer)
// Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan<byte> buffer)
{
var s1 = adler & 0xFFFF;
var s2 = (adler >> 16) & 0xFFFF;
var length = (uint)buffer.Length;
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
{
var s1 = adler & 0xFFFF;
var s2 = (adler >> 16) & 0xFFFF;
var length = (uint)buffer.Length;
var localBufferPtr = bufferPtr;
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
var zero = Vector256<byte>.Zero;
var dot3v = Vector256.Create((short)1);
var dot2v = Vector256.Create(
32,
31,
30,
29,
28,
27,
26,
25,
24,
23,
22,
21,
20,
19,
18,
17,
16,
15,
14,
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
);
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
var vs1 = Vector256.CreateScalar(s1);
var vs2 = Vector256.CreateScalar(s2);
while (length >= 32)
{
var k = length < NMAX ? (int)length : (int)NMAX;
k -= k % 32;
length -= (uint)k;
var vs10 = vs1;
var vs3 = Vector256<uint>.Zero;
while (k >= 32)
{
var localBufferPtr = bufferPtr;
// Load 32 input bytes.
var block = Avx.LoadVector256(localBufferPtr);
var zero = Vector256<byte>.Zero;
var dot3v = Vector256.Create((short)1);
var dot2v = Vector256.Create(
32,
31,
30,
29,
28,
27,
26,
25,
24,
23,
22,
21,
20,
19,
18,
17,
16,
15,
14,
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1
);
// Sum of abs diff, resulting in 2 x int32's
var vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
// Process n blocks of data. At most NMAX data bytes can be
// processed before s2 must be reduced modulo BASE.
var vs1 = Vector256.CreateScalar(s1);
var vs2 = Vector256.CreateScalar(s2);
vs1 = Avx2.Add(vs1, vs1sad.AsUInt32());
vs3 = Avx2.Add(vs3, vs10);
while (length >= 32)
{
var k = length < NMAX ? (int)length : (int)NMAX;
k -= k % 32;
length -= (uint)k;
// sum 32 uint8s to 16 shorts.
var vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
var vs10 = vs1;
var vs3 = Vector256<uint>.Zero;
// sum 16 shorts to 8 uint32s.
var vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
while (k >= 32)
{
// Load 32 input bytes.
var block = Avx.LoadVector256(localBufferPtr);
vs2 = Avx2.Add(vsum2.AsUInt32(), vs2);
vs10 = vs1;
// Sum of abs diff, resulting in 2 x int32's
var vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
vs1 = Avx2.Add(vs1, vs1sad.AsUInt32());
vs3 = Avx2.Add(vs3, vs10);
// sum 32 uint8s to 16 shorts.
var vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
// sum 16 shorts to 8 uint32s.
var vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
vs2 = Avx2.Add(vsum2.AsUInt32(), vs2);
vs10 = vs1;
localBufferPtr += BlockSize;
k -= 32;
}
// Defer the multiplication with 32 to outside of the loop.
vs3 = Avx2.ShiftLeftLogical(vs3, 5);
vs2 = Avx2.Add(vs2, vs3);
s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32());
s2 = (uint)Numerics.ReduceSum(vs2.AsInt32());
s1 %= BASE;
s2 %= BASE;
vs1 = Vector256.CreateScalar(s1);
vs2 = Vector256.CreateScalar(s2);
}
if (length > 0)
{
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
return s1 | (s2 << 16);
}
}
private static unsafe void HandleLeftOver(
byte* localBufferPtr,
uint length,
ref uint s1,
ref uint s2
)
{
if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
localBufferPtr += BlockSize;
k -= 32;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
// Defer the multiplication with 32 to outside of the loop.
vs3 = Avx2.ShiftLeftLogical(vs3, 5);
vs2 = Avx2.Add(vs2, vs3);
if (s1 >= BASE)
{
s1 -= BASE;
}
s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32());
s2 = (uint)Numerics.ReduceSum(vs2.AsInt32());
s1 %= BASE;
s2 %= BASE;
vs1 = Vector256.CreateScalar(s1);
vs2 = Vector256.CreateScalar(s2);
}
if (length > 0)
{
HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
}
return s1 | (s2 << 16);
}
}
private static unsafe void HandleLeftOver(
byte* localBufferPtr,
uint length,
ref uint s1,
ref uint s2
)
{
if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
if (s1 >= BASE)
{
s1 -= BASE;
}
s2 %= BASE;
}
#endif
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan<byte> buffer)
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan<byte> buffer)
{
var s1 = adler & 0xFFFF;
var s2 = (adler >> 16) & 0xFFFF;
uint k;
fixed (byte* bufferPtr = buffer)
{
var s1 = adler & 0xFFFF;
var s2 = (adler >> 16) & 0xFFFF;
uint k;
var localBufferPtr = bufferPtr;
var length = (uint)buffer.Length;
fixed (byte* bufferPtr = buffer)
while (length > 0)
{
k = length < NMAX ? length : NMAX;
length -= k;
while (k >= 16)
{
var localBufferPtr = bufferPtr;
var length = (uint)buffer.Length;
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
while (length > 0)
{
k = length < NMAX ? length : NMAX;
length -= k;
while (k >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
k -= 16;
}
while (k-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
s1 %= BASE;
s2 %= BASE;
}
return (s2 << 16) | s1;
localBufferPtr += 16;
k -= 16;
}
while (k-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
s1 %= BASE;
s2 %= BASE;
}
return (s2 << 16) | s1;
}
}
}

View File

@@ -9,161 +9,155 @@ using SharpCompress.Readers;
namespace SharpCompress.Archives;
public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtractionListener
where TEntry : IArchiveEntry
where TVolume : IVolume
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private readonly LazyReadOnlyCollection<TVolume> _lazyVolumes;
private readonly LazyReadOnlyCollection<TEntry> _lazyEntries;
private bool _disposed;
private readonly SourceStream? _sourceStream;
private readonly LazyReadOnlyCollection<TVolume> _lazyVolumes;
private readonly LazyReadOnlyCollection<TEntry> _lazyEntries;
private bool _disposed;
private readonly SourceStream? _sourceStream;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionBegin;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionEnd;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionBegin;
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionEnd;
public event EventHandler<CompressedBytesReadEventArgs>? CompressedBytesRead;
public event EventHandler<FilePartExtractionBeginEventArgs>? FilePartExtractionBegin;
protected ReaderOptions ReaderOptions { get; }
public event EventHandler<CompressedBytesReadEventArgs>? CompressedBytesRead;
public event EventHandler<FilePartExtractionBeginEventArgs>? FilePartExtractionBegin;
protected ReaderOptions ReaderOptions { get; }
internal AbstractArchive(ArchiveType type, SourceStream sourceStream)
internal AbstractArchive(ArchiveType type, SourceStream sourceStream)
{
Type = type;
ReaderOptions = sourceStream.ReaderOptions;
_sourceStream = sourceStream;
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(_sourceStream));
_lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
}
internal AbstractArchive(ArchiveType type)
{
Type = type;
ReaderOptions = new();
_lazyVolumes = new LazyReadOnlyCollection<TVolume>([]);
_lazyEntries = new LazyReadOnlyCollection<TEntry>([]);
}
public ArchiveType Type { get; }
void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry) =>
EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry) =>
EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
private static Stream CheckStreams(Stream stream)
{
if (!stream.CanSeek || !stream.CanRead)
{
Type = type;
ReaderOptions = sourceStream.ReaderOptions;
_sourceStream = sourceStream;
_lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(_sourceStream));
_lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
throw new ArchiveException("Archive streams must be Readable and Seekable");
}
return stream;
}
internal AbstractArchive(ArchiveType type)
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
/// </summary>
public virtual ICollection<TEntry> Entries => _lazyEntries;
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
/// </summary>
public ICollection<TVolume> Volumes => _lazyVolumes;
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
public virtual long TotalSize => Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize);
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size);
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream sourceStream);
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
IEnumerable<IVolume> IArchive.Volumes => _lazyVolumes.Cast<IVolume>();
public virtual void Dispose()
{
if (!_disposed)
{
Type = type;
ReaderOptions = new();
_lazyVolumes = new LazyReadOnlyCollection<TVolume>([]);
_lazyEntries = new LazyReadOnlyCollection<TEntry>([]);
_lazyVolumes.ForEach(v => v.Dispose());
_lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
_sourceStream?.Dispose();
_disposed = true;
}
}
public ArchiveType Type { get; }
void IArchiveExtractionListener.EnsureEntriesLoaded()
{
_lazyEntries.EnsureFullyLoaded();
_lazyVolumes.EnsureFullyLoaded();
}
void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry) =>
EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
void IExtractionListener.FireCompressedBytesRead(
long currentPartCompressedBytes,
long compressedReadBytes
) =>
CompressedBytesRead?.Invoke(
this,
new CompressedBytesReadEventArgs(
currentFilePartCompressedBytesRead: currentPartCompressedBytes,
compressedBytesRead: compressedReadBytes
)
);
void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry) =>
EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
void IExtractionListener.FireFilePartExtractionBegin(
string name,
long size,
long compressedSize
) =>
FilePartExtractionBegin?.Invoke(
this,
new FilePartExtractionBeginEventArgs(compressedSize: compressedSize, size: size, name: name)
);
private static Stream CheckStreams(Stream stream)
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
/// extracted sequentially for the best performance.
///
/// This method will load all entry information from the archive.
///
/// WARNING: this will reuse the underlying stream for the archive. Errors may
/// occur if this is used at the same time as other extraction methods on this instance.
/// </summary>
/// <returns></returns>
public IReader ExtractAllEntries()
{
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return CreateReaderForSolidExtraction();
}
protected abstract IReader CreateReaderForSolidExtraction();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// </summary>
public virtual bool IsSolid => false;
/// <summary>
/// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive.
/// </summary>
public bool IsComplete
{
get
{
if (!stream.CanSeek || !stream.CanRead)
{
throw new ArchiveException("Archive streams must be Readable and Seekable");
}
return stream;
}
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
/// </summary>
public virtual ICollection<TEntry> Entries => _lazyEntries;
/// <summary>
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
/// </summary>
public ICollection<TVolume> Volumes => _lazyVolumes;
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
public virtual long TotalSize =>
Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize);
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
public virtual long TotalUncompressSize =>
Entries.Aggregate(0L, (total, cf) => total + cf.Size);
protected abstract IEnumerable<TVolume> LoadVolumes(SourceStream sourceStream);
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
IEnumerable<IVolume> IArchive.Volumes => _lazyVolumes.Cast<IVolume>();
public virtual void Dispose()
{
if (!_disposed)
{
_lazyVolumes.ForEach(v => v.Dispose());
_lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
_sourceStream?.Dispose();
_disposed = true;
}
}
void IArchiveExtractionListener.EnsureEntriesLoaded()
{
_lazyEntries.EnsureFullyLoaded();
_lazyVolumes.EnsureFullyLoaded();
}
void IExtractionListener.FireCompressedBytesRead(
long currentPartCompressedBytes,
long compressedReadBytes
) =>
CompressedBytesRead?.Invoke(
this,
new CompressedBytesReadEventArgs(
currentFilePartCompressedBytesRead: currentPartCompressedBytes,
compressedBytesRead: compressedReadBytes
)
);
void IExtractionListener.FireFilePartExtractionBegin(
string name,
long size,
long compressedSize
) =>
FilePartExtractionBegin?.Invoke(
this,
new FilePartExtractionBeginEventArgs(
compressedSize: compressedSize,
size: size,
name: name
)
);
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
/// extracted sequentially for the best performance.
///
/// This method will load all entry information from the archive.
///
/// WARNING: this will reuse the underlying stream for the archive. Errors may
/// occur if this is used at the same time as other extraction methods on this instance.
/// </summary>
/// <returns></returns>
public IReader ExtractAllEntries()
{
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return CreateReaderForSolidExtraction();
}
protected abstract IReader CreateReaderForSolidExtraction();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// </summary>
public virtual bool IsSolid => false;
/// <summary>
/// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive.
/// </summary>
public bool IsComplete
{
get
{
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return Entries.All(x => x.IsComplete);
}
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
return Entries.All(x => x.IsComplete);
}
}
}

View File

@@ -9,175 +9,175 @@ using SharpCompress.Writers;
namespace SharpCompress.Archives;
public abstract class AbstractWritableArchive<TEntry, TVolume>
: AbstractArchive<TEntry, TVolume>,
IWritableArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
: AbstractArchive<TEntry, TVolume>,
IWritableArchive
where TEntry : IArchiveEntry
where TVolume : IVolume
{
private class RebuildPauseDisposable : IDisposable
private class RebuildPauseDisposable : IDisposable
{
private readonly AbstractWritableArchive<TEntry, TVolume> archive;
public RebuildPauseDisposable(AbstractWritableArchive<TEntry, TVolume> archive)
{
private readonly AbstractWritableArchive<TEntry, TVolume> archive;
public RebuildPauseDisposable(AbstractWritableArchive<TEntry, TVolume> archive)
{
this.archive = archive;
archive.pauseRebuilding = true;
}
public void Dispose()
{
archive.pauseRebuilding = false;
archive.RebuildModifiedCollection();
}
this.archive = archive;
archive.pauseRebuilding = true;
}
private readonly List<TEntry> newEntries = [];
private readonly List<TEntry> removedEntries = [];
private readonly List<TEntry> modifiedEntries = [];
private bool hasModifications;
private bool pauseRebuilding;
internal AbstractWritableArchive(ArchiveType type)
: base(type) { }
internal AbstractWritableArchive(ArchiveType type, SourceStream sourceStream)
: base(type, sourceStream) { }
public override ICollection<TEntry> Entries
public void Dispose()
{
get
{
if (hasModifications)
{
return modifiedEntries;
}
return base.Entries;
}
archive.pauseRebuilding = false;
archive.RebuildModifiedCollection();
}
}
public IDisposable PauseEntryRebuilding() => new RebuildPauseDisposable(this);
private readonly List<TEntry> newEntries = [];
private readonly List<TEntry> removedEntries = [];
private void RebuildModifiedCollection()
private readonly List<TEntry> modifiedEntries = [];
private bool hasModifications;
private bool pauseRebuilding;
internal AbstractWritableArchive(ArchiveType type)
: base(type) { }
internal AbstractWritableArchive(ArchiveType type, SourceStream sourceStream)
: base(type, sourceStream) { }
public override ICollection<TEntry> Entries
{
get
{
if (pauseRebuilding)
{
return;
}
hasModifications = true;
newEntries.RemoveAll(v => removedEntries.Contains(v));
modifiedEntries.Clear();
modifiedEntries.AddRange(OldEntries.Concat(newEntries));
if (hasModifications)
{
return modifiedEntries;
}
return base.Entries;
}
}
private IEnumerable<TEntry> OldEntries => base.Entries.Where(x => !removedEntries.Contains(x));
public IDisposable PauseEntryRebuilding() => new RebuildPauseDisposable(this);
public void RemoveEntry(TEntry entry)
private void RebuildModifiedCollection()
{
if (pauseRebuilding)
{
if (!removedEntries.Contains(entry))
{
removedEntries.Add(entry);
RebuildModifiedCollection();
}
return;
}
hasModifications = true;
newEntries.RemoveAll(v => removedEntries.Contains(v));
modifiedEntries.Clear();
modifiedEntries.AddRange(OldEntries.Concat(newEntries));
}
void IWritableArchive.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry);
private IEnumerable<TEntry> OldEntries => base.Entries.Where(x => !removedEntries.Contains(x));
public TEntry AddEntry(string key, Stream source, long size = 0, DateTime? modified = null) =>
AddEntry(key, source, false, size, modified);
IArchiveEntry IWritableArchive.AddEntry(
string key,
Stream source,
bool closeStream,
long size,
DateTime? modified
) => AddEntry(key, source, closeStream, size, modified);
public TEntry AddEntry(
string key,
Stream source,
bool closeStream,
long size = 0,
DateTime? modified = null
)
public void RemoveEntry(TEntry entry)
{
if (!removedEntries.Contains(entry))
{
if (key.Length > 0 && key[0] is '/' or '\\')
{
key = key.Substring(1);
}
if (DoesKeyMatchExisting(key))
{
throw new ArchiveException("Cannot add entry with duplicate key: " + key);
}
var entry = CreateEntry(key, source, size, modified, closeStream);
newEntries.Add(entry);
RebuildModifiedCollection();
return entry;
removedEntries.Add(entry);
RebuildModifiedCollection();
}
}
private bool DoesKeyMatchExisting(string key)
void IWritableArchive.RemoveEntry(IArchiveEntry entry) => RemoveEntry((TEntry)entry);
public TEntry AddEntry(string key, Stream source, long size = 0, DateTime? modified = null) =>
AddEntry(key, source, false, size, modified);
IArchiveEntry IWritableArchive.AddEntry(
string key,
Stream source,
bool closeStream,
long size,
DateTime? modified
) => AddEntry(key, source, closeStream, size, modified);
public TEntry AddEntry(
string key,
Stream source,
bool closeStream,
long size = 0,
DateTime? modified = null
)
{
if (key.Length > 0 && key[0] is '/' or '\\')
{
foreach (var path in Entries.Select(x => x.Key))
{
if (path is null)
{
continue;
}
var p = path.Replace('/', '\\');
if (p.Length > 0 && p[0] == '\\')
{
p = p.Substring(1);
}
return string.Equals(p, key, StringComparison.OrdinalIgnoreCase);
}
return false;
key = key.Substring(1);
}
public void SaveTo(Stream stream, WriterOptions options)
if (DoesKeyMatchExisting(key))
{
//reset streams of new entries
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
SaveTo(stream, options, OldEntries, newEntries);
throw new ArchiveException("Cannot add entry with duplicate key: " + key);
}
var entry = CreateEntry(key, source, size, modified, closeStream);
newEntries.Add(entry);
RebuildModifiedCollection();
return entry;
}
protected TEntry CreateEntry(
string key,
Stream source,
long size,
DateTime? modified,
bool closeStream
)
private bool DoesKeyMatchExisting(string key)
{
foreach (var path in Entries.Select(x => x.Key))
{
if (!source.CanRead || !source.CanSeek)
{
throw new ArchiveException(
"Streams must be readable and seekable to use the Writing Archive API"
);
}
return CreateEntryInternal(key, source, size, modified, closeStream);
if (path is null)
{
continue;
}
var p = path.Replace('/', '\\');
if (p.Length > 0 && p[0] == '\\')
{
p = p.Substring(1);
}
return string.Equals(p, key, StringComparison.OrdinalIgnoreCase);
}
return false;
}
protected abstract TEntry CreateEntryInternal(
string key,
Stream source,
long size,
DateTime? modified,
bool closeStream
);
public void SaveTo(Stream stream, WriterOptions options)
{
//reset streams of new entries
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
SaveTo(stream, options, OldEntries, newEntries);
}
protected abstract void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries
);
public override void Dispose()
protected TEntry CreateEntry(
string key,
Stream source,
long size,
DateTime? modified,
bool closeStream
)
{
if (!source.CanRead || !source.CanSeek)
{
base.Dispose();
newEntries.Cast<Entry>().ForEach(x => x.Close());
removedEntries.Cast<Entry>().ForEach(x => x.Close());
modifiedEntries.Cast<Entry>().ForEach(x => x.Close());
throw new ArchiveException(
"Streams must be readable and seekable to use the Writing Archive API"
);
}
return CreateEntryInternal(key, source, size, modified, closeStream);
}
protected abstract TEntry CreateEntryInternal(
string key,
Stream source,
long size,
DateTime? modified,
bool closeStream
);
protected abstract void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<TEntry> oldEntries,
IEnumerable<TEntry> newEntries
);
public override void Dispose()
{
base.Dispose();
newEntries.Cast<Entry>().ForEach(x => x.Close());
removedEntries.Cast<Entry>().ForEach(x => x.Close());
modifiedEntries.Cast<Entry>().ForEach(x => x.Close());
}
}

View File

@@ -11,241 +11,241 @@ namespace SharpCompress.Archives;
public static class ArchiveFactory
{
/// <summary>
/// Opens an Archive for random access
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <returns></returns>
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
/// <summary>
/// Opens an Archive for random access
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
/// <returns></returns>
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
readerOptions ??= new ReaderOptions();
stream = new SharpCompressStream(stream, bufferSize: readerOptions.BufferSize);
return FindFactory<IArchiveFactory>(stream).Open(stream, readerOptions);
}
public static IWritableArchive Create(ArchiveType type)
{
var factory = Factory
.Factories.OfType<IWriteableArchiveFactory>()
.FirstOrDefault(item => item.KnownArchiveType == type);
if (factory != null)
{
readerOptions ??= new ReaderOptions();
stream = new SharpCompressStream(stream, bufferSize: readerOptions.BufferSize);
return FindFactory<IArchiveFactory>(stream).Open(stream, readerOptions);
return factory.CreateWriteableArchive();
}
public static IWritableArchive Create(ArchiveType type)
{
var factory = Factory
.Factories.OfType<IWriteableArchiveFactory>()
.FirstOrDefault(item => item.KnownArchiveType == type);
throw new NotSupportedException("Cannot create Archives of type: " + type);
}
if (factory != null)
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static IArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), options);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
return FindFactory<IArchiveFactory>(fileInfo).Open(fileInfo, options);
}
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="options"></param>
public static IArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? options = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
{
throw new InvalidOperationException("No files to open");
}
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
{
return Open(fileInfo, options);
}
fileInfo.CheckNotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
return FindFactory<IMultiArchiveFactory>(fileInfo).Open(filesArray, options);
}
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
{
streams.CheckNotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
{
throw new InvalidOperationException("No streams");
}
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
{
return Open(firstStream, options);
}
firstStream.CheckNotNull(nameof(firstStream));
options ??= new ReaderOptions();
return FindFactory<IMultiArchiveFactory>(firstStream).Open(streamsArray, options);
}
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(
string sourceArchive,
string destinationDirectory,
ExtractionOptions? options = null
)
{
using var archive = Open(sourceArchive);
archive.WriteToDirectory(destinationDirectory, options);
}
private static T FindFactory<T>(FileInfo finfo)
where T : IFactory
{
finfo.CheckNotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return FindFactory<T>(stream);
}
private static T FindFactory<T>(Stream stream)
where T : IFactory
{
stream.CheckNotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (factory.IsArchive(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new InvalidOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
public static bool IsArchive(
string filePath,
out ArchiveType? type,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
using Stream s = File.OpenRead(filePath);
return IsArchive(s, out type, bufferSize);
}
public static bool IsArchive(
Stream stream,
out ArchiveType? type,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
type = null;
stream.CheckNotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
var isArchive = factory.IsArchive(stream);
stream.Position = startPosition;
if (isArchive)
{
type = factory.KnownArchiveType;
return true;
}
}
return false;
}
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
/// </summary>
/// <param name="part1"></param>
/// <returns></returns>
public static IEnumerable<string> GetFileParts(string part1)
{
part1.CheckNotNullOrEmpty(nameof(part1));
return GetFileParts(new FileInfo(part1)).Select(a => a.FullName);
}
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
/// </summary>
/// <param name="part1"></param>
/// <returns></returns>
public static IEnumerable<FileInfo> GetFileParts(FileInfo part1)
{
part1.CheckNotNull(nameof(part1));
yield return part1;
foreach (var factory in Factory.Factories.OfType<IFactory>())
{
var i = 1;
var part = factory.GetFilePart(i++, part1);
if (part != null)
{
yield return part;
while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too
{
return factory.CreateWriteableArchive();
yield return part;
}
throw new NotSupportedException("Cannot create Archives of type: " + type);
yield break;
}
}
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static IArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), options);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
{
options ??= new ReaderOptions { LeaveStreamOpen = false };
return FindFactory<IArchiveFactory>(fileInfo).Open(fileInfo, options);
}
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="options"></param>
public static IArchive Open(IEnumerable<FileInfo> fileInfos, ReaderOptions? options = null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var filesArray = fileInfos.ToArray();
if (filesArray.Length == 0)
{
throw new InvalidOperationException("No files to open");
}
var fileInfo = filesArray[0];
if (filesArray.Length == 1)
{
return Open(fileInfo, options);
}
fileInfo.CheckNotNull(nameof(fileInfo));
options ??= new ReaderOptions { LeaveStreamOpen = false };
return FindFactory<IMultiArchiveFactory>(fileInfo).Open(filesArray, options);
}
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="streams"></param>
/// <param name="options"></param>
public static IArchive Open(IEnumerable<Stream> streams, ReaderOptions? options = null)
{
streams.CheckNotNull(nameof(streams));
var streamsArray = streams.ToArray();
if (streamsArray.Length == 0)
{
throw new InvalidOperationException("No streams");
}
var firstStream = streamsArray[0];
if (streamsArray.Length == 1)
{
return Open(firstStream, options);
}
firstStream.CheckNotNull(nameof(firstStream));
options ??= new ReaderOptions();
return FindFactory<IMultiArchiveFactory>(firstStream).Open(streamsArray, options);
}
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(
string sourceArchive,
string destinationDirectory,
ExtractionOptions? options = null
)
{
using var archive = Open(sourceArchive);
archive.WriteToDirectory(destinationDirectory, options);
}
private static T FindFactory<T>(FileInfo finfo)
where T : IFactory
{
finfo.CheckNotNull(nameof(finfo));
using Stream stream = finfo.OpenRead();
return FindFactory<T>(stream);
}
private static T FindFactory<T>(Stream stream)
where T : IFactory
{
stream.CheckNotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var factories = Factory.Factories.OfType<T>();
var startPosition = stream.Position;
foreach (var factory in factories)
{
stream.Seek(startPosition, SeekOrigin.Begin);
if (factory.IsArchive(stream))
{
stream.Seek(startPosition, SeekOrigin.Begin);
return factory;
}
}
var extensions = string.Join(", ", factories.Select(item => item.Name));
throw new InvalidOperationException(
$"Cannot determine compressed stream type. Supported Archive Formats: {extensions}"
);
}
public static bool IsArchive(
string filePath,
out ArchiveType? type,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
using Stream s = File.OpenRead(filePath);
return IsArchive(s, out type, bufferSize);
}
public static bool IsArchive(
Stream stream,
out ArchiveType? type,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
type = null;
stream.CheckNotNull(nameof(stream));
if (!stream.CanRead || !stream.CanSeek)
{
throw new ArgumentException("Stream should be readable and seekable");
}
var startPosition = stream.Position;
foreach (var factory in Factory.Factories)
{
var isArchive = factory.IsArchive(stream);
stream.Position = startPosition;
if (isArchive)
{
type = factory.KnownArchiveType;
return true;
}
}
return false;
}
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
/// </summary>
/// <param name="part1"></param>
/// <returns></returns>
public static IEnumerable<string> GetFileParts(string part1)
{
part1.CheckNotNullOrEmpty(nameof(part1));
return GetFileParts(new FileInfo(part1)).Select(a => a.FullName);
}
/// <summary>
/// From a passed in archive (zip, rar, 7z, 001), return all parts.
/// </summary>
/// <param name="part1"></param>
/// <returns></returns>
public static IEnumerable<FileInfo> GetFileParts(FileInfo part1)
{
part1.CheckNotNull(nameof(part1));
yield return part1;
foreach (var factory in Factory.Factories.OfType<IFactory>())
{
var i = 1;
var part = factory.GetFilePart(i++, part1);
if (part != null)
{
yield return part;
while ((part = factory.GetFilePart(i++, part1)) != null) //tests split too
{
yield return part;
}
yield break;
}
}
}
public static IArchiveFactory AutoFactory { get; } = new AutoArchiveFactory();
public static IArchiveFactory AutoFactory { get; } = new AutoArchiveFactory();
}

View File

@@ -6,30 +6,30 @@ namespace SharpCompress.Archives;
internal abstract class ArchiveVolumeFactory
{
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
{
FileInfo? item = null;
//split 001, 002 ...
var m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
{
FileInfo? item = null;
//split 001, 002 ...
var m = Regex.Match(part1.Name, @"^(.*\.)([0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
{
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
(index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0')
)
)
);
}
if (item != null && item.Exists)
{
return item;
}
return null;
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
(index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0')
)
)
);
}
if (item != null && item.Exists)
{
return item;
}
return null;
}
}

View File

@@ -8,23 +8,23 @@ namespace SharpCompress.Archives;
internal class AutoArchiveFactory : IArchiveFactory
{
public string Name => nameof(AutoArchiveFactory);
public string Name => nameof(AutoArchiveFactory);
public ArchiveType? KnownArchiveType => null;
public ArchiveType? KnownArchiveType => null;
public IEnumerable<string> GetSupportedExtensions() => throw new NotSupportedException();
public IEnumerable<string> GetSupportedExtensions() => throw new NotSupportedException();
public bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => throw new NotSupportedException();
public bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => throw new NotSupportedException();
public FileInfo? GetFilePart(int index, FileInfo part1) => throw new NotSupportedException();
public FileInfo? GetFilePart(int index, FileInfo part1) => throw new NotSupportedException();
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
ArchiveFactory.Open(stream, readerOptions);
public IArchive Open(Stream stream, ReaderOptions? readerOptions = null) =>
ArchiveFactory.Open(stream, readerOptions);
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
ArchiveFactory.Open(fileInfo, readerOptions);
public IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null) =>
ArchiveFactory.Open(fileInfo, readerOptions);
}

View File

@@ -14,201 +14,197 @@ namespace SharpCompress.Archives.GZip;
public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
{
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new GZipArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new GZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new GZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
return new GZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
);
}
public static GZipArchive Create() => new();
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private GZipArchive(SourceStream sourceStream)
: base(ArchiveType.GZip, sourceStream) { }
protected override IEnumerable<GZipVolume> LoadVolumes(SourceStream sourceStream)
{
sourceStream.LoadAllParts();
return sourceStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, 0));
}
public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath));
public static bool IsGZipFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new GZipArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
return false;
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
using Stream stream = fileInfo.OpenRead();
return IsGZipFile(stream);
}
public void SaveTo(string filePath) => SaveTo(new FileInfo(filePath));
public void SaveTo(FileInfo fileInfo)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
SaveTo(stream, new WriterOptions(CompressionType.GZip));
}
public static bool IsGZipFile(Stream stream)
{
// read the header on the first read
Span<byte> header = stackalloc byte[10];
// workitem 8501: handle edge case (decompress empty stream)
if (!stream.ReadFully(header))
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new GZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
return false;
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new GZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
return false;
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
return true;
}
internal GZipArchive()
: base(ArchiveType.GZip) { }
protected override GZipArchiveEntry CreateEntryInternal(
string filePath,
Stream source,
long size,
DateTime? modified,
bool closeStream
)
{
if (Entries.Count != 0)
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new GZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
);
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
}
public static GZipArchive Create() => new();
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private GZipArchive(SourceStream sourceStream)
: base(ArchiveType.GZip, sourceStream) { }
protected override IEnumerable<GZipVolume> LoadVolumes(SourceStream sourceStream)
protected override void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries
)
{
if (Entries.Count > 1)
{
sourceStream.LoadAllParts();
return sourceStream.Streams.Select(a => new GZipVolume(a, ReaderOptions, 0));
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
public static bool IsGZipFile(string filePath) => IsGZipFile(new FileInfo(filePath));
public static bool IsGZipFile(FileInfo fileInfo)
using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
if (!fileInfo.Exists)
{
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsGZipFile(stream);
using var entryStream = entry.OpenEntryStream();
writer.Write(entry.Key.NotNull("Entry Key is null"), entryStream, entry.LastModifiedTime);
}
}
public void SaveTo(string filePath) => SaveTo(new FileInfo(filePath));
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
{
var stream = volumes.Single().Stream;
yield return new GZipArchiveEntry(
this,
new GZipFilePart(stream, ReaderOptions.ArchiveEncoding)
);
}
public void SaveTo(FileInfo fileInfo)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
SaveTo(stream, new WriterOptions(CompressionType.GZip));
}
public static bool IsGZipFile(Stream stream)
{
// read the header on the first read
Span<byte> header = stackalloc byte[10];
// workitem 8501: handle edge case (decompress empty stream)
if (!stream.ReadFully(header))
{
return false;
}
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
{
return false;
}
return true;
}
internal GZipArchive()
: base(ArchiveType.GZip) { }
protected override GZipArchiveEntry CreateEntryInternal(
string filePath,
Stream source,
long size,
DateTime? modified,
bool closeStream
)
{
if (Entries.Count != 0)
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
}
protected override void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<GZipArchiveEntry> oldEntries,
IEnumerable<GZipArchiveEntry> newEntries
)
{
if (Entries.Count > 1)
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
using var entryStream = entry.OpenEntryStream();
writer.Write(
entry.Key.NotNull("Entry Key is null"),
entryStream,
entry.LastModifiedTime
);
}
}
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
{
var stream = volumes.Single().Stream;
yield return new GZipArchiveEntry(
this,
new GZipFilePart(stream, ReaderOptions.ArchiveEncoding)
);
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return GZipReader.Open(stream);
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return GZipReader.Open(stream);
}
}

View File

@@ -6,28 +6,28 @@ namespace SharpCompress.Archives.GZip;
public class GZipArchiveEntry : GZipEntry, IArchiveEntry
{
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part)
: base(part)
internal GZipArchiveEntry(GZipArchive archive, GZipFilePart? part)
: base(part)
{
Archive = archive;
}
public virtual Stream OpenEntryStream()
{
//this is to reset the stream to be read multiple times
var part = (GZipFilePart)Parts.Single();
if (part.GetRawStream().Position != part.EntryStartPosition)
{
Archive = archive;
part.GetRawStream().Position = part.EntryStartPosition;
}
return Parts.Single().GetCompressedStream().NotNull();
}
public virtual Stream OpenEntryStream()
{
//this is to reset the stream to be read multiple times
var part = (GZipFilePart)Parts.Single();
if (part.GetRawStream().Position != part.EntryStartPosition)
{
part.GetRawStream().Position = part.EntryStartPosition;
}
return Parts.Single().GetCompressedStream().NotNull();
}
#region IArchiveEntry Members
#region IArchiveEntry Members
public IArchive Archive { get; }
public IArchive Archive { get; }
public bool IsComplete => true;
public bool IsComplete => true;
#endregion
#endregion
}

View File

@@ -8,64 +8,64 @@ namespace SharpCompress.Archives.GZip;
internal sealed class GZipWritableArchiveEntry : GZipArchiveEntry, IWritableArchiveEntry
{
private readonly bool closeStream;
private readonly Stream stream;
private readonly bool closeStream;
private readonly Stream stream;
internal GZipWritableArchiveEntry(
GZipArchive archive,
Stream stream,
string path,
long size,
DateTime? lastModified,
bool closeStream
)
: base(archive, null)
internal GZipWritableArchiveEntry(
GZipArchive archive,
Stream stream,
string path,
long size,
DateTime? lastModified,
bool closeStream
)
: base(archive, null)
{
this.stream = stream;
Key = path;
Size = size;
LastModifiedTime = lastModified;
this.closeStream = closeStream;
}
public override long Crc => 0;
public override string? Key { get; }
public override long CompressedSize => 0;
public override long Size { get; }
public override DateTime? LastModifiedTime { get; }
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
Stream IWritableArchiveEntry.Stream => stream;
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()
{
if (closeStream)
{
this.stream = stream;
Key = path;
Size = size;
LastModifiedTime = lastModified;
this.closeStream = closeStream;
}
public override long Crc => 0;
public override string? Key { get; }
public override long CompressedSize => 0;
public override long Size { get; }
public override DateTime? LastModifiedTime { get; }
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
Stream IWritableArchiveEntry.Stream => stream;
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()
{
if (closeStream)
{
stream.Dispose();
}
stream.Dispose();
}
}
}

View File

@@ -7,42 +7,42 @@ namespace SharpCompress.Archives;
public interface IArchive : IDisposable
{
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionBegin;
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionEnd;
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionBegin;
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionEnd;
event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
IEnumerable<IArchiveEntry> Entries { get; }
IEnumerable<IVolume> Volumes { get; }
IEnumerable<IArchiveEntry> Entries { get; }
IEnumerable<IVolume> Volumes { get; }
ArchiveType Type { get; }
ArchiveType Type { get; }
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
/// extracted sequentially for the best performance.
/// </summary>
IReader ExtractAllEntries();
/// <summary>
/// Use this method to extract all entries in an archive in order.
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
/// extracted sequentially for the best performance.
/// </summary>
IReader ExtractAllEntries();
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// Rar Archives can be SOLID while all 7Zip archives are considered SOLID.
/// </summary>
bool IsSolid { get; }
/// <summary>
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// Rar Archives can be SOLID while all 7Zip archives are considered SOLID.
/// </summary>
bool IsSolid { get; }
/// <summary>
/// This checks to see if all the known entries have IsComplete = true
/// </summary>
bool IsComplete { get; }
/// <summary>
/// This checks to see if all the known entries have IsComplete = true
/// </summary>
bool IsComplete { get; }
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
long TotalSize { get; }
/// <summary>
/// The total size of the files compressed in the archive.
/// </summary>
long TotalSize { get; }
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
long TotalUncompressSize { get; }
/// <summary>
/// The total size of the files as uncompressed in the archive.
/// </summary>
long TotalUncompressSize { get; }
}

View File

@@ -5,19 +5,19 @@ namespace SharpCompress.Archives;
public interface IArchiveEntry : IEntry
{
/// <summary>
/// Opens the current entry as a stream that will decompress as it is read.
/// Read the entire stream or use SkipEntry on EntryStream.
/// </summary>
Stream OpenEntryStream();
/// <summary>
/// Opens the current entry as a stream that will decompress as it is read.
/// Read the entire stream or use SkipEntry on EntryStream.
/// </summary>
Stream OpenEntryStream();
/// <summary>
/// The archive can find all the parts of the archive needed to extract this entry.
/// </summary>
bool IsComplete { get; }
/// <summary>
/// The archive can find all the parts of the archive needed to extract this entry.
/// </summary>
bool IsComplete { get; }
/// <summary>
/// The archive instance this entry belongs to
/// </summary>
IArchive Archive { get; }
/// <summary>
/// The archive instance this entry belongs to
/// </summary>
IArchive Archive { get; }
}

View File

@@ -6,61 +6,61 @@ namespace SharpCompress.Archives;
public static class IArchiveEntryExtensions
{
public static void WriteTo(this IArchiveEntry archiveEntry, Stream streamToWriteTo)
public static void WriteTo(this IArchiveEntry archiveEntry, Stream streamToWriteTo)
{
if (archiveEntry.IsDirectory)
{
if (archiveEntry.IsDirectory)
{
throw new ExtractionException("Entry is a file directory and cannot be extracted.");
}
var streamListener = (IArchiveExtractionListener)archiveEntry.Archive;
streamListener.EnsureEntriesLoaded();
streamListener.FireEntryExtractionBegin(archiveEntry);
streamListener.FireFilePartExtractionBegin(
archiveEntry.Key ?? "Key",
archiveEntry.Size,
archiveEntry.CompressedSize
);
var entryStream = archiveEntry.OpenEntryStream();
using (entryStream)
{
using Stream s = new ListeningStream(streamListener, entryStream);
s.TransferTo(streamToWriteTo);
}
streamListener.FireEntryExtractionEnd(archiveEntry);
throw new ExtractionException("Entry is a file directory and cannot be extracted.");
}
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(
this IArchiveEntry entry,
string destinationDirectory,
ExtractionOptions? options = null
) =>
ExtractionMethods.WriteEntryToDirectory(
entry,
destinationDirectory,
options,
entry.WriteToFile
);
var streamListener = (IArchiveExtractionListener)archiveEntry.Archive;
streamListener.EnsureEntriesLoaded();
streamListener.FireEntryExtractionBegin(archiveEntry);
streamListener.FireFilePartExtractionBegin(
archiveEntry.Key ?? "Key",
archiveEntry.Size,
archiveEntry.CompressedSize
);
var entryStream = archiveEntry.OpenEntryStream();
using (entryStream)
{
using Stream s = new ListeningStream(streamListener, entryStream);
s.TransferTo(streamToWriteTo);
}
streamListener.FireEntryExtractionEnd(archiveEntry);
}
/// <summary>
/// Extract to specific file
/// </summary>
public static void WriteToFile(
this IArchiveEntry entry,
string destinationFileName,
ExtractionOptions? options = null
) =>
ExtractionMethods.WriteEntryToFile(
entry,
destinationFileName,
options,
(x, fm) =>
{
using var fs = File.Open(destinationFileName, fm);
entry.WriteTo(fs);
}
);
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(
this IArchiveEntry entry,
string destinationDirectory,
ExtractionOptions? options = null
) =>
ExtractionMethods.WriteEntryToDirectory(
entry,
destinationDirectory,
options,
entry.WriteToFile
);
/// <summary>
/// Extract to specific file
/// </summary>
public static void WriteToFile(
this IArchiveEntry entry,
string destinationFileName,
ExtractionOptions? options = null
) =>
ExtractionMethods.WriteEntryToFile(
entry,
destinationFileName,
options,
(x, fm) =>
{
using var fs = File.Open(destinationFileName, fm);
entry.WriteTo(fs);
}
);
}

View File

@@ -9,78 +9,78 @@ namespace SharpCompress.Archives;
public static class IArchiveExtensions
{
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(
this IArchive archive,
string destinationDirectory,
ExtractionOptions? options = null
)
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteToDirectory(
this IArchive archive,
string destinationDirectory,
ExtractionOptions? options = null
)
{
using var reader = archive.ExtractAllEntries();
reader.WriteAllToDirectory(destinationDirectory, options);
}
/// <summary>
/// Extracts the archive to the destination directory. Directories will be created as needed.
/// </summary>
/// <param name="archive">The archive to extract.</param>
/// <param name="destination">The folder to extract into.</param>
/// <param name="progressReport">Optional progress report callback.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
public static void ExtractToDirectory(
this IArchive archive,
string destination,
Action<double>? progressReport = null,
CancellationToken cancellationToken = default
)
{
// Prepare for progress reporting
var totalBytes = archive.TotalUncompressSize;
var bytesRead = 0L;
// Tracking for created directories.
var seenDirectories = new HashSet<string>();
// Extract
var entries = archive.ExtractAllEntries();
while (entries.MoveToNextEntry())
{
using var reader = archive.ExtractAllEntries();
reader.WriteAllToDirectory(destinationDirectory, options);
}
cancellationToken.ThrowIfCancellationRequested();
/// <summary>
/// Extracts the archive to the destination directory. Directories will be created as needed.
/// </summary>
/// <param name="archive">The archive to extract.</param>
/// <param name="destination">The folder to extract into.</param>
/// <param name="progressReport">Optional progress report callback.</param>
/// <param name="cancellationToken">Optional cancellation token.</param>
public static void ExtractToDirectory(
this IArchive archive,
string destination,
Action<double>? progressReport = null,
CancellationToken cancellationToken = default
)
{
// Prepare for progress reporting
var totalBytes = archive.TotalUncompressSize;
var bytesRead = 0L;
// Tracking for created directories.
var seenDirectories = new HashSet<string>();
// Extract
var entries = archive.ExtractAllEntries();
while (entries.MoveToNextEntry())
var entry = entries.Entry;
if (entry.IsDirectory)
{
var dirPath = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
if (
Path.GetDirectoryName(dirPath + "/") is { } emptyDirectory
&& seenDirectories.Add(dirPath)
)
{
cancellationToken.ThrowIfCancellationRequested();
var entry = entries.Entry;
if (entry.IsDirectory)
{
var dirPath = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
if (
Path.GetDirectoryName(dirPath + "/") is { } emptyDirectory
&& seenDirectories.Add(dirPath)
)
{
Directory.CreateDirectory(emptyDirectory);
}
continue;
}
// Create each directory if not already created
var path = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
if (Path.GetDirectoryName(path) is { } directory)
{
if (!Directory.Exists(directory) && !seenDirectories.Contains(directory))
{
Directory.CreateDirectory(directory);
seenDirectories.Add(directory);
}
}
// Write file
using var fs = File.OpenWrite(path);
entries.WriteEntryTo(fs);
// Update progress
bytesRead += entry.Size;
progressReport?.Invoke(bytesRead / (double)totalBytes);
Directory.CreateDirectory(emptyDirectory);
}
continue;
}
// Create each directory if not already created
var path = Path.Combine(destination, entry.Key.NotNull("Entry Key is null"));
if (Path.GetDirectoryName(path) is { } directory)
{
if (!Directory.Exists(directory) && !seenDirectories.Contains(directory))
{
Directory.CreateDirectory(directory);
seenDirectories.Add(directory);
}
}
// Write file
using var fs = File.OpenWrite(path);
entries.WriteEntryTo(fs);
// Update progress
bytesRead += entry.Size;
progressReport?.Invoke(bytesRead / (double)totalBytes);
}
}
}

View File

@@ -4,7 +4,7 @@ namespace SharpCompress.Archives;
internal interface IArchiveExtractionListener : IExtractionListener
{
void EnsureEntriesLoaded();
void FireEntryExtractionBegin(IArchiveEntry entry);
void FireEntryExtractionEnd(IArchiveEntry entry);
void EnsureEntriesLoaded();
void FireEntryExtractionBegin(IArchiveEntry entry);
void FireEntryExtractionEnd(IArchiveEntry entry);
}

View File

@@ -19,17 +19,17 @@ namespace SharpCompress.Archives;
/// </remarks>
public interface IArchiveFactory : IFactory
{
/// <summary>
/// Opens an Archive for random access.
/// </summary>
/// <param name="stream">An open, readable and seekable stream.</param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(Stream stream, ReaderOptions? readerOptions = null);
/// <summary>
/// Opens an Archive for random access.
/// </summary>
/// <param name="stream">An open, readable and seekable stream.</param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(Stream stream, ReaderOptions? readerOptions = null);
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo">the file to open.</param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null);
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo">the file to open.</param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null);
}

View File

@@ -20,17 +20,17 @@ namespace SharpCompress.Archives;
/// </remarks>
public interface IMultiArchiveFactory : IFactory
{
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(IReadOnlyList<Stream> streams, ReaderOptions? readerOptions = null);
/// <summary>
/// Constructor with IEnumerable FileInfo objects, multi and split support.
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(IReadOnlyList<Stream> streams, ReaderOptions? readerOptions = null);
/// <summary>
/// Constructor with IEnumerable Stream objects, multi and split support.
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(IReadOnlyList<FileInfo> fileInfos, ReaderOptions? readerOptions = null);
/// <summary>
/// Constructor with IEnumerable Stream objects, multi and split support.
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions">reading options.</param>
IArchive Open(IReadOnlyList<FileInfo> fileInfos, ReaderOptions? readerOptions = null);
}

View File

@@ -6,21 +6,21 @@ namespace SharpCompress.Archives;
public interface IWritableArchive : IArchive
{
void RemoveEntry(IArchiveEntry entry);
void RemoveEntry(IArchiveEntry entry);
IArchiveEntry AddEntry(
string key,
Stream source,
bool closeStream,
long size = 0,
DateTime? modified = null
);
IArchiveEntry AddEntry(
string key,
Stream source,
bool closeStream,
long size = 0,
DateTime? modified = null
);
void SaveTo(Stream stream, WriterOptions options);
void SaveTo(Stream stream, WriterOptions options);
/// <summary>
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
/// </summary>
/// <returns>IDisposeable to resume entry rebuilding</returns>
IDisposable PauseEntryRebuilding();
/// <summary>
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
/// </summary>
/// <returns>IDisposeable to resume entry rebuilding</returns>
IDisposable PauseEntryRebuilding();
}

View File

@@ -4,5 +4,5 @@ namespace SharpCompress.Archives;
internal interface IWritableArchiveEntry
{
Stream Stream { get; }
Stream Stream { get; }
}

View File

@@ -6,81 +6,81 @@ namespace SharpCompress.Archives;
public static class IWritableArchiveExtensions
{
public static void AddEntry(
this IWritableArchive writableArchive,
string entryPath,
string filePath
)
public static void AddEntry(
this IWritableArchive writableArchive,
string entryPath,
string filePath
)
{
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Exists)
{
var fileInfo = new FileInfo(filePath);
if (!fileInfo.Exists)
{
throw new FileNotFoundException("Could not AddEntry: " + filePath);
}
throw new FileNotFoundException("Could not AddEntry: " + filePath);
}
writableArchive.AddEntry(
entryPath,
new FileInfo(filePath).OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
public static void SaveTo(
this IWritableArchive writableArchive,
string filePath,
WriterOptions options
) => writableArchive.SaveTo(new FileInfo(filePath), options);
public static void SaveTo(
this IWritableArchive writableArchive,
FileInfo fileInfo,
WriterOptions options
)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
writableArchive.SaveTo(stream, options);
}
public static void AddAllFromDirectory(
this IWritableArchive writableArchive,
string filePath,
string searchPattern = "*.*",
SearchOption searchOption = SearchOption.AllDirectories
)
{
using (writableArchive.PauseEntryRebuilding())
{
foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption))
{
var fileInfo = new FileInfo(path);
writableArchive.AddEntry(
entryPath,
new FileInfo(filePath).OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
path.Substring(filePath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
}
}
public static void SaveTo(
this IWritableArchive writableArchive,
string filePath,
WriterOptions options
) => writableArchive.SaveTo(new FileInfo(filePath), options);
public static void SaveTo(
this IWritableArchive writableArchive,
FileInfo fileInfo,
WriterOptions options
)
public static IArchiveEntry AddEntry(
this IWritableArchive writableArchive,
string key,
FileInfo fileInfo
)
{
if (!fileInfo.Exists)
{
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
writableArchive.SaveTo(stream, options);
}
public static void AddAllFromDirectory(
this IWritableArchive writableArchive,
string filePath,
string searchPattern = "*.*",
SearchOption searchOption = SearchOption.AllDirectories
)
{
using (writableArchive.PauseEntryRebuilding())
{
foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption))
{
var fileInfo = new FileInfo(path);
writableArchive.AddEntry(
path.Substring(filePath.Length),
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
}
}
public static IArchiveEntry AddEntry(
this IWritableArchive writableArchive,
string key,
FileInfo fileInfo
)
{
if (!fileInfo.Exists)
{
throw new ArgumentException("FileInfo does not exist.");
}
return writableArchive.AddEntry(
key,
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
throw new ArgumentException("FileInfo does not exist.");
}
return writableArchive.AddEntry(
key,
fileInfo.OpenRead(),
true,
fileInfo.Length,
fileInfo.LastWriteTime
);
}
}

View File

@@ -2,5 +2,5 @@ namespace SharpCompress.Archives;
public interface IWriteableArchiveFactory : Factories.IFactory
{
IWritableArchive CreateWriteableArchive();
IWritableArchive CreateWriteableArchive();
}

View File

@@ -14,26 +14,26 @@ namespace SharpCompress.Archives.Rar;
/// </summary>
internal class FileInfoRarArchiveVolume : RarVolume
{
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
{
FileInfo = fileInfo;
FileParts = GetVolumeFileParts().ToArray().ToReadOnly();
}
internal FileInfoRarArchiveVolume(FileInfo fileInfo, ReaderOptions options, int index)
: base(StreamingMode.Seekable, fileInfo.OpenRead(), FixOptions(options), index)
{
FileInfo = fileInfo;
FileParts = GetVolumeFileParts().ToArray().ToReadOnly();
}
private static ReaderOptions FixOptions(ReaderOptions options)
{
//make sure we're closing streams with fileinfo
options.LeaveStreamOpen = false;
return options;
}
private static ReaderOptions FixOptions(ReaderOptions options)
{
//make sure we're closing streams with fileinfo
options.LeaveStreamOpen = false;
return options;
}
internal ReadOnlyCollection<RarFilePart> FileParts { get; }
internal ReadOnlyCollection<RarFilePart> FileParts { get; }
internal FileInfo FileInfo { get; }
internal FileInfo FileInfo { get; }
internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) =>
new FileInfoRarFilePart(this, ReaderOptions.Password, markHeader, fileHeader, FileInfo);
internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) =>
new FileInfoRarFilePart(this, ReaderOptions.Password, markHeader, fileHeader, FileInfo);
internal override IEnumerable<RarFilePart> ReadFileParts() => FileParts;
internal override IEnumerable<RarFilePart> ReadFileParts() => FileParts;
}

View File

@@ -5,20 +5,20 @@ namespace SharpCompress.Archives.Rar;
internal sealed class FileInfoRarFilePart : SeekableFilePart
{
internal FileInfoRarFilePart(
FileInfoRarArchiveVolume volume,
string? password,
MarkHeader mh,
FileHeader fh,
FileInfo fi
)
: base(mh, fh, volume.Index, volume.Stream, password)
{
FileInfo = fi;
}
internal FileInfoRarFilePart(
FileInfoRarArchiveVolume volume,
string? password,
MarkHeader mh,
FileHeader fh,
FileInfo fi
)
: base(mh, fh, volume.Index, volume.Stream, password)
{
FileInfo = fi;
}
internal FileInfo FileInfo { get; }
internal FileInfo FileInfo { get; }
internal override string FilePartName =>
"Rar File: " + FileInfo.FullName + " File Entry: " + FileHeader.FileName;
internal override string FilePartName =>
"Rar File: " + FileInfo.FullName + " File Entry: " + FileHeader.FileName;
}

View File

@@ -4,15 +4,15 @@ namespace SharpCompress.Archives.Rar;
public static class RarArchiveExtensions
{
/// <summary>
/// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing.
/// </summary>
public static bool IsFirstVolume(this RarArchive archive) =>
archive.Volumes.First().IsFirstVolume;
/// <summary>
/// RarArchive is the first volume of a multi-part archive. If MultipartVolume is true and IsFirstVolume is false then the first volume file must be missing.
/// </summary>
public static bool IsFirstVolume(this RarArchive archive) =>
archive.Volumes.First().IsFirstVolume;
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public static bool IsMultipartVolume(this RarArchive archive) =>
archive.Volumes.First().IsMultiVolume;
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public static bool IsMultipartVolume(this RarArchive archive) =>
archive.Volumes.First().IsMultiVolume;
}

View File

@@ -15,195 +15,190 @@ namespace SharpCompress.Archives.Rar;
public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
{
private bool _disposed;
internal Lazy<IRarUnpack> UnpackV2017 { get; } =
new(() => new Unpack());
internal Lazy<IRarUnpack> UnpackV1 { get; } = new(() => new Compressors.Rar.UnpackV1.Unpack());
private bool _disposed;
internal Lazy<IRarUnpack> UnpackV2017 { get; } = new(() => new Unpack());
internal Lazy<IRarUnpack> UnpackV1 { get; } = new(() => new Compressors.Rar.UnpackV1.Unpack());
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private RarArchive(SourceStream sourceStream)
: base(ArchiveType.Rar, sourceStream) { }
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private RarArchive(SourceStream sourceStream)
: base(ArchiveType.Rar, sourceStream) { }
public override void Dispose()
public override void Dispose()
{
if (!_disposed)
{
if (!_disposed)
{
if (UnpackV1.IsValueCreated && UnpackV1.Value is IDisposable unpackV1)
{
unpackV1.Dispose();
}
if (UnpackV1.IsValueCreated && UnpackV1.Value is IDisposable unpackV1)
{
unpackV1.Dispose();
}
_disposed = true;
base.Dispose();
}
_disposed = true;
base.Dispose();
}
}
protected override IEnumerable<RarArchiveEntry> LoadEntries(IEnumerable<RarVolume> volumes) =>
RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions);
protected override IEnumerable<RarVolume> LoadVolumes(SourceStream sourceStream)
{
sourceStream.LoadAllParts(); //request all streams
var streams = sourceStream.Streams.ToArray();
var i = 0;
if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split
{
sourceStream.IsVolumes = true;
streams[1].Position = 0;
sourceStream.Position = 0;
return sourceStream.Streams.Select(a => new StreamRarArchiveVolume(a, ReaderOptions, i++));
}
protected override IEnumerable<RarArchiveEntry> LoadEntries(IEnumerable<RarVolume> volumes) =>
RarArchiveEntryFactory.GetEntries(this, volumes, ReaderOptions);
//split mode or single file
return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable();
}
protected override IEnumerable<RarVolume> LoadVolumes(SourceStream sourceStream)
protected override IReader CreateReaderForSolidExtraction()
{
if (this.IsMultipartVolume())
{
sourceStream.LoadAllParts(); //request all streams
var streams = sourceStream.Streams.ToArray();
var i = 0;
if (streams.Length > 1 && IsRarFile(streams[1], ReaderOptions)) //test part 2 - true = multipart not split
{
sourceStream.IsVolumes = true;
streams[1].Position = 0;
sourceStream.Position = 0;
return sourceStream.Streams.Select(a => new StreamRarArchiveVolume(
a,
ReaderOptions,
i++
));
}
//split mode or single file
return new StreamRarArchiveVolume(sourceStream, ReaderOptions, i++).AsEnumerable();
var streams = Volumes.Select(volume =>
{
volume.Stream.Position = 0;
return volume.Stream;
});
return RarReader.Open(streams, ReaderOptions);
}
protected override IReader CreateReaderForSolidExtraction()
{
if (this.IsMultipartVolume())
{
var streams = Volumes.Select(volume =>
{
volume.Stream.Position = 0;
return volume.Stream;
});
return RarReader.Open(streams, ReaderOptions);
}
var stream = Volumes.First().Stream;
stream.Position = 0;
return RarReader.Open(stream, ReaderOptions);
}
var stream = Volumes.First().Stream;
stream.Position = 0;
return RarReader.Open(stream, ReaderOptions);
public override bool IsSolid => Volumes.First().IsSolidArchive;
public virtual int MinVersion => Volumes.First().MinVersion;
public virtual int MaxVersion => Volumes.First().MaxVersion;
#region Creation
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static RarArchive Open(string filePath, ReaderOptions? options = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
var fileInfo = new FileInfo(filePath);
return new RarArchive(
new SourceStream(
fileInfo,
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
options ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new RarArchive(
new SourceStream(
fileInfo,
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
options ?? new ReaderOptions()
)
);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
public static RarArchive Open(Stream stream, ReaderOptions? options = null)
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
public override bool IsSolid => Volumes.First().IsSolidArchive;
return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions()));
}
public virtual int MinVersion => Volumes.First().MinVersion;
public virtual int MaxVersion => Volumes.First().MaxVersion;
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static RarArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new RarArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
#region Creation
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="options"></param>
public static RarArchive Open(string filePath, ReaderOptions? options = null)
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static RarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new RarArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath));
public static bool IsRarFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
var fileInfo = new FileInfo(filePath);
return new RarArchive(
new SourceStream(
fileInfo,
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
options ?? new ReaderOptions()
)
);
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsRarFile(stream);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="options"></param>
public static RarArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
public static bool IsRarFile(Stream stream, ReaderOptions? options = null)
{
try
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new RarArchive(
new SourceStream(
fileInfo,
i => RarArchiveVolumeFactory.GetFilePart(i, fileInfo),
options ?? new ReaderOptions()
)
);
MarkHeader.Read(stream, true, false);
return true;
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
public static RarArchive Open(Stream stream, ReaderOptions? options = null)
catch
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
return new RarArchive(new SourceStream(stream, _ => null, options ?? new ReaderOptions()));
return false;
}
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static RarArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new RarArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static RarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new RarArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
public static bool IsRarFile(string filePath) => IsRarFile(new FileInfo(filePath));
public static bool IsRarFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsRarFile(stream);
}
public static bool IsRarFile(Stream stream, ReaderOptions? options = null)
{
try
{
MarkHeader.Read(stream, true, false);
return true;
}
catch
{
return false;
}
}
#endregion
#endregion
}

View File

@@ -12,96 +12,96 @@ namespace SharpCompress.Archives.Rar;
public class RarArchiveEntry : RarEntry, IArchiveEntry
{
private readonly ICollection<RarFilePart> parts;
private readonly RarArchive archive;
private readonly ReaderOptions readerOptions;
private readonly ICollection<RarFilePart> parts;
private readonly RarArchive archive;
private readonly ReaderOptions readerOptions;
internal RarArchiveEntry(
RarArchive archive,
IEnumerable<RarFilePart> parts,
ReaderOptions readerOptions
)
internal RarArchiveEntry(
RarArchive archive,
IEnumerable<RarFilePart> parts,
ReaderOptions readerOptions
)
{
this.parts = parts.ToList();
this.archive = archive;
this.readerOptions = readerOptions;
IsSolid = FileHeader.IsSolid;
}
public override CompressionType CompressionType => CompressionType.Rar;
public IArchive Archive => archive;
internal override IEnumerable<FilePart> Parts => parts;
internal override FileHeader FileHeader => parts.First().FileHeader;
public override long Crc
{
get
{
this.parts = parts.ToList();
this.archive = archive;
this.readerOptions = readerOptions;
IsSolid = FileHeader.IsSolid;
CheckIncomplete();
return BitConverter.ToUInt32(
parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc.NotNull(),
0
);
}
}
public override long Size
{
get
{
CheckIncomplete();
return parts.First().FileHeader.UncompressedSize;
}
}
public override long CompressedSize
{
get
{
CheckIncomplete();
return parts.Aggregate(0L, (total, fp) => total + fp.FileHeader.CompressedSize);
}
}
public Stream OpenEntryStream()
{
if (IsRarV3)
{
return new RarStream(
archive.UnpackV1.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
public override CompressionType CompressionType => CompressionType.Rar;
return new RarStream(
archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
public IArchive Archive => archive;
internal override IEnumerable<FilePart> Parts => parts;
internal override FileHeader FileHeader => parts.First().FileHeader;
public override long Crc
public bool IsComplete
{
get
{
get
{
CheckIncomplete();
return BitConverter.ToUInt32(
parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc.NotNull(),
0
);
}
}
public override long Size
{
get
{
CheckIncomplete();
return parts.First().FileHeader.UncompressedSize;
}
}
public override long CompressedSize
{
get
{
CheckIncomplete();
return parts.Aggregate(0L, (total, fp) => total + fp.FileHeader.CompressedSize);
}
}
public Stream OpenEntryStream()
{
if (IsRarV3)
{
return new RarStream(
archive.UnpackV1.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
return new RarStream(
archive.UnpackV2017.Value,
FileHeader,
new MultiVolumeReadOnlyStream(Parts.Cast<RarFilePart>(), archive)
);
}
public bool IsComplete
{
get
{
#pragma warning disable CA1851
var headers = parts.Select(x => x.FileHeader);
return !headers.First().IsSplitBefore && !headers.Last().IsSplitAfter;
var headers = parts.Select(x => x.FileHeader);
return !headers.First().IsSplitBefore && !headers.Last().IsSplitAfter;
#pragma warning restore CA1851
}
}
}
private void CheckIncomplete()
private void CheckIncomplete()
{
if (!readerOptions.DisableCheckIncomplete && !IsComplete)
{
if (!readerOptions.DisableCheckIncomplete && !IsComplete)
{
throw new IncompleteArchiveException(
"ArchiveEntry is incomplete and cannot perform this operation."
);
}
throw new IncompleteArchiveException(
"ArchiveEntry is incomplete and cannot perform this operation."
);
}
}
}

View File

@@ -6,47 +6,47 @@ namespace SharpCompress.Archives.Rar;
internal static class RarArchiveEntryFactory
{
private static IEnumerable<RarFilePart> GetFileParts(IEnumerable<RarVolume> parts)
private static IEnumerable<RarFilePart> GetFileParts(IEnumerable<RarVolume> parts)
{
foreach (var rarPart in parts)
{
foreach (var rarPart in parts)
{
foreach (var fp in rarPart.ReadFileParts())
{
yield return fp;
}
}
foreach (var fp in rarPart.ReadFileParts())
{
yield return fp;
}
}
}
private static IEnumerable<IEnumerable<RarFilePart>> GetMatchedFileParts(
IEnumerable<RarVolume> parts
)
private static IEnumerable<IEnumerable<RarFilePart>> GetMatchedFileParts(
IEnumerable<RarVolume> parts
)
{
var groupedParts = new List<RarFilePart>();
foreach (var fp in GetFileParts(parts))
{
var groupedParts = new List<RarFilePart>();
foreach (var fp in GetFileParts(parts))
{
groupedParts.Add(fp);
groupedParts.Add(fp);
if (!fp.FileHeader.IsSplitAfter)
{
yield return groupedParts;
groupedParts = [];
}
}
if (groupedParts.Count > 0)
{
yield return groupedParts;
}
if (!fp.FileHeader.IsSplitAfter)
{
yield return groupedParts;
groupedParts = [];
}
}
internal static IEnumerable<RarArchiveEntry> GetEntries(
RarArchive archive,
IEnumerable<RarVolume> rarParts,
ReaderOptions readerOptions
)
if (groupedParts.Count > 0)
{
foreach (var groupedParts in GetMatchedFileParts(rarParts))
{
yield return new RarArchiveEntry(archive, groupedParts, readerOptions);
}
yield return groupedParts;
}
}
internal static IEnumerable<RarArchiveEntry> GetEntries(
RarArchive archive,
IEnumerable<RarVolume> rarParts,
ReaderOptions readerOptions
)
{
foreach (var groupedParts in GetMatchedFileParts(rarParts))
{
yield return new RarArchiveEntry(archive, groupedParts, readerOptions);
}
}
}

View File

@@ -6,55 +6,55 @@ namespace SharpCompress.Archives.Rar;
internal static class RarArchiveVolumeFactory
{
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
{
FileInfo? item = null;
//new style rar - ..part1 | /part01 | part001 ....
var m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase);
if (m.Success)
{
FileInfo? item = null;
//new style rar - ..part1 | /part01 | part001 ....
var m = Regex.Match(part1.Name, @"^(.*\.part)([0-9]+)(\.rar)$", RegexOptions.IgnoreCase);
if (m.Success)
{
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
(index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'),
m.Groups[3].Value
)
)
);
}
else
{
//old style - ...rar, .r00, .r01 ...
m = Regex.Match(part1.Name, @"^(.*\.)([r-z{])(ar|[0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
{
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
index == 0
? m.Groups[2].Value + m.Groups[3].Value
: (char)(m.Groups[2].Value[0] + ((index - 1) / 100))
+ (index - 1).ToString("D4").Substring(2)
)
)
);
}
else //split .001, .002 ....
{
return ArchiveVolumeFactory.GetFilePart(index, part1);
}
}
if (item != null && item.Exists)
{
return item;
}
return null; //no more items
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
(index + 1).ToString().PadLeft(m.Groups[2].Value.Length, '0'),
m.Groups[3].Value
)
)
);
}
else
{
//old style - ...rar, .r00, .r01 ...
m = Regex.Match(part1.Name, @"^(.*\.)([r-z{])(ar|[0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
{
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
index == 0
? m.Groups[2].Value + m.Groups[3].Value
: (char)(m.Groups[2].Value[0] + ((index - 1) / 100))
+ (index - 1).ToString("D4").Substring(2)
)
)
);
}
else //split .001, .002 ....
{
return ArchiveVolumeFactory.GetFilePart(index, part1);
}
}
if (item != null && item.Exists)
{
return item;
}
return null; //no more items
}
}

View File

@@ -6,40 +6,40 @@ namespace SharpCompress.Archives.Rar;
internal class SeekableFilePart : RarFilePart
{
private readonly Stream _stream;
private readonly string? _password;
private readonly Stream _stream;
private readonly string? _password;
internal SeekableFilePart(
MarkHeader mh,
FileHeader fh,
int index,
Stream stream,
string? password
)
: base(mh, fh, index)
internal SeekableFilePart(
MarkHeader mh,
FileHeader fh,
int index,
Stream stream,
string? password
)
: base(mh, fh, index)
{
_stream = stream;
_password = password;
}
internal override Stream GetCompressedStream()
{
_stream.Position = FileHeader.DataStartPosition;
if (FileHeader.R4Salt != null)
{
_stream = stream;
_password = password;
var cryptKey = new CryptKey3(_password!);
return new RarCryptoWrapper(_stream, FileHeader.R4Salt, cryptKey);
}
internal override Stream GetCompressedStream()
if (FileHeader.Rar5CryptoInfo != null)
{
_stream.Position = FileHeader.DataStartPosition;
if (FileHeader.R4Salt != null)
{
var cryptKey = new CryptKey3(_password!);
return new RarCryptoWrapper(_stream, FileHeader.R4Salt, cryptKey);
}
if (FileHeader.Rar5CryptoInfo != null)
{
var cryptKey = new CryptKey5(_password!, FileHeader.Rar5CryptoInfo);
return new RarCryptoWrapper(_stream, FileHeader.Rar5CryptoInfo.Salt, cryptKey);
}
return _stream;
var cryptKey = new CryptKey5(_password!, FileHeader.Rar5CryptoInfo);
return new RarCryptoWrapper(_stream, FileHeader.Rar5CryptoInfo.Salt, cryptKey);
}
internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName;
return _stream;
}
internal override string FilePartName => "Unknown Stream - File Entry: " + FileHeader.FileName;
}

View File

@@ -9,11 +9,11 @@ namespace SharpCompress.Archives.Rar;
internal class StreamRarArchiveVolume : RarVolume
{
internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index)
: base(StreamingMode.Seekable, stream, options, index) { }
internal StreamRarArchiveVolume(Stream stream, ReaderOptions options, int index)
: base(StreamingMode.Seekable, stream, options, index) { }
internal override IEnumerable<RarFilePart> ReadFileParts() => GetVolumeFileParts();
internal override IEnumerable<RarFilePart> ReadFileParts() => GetVolumeFileParts();
internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) =>
new SeekableFilePart(markHeader, fileHeader, Index, Stream, ReaderOptions.Password);
internal override RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader) =>
new SeekableFilePart(markHeader, fileHeader, Index, Stream, ReaderOptions.Password);
}

View File

@@ -12,268 +12,265 @@ namespace SharpCompress.Archives.SevenZip;
public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVolume>
{
private ArchiveDatabase? _database;
private ArchiveDatabase? _database;
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull("fileInfo");
return new SevenZipArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new SevenZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new SevenZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull("stream");
if (stream is not { CanSeek: true })
{
filePath.CheckNotNullOrEmpty("filePath");
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
return new SevenZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
);
}
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private SevenZipArchive(SourceStream sourceStream)
: base(ArchiveType.SevenZip, sourceStream) { }
protected override IEnumerable<SevenZipVolume> LoadVolumes(SourceStream sourceStream)
{
sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams
return new SevenZipVolume(sourceStream, ReaderOptions).AsEnumerable(); //simple single volume or split, multivolume not supported
}
public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath));
public static bool IsSevenZipFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
fileInfo.CheckNotNull("fileInfo");
return new SevenZipArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsSevenZipFile(stream);
}
internal SevenZipArchive()
: base(ArchiveType.SevenZip) { }
protected override IEnumerable<SevenZipArchiveEntry> LoadEntries(
IEnumerable<SevenZipVolume> volumes
)
{
var stream = volumes.Single().Stream;
LoadFactory(stream);
if (_database is null)
{
return [];
}
var entries = new SevenZipArchiveEntry[_database._files.Count];
for (var i = 0; i < _database._files.Count; i++)
{
var file = _database._files[i];
entries[i] = new SevenZipArchiveEntry(
this,
new SevenZipFilePart(stream, _database, i, file, ReaderOptions.ArchiveEncoding)
);
}
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))
{
var isSolid = false;
foreach (var entry in group)
{
entry.IsSolid = isSolid;
isSolid = true; //mark others in this group as solid - same as rar behaviour.
}
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
return entries;
}
private void LoadFactory(Stream stream)
{
if (_database is null)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new SevenZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
stream.Position = 0;
var reader = new ArchiveReader();
reader.Open(stream, lookForHeader: ReaderOptions.LookForHeader);
_database = reader.ReadDatabase(new PasswordProvider(ReaderOptions.Password));
}
}
public static bool IsSevenZipFile(Stream stream)
{
try
{
return SignatureMatch(stream);
}
catch
{
return false;
}
}
private static ReadOnlySpan<byte> Signature => [(byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C];
private static bool SignatureMatch(Stream stream)
{
var reader = new BinaryReader(stream);
ReadOnlySpan<byte> signatureBytes = reader.ReadBytes(6);
return signatureBytes.SequenceEqual(Signature);
}
protected override IReader CreateReaderForSolidExtraction() =>
new SevenZipReader(ReaderOptions, this);
public override bool IsSolid =>
Entries
.Where(x => !x.IsDirectory)
.GroupBy(x => x.FilePart.Folder)
.Any(folder => folder.Count() > 1);
public override long TotalSize =>
_database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0;
private sealed class SevenZipReader : AbstractReader<SevenZipEntry, SevenZipVolume>
{
private readonly SevenZipArchive _archive;
private CFolder? _currentFolder;
private Stream? _currentStream;
private CFileItem? _currentItem;
internal SevenZipReader(ReaderOptions readerOptions, SevenZipArchive archive)
: base(readerOptions, ArchiveType.SevenZip)
{
_archive = archive;
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(
IEnumerable<Stream> streams,
ReaderOptions? readerOptions = null
)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new SevenZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
public override SevenZipVolume Volume => _archive.Volumes.Single();
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static SevenZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
protected override IEnumerable<SevenZipEntry> GetEntries(Stream stream)
{
stream.CheckNotNull("stream");
if (stream is not { CanSeek: true })
var entries = _archive.Entries.ToList();
stream.Position = 0;
foreach (var dir in entries.Where(x => x.IsDirectory))
{
yield return dir;
}
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))
{
_currentFolder = group.Key;
if (group.Key is null)
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
_currentStream = Stream.Null;
}
return new SevenZipArchive(
new SourceStream(stream, _ => null, readerOptions ?? new ReaderOptions())
);
else
{
_currentStream = _archive._database?.GetFolderStream(
stream,
_currentFolder,
new PasswordProvider(Options.Password)
);
}
foreach (var entry in group)
{
_currentItem = entry.FilePart.Header;
yield return entry;
}
}
}
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private SevenZipArchive(SourceStream sourceStream)
: base(ArchiveType.SevenZip, sourceStream) { }
protected override EntryStream GetEntryStream() =>
CreateEntryStream(
new ReadOnlySubStream(
_currentStream.NotNull("currentStream is not null"),
_currentItem?.Size ?? 0
)
);
}
protected override IEnumerable<SevenZipVolume> LoadVolumes(SourceStream sourceStream)
private class PasswordProvider : IPasswordProvider
{
private readonly string? _password;
public PasswordProvider(string? password)
{
sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams
return new SevenZipVolume(sourceStream, ReaderOptions).AsEnumerable(); //simple single volume or split, multivolume not supported
_password = password;
}
public static bool IsSevenZipFile(string filePath) => IsSevenZipFile(new FileInfo(filePath));
public static bool IsSevenZipFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsSevenZipFile(stream);
}
internal SevenZipArchive()
: base(ArchiveType.SevenZip) { }
protected override IEnumerable<SevenZipArchiveEntry> LoadEntries(
IEnumerable<SevenZipVolume> volumes
)
{
var stream = volumes.Single().Stream;
LoadFactory(stream);
if (_database is null)
{
return [];
}
var entries = new SevenZipArchiveEntry[_database._files.Count];
for (var i = 0; i < _database._files.Count; i++)
{
var file = _database._files[i];
entries[i] = new SevenZipArchiveEntry(
this,
new SevenZipFilePart(stream, _database, i, file, ReaderOptions.ArchiveEncoding)
);
}
foreach (var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder))
{
var isSolid = false;
foreach (var entry in group)
{
entry.IsSolid = isSolid;
isSolid = true; //mark others in this group as solid - same as rar behaviour.
}
}
return entries;
}
private void LoadFactory(Stream stream)
{
if (_database is null)
{
stream.Position = 0;
var reader = new ArchiveReader();
reader.Open(stream, lookForHeader: ReaderOptions.LookForHeader);
_database = reader.ReadDatabase(new PasswordProvider(ReaderOptions.Password));
}
}
public static bool IsSevenZipFile(Stream stream)
{
try
{
return SignatureMatch(stream);
}
catch
{
return false;
}
}
private static ReadOnlySpan<byte> Signature =>
[(byte)'7', (byte)'z', 0xBC, 0xAF, 0x27, 0x1C];
private static bool SignatureMatch(Stream stream)
{
var reader = new BinaryReader(stream);
ReadOnlySpan<byte> signatureBytes = reader.ReadBytes(6);
return signatureBytes.SequenceEqual(Signature);
}
protected override IReader CreateReaderForSolidExtraction() =>
new SevenZipReader(ReaderOptions, this);
public override bool IsSolid =>
Entries
.Where(x => !x.IsDirectory)
.GroupBy(x => x.FilePart.Folder)
.Any(folder => folder.Count() > 1);
public override long TotalSize =>
_database?._packSizes.Aggregate(0L, (total, packSize) => total + packSize) ?? 0;
private sealed class SevenZipReader : AbstractReader<SevenZipEntry, SevenZipVolume>
{
private readonly SevenZipArchive _archive;
private CFolder? _currentFolder;
private Stream? _currentStream;
private CFileItem? _currentItem;
internal SevenZipReader(ReaderOptions readerOptions, SevenZipArchive archive)
: base(readerOptions, ArchiveType.SevenZip)
{
_archive = archive;
}
public override SevenZipVolume Volume => _archive.Volumes.Single();
protected override IEnumerable<SevenZipEntry> GetEntries(Stream stream)
{
var entries = _archive.Entries.ToList();
stream.Position = 0;
foreach (var dir in entries.Where(x => x.IsDirectory))
{
yield return dir;
}
foreach (
var group in entries.Where(x => !x.IsDirectory).GroupBy(x => x.FilePart.Folder)
)
{
_currentFolder = group.Key;
if (group.Key is null)
{
_currentStream = Stream.Null;
}
else
{
_currentStream = _archive._database?.GetFolderStream(
stream,
_currentFolder,
new PasswordProvider(Options.Password)
);
}
foreach (var entry in group)
{
_currentItem = entry.FilePart.Header;
yield return entry;
}
}
}
protected override EntryStream GetEntryStream() =>
CreateEntryStream(
new ReadOnlySubStream(
_currentStream.NotNull("currentStream is not null"),
_currentItem?.Size ?? 0
)
);
}
private class PasswordProvider : IPasswordProvider
{
private readonly string? _password;
public PasswordProvider(string? password)
{
_password = password;
}
public string? CryptoGetTextPassword() => _password;
}
public string? CryptoGetTextPassword() => _password;
}
}

View File

@@ -5,20 +5,20 @@ namespace SharpCompress.Archives.SevenZip;
public class SevenZipArchiveEntry : SevenZipEntry, IArchiveEntry
{
internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part)
: base(part)
{
Archive = archive;
}
internal SevenZipArchiveEntry(SevenZipArchive archive, SevenZipFilePart part)
: base(part)
{
Archive = archive;
}
public Stream OpenEntryStream() => FilePart.GetCompressedStream();
public Stream OpenEntryStream() => FilePart.GetCompressedStream();
public IArchive Archive { get; }
public IArchive Archive { get; }
public bool IsComplete => true;
public bool IsComplete => true;
/// <summary>
/// This is a 7Zip Anti item
/// </summary>
public bool IsAnti => FilePart.Header.IsAnti;
/// <summary>
/// This is a 7Zip Anti item
/// </summary>
public bool IsAnti => FilePart.Header.IsAnti;
}

View File

@@ -15,237 +15,237 @@ namespace SharpCompress.Archives.Tar;
public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
{
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(string filePath, ReaderOptions? readerOptions = null)
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new TarArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new TarArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new TarArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new TarArchive(
new SourceStream(
fileInfo,
i => ArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
}
return new TarArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
public static bool IsTarFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
{
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsTarFile(stream);
}
public static bool IsTarFile(Stream stream)
{
try
{
var tarHeader = new TarHeader(new ArchiveEncoding());
var readSucceeded = tarHeader.Read(new BinaryReader(stream));
var isEmptyArchive =
tarHeader.Name?.Length == 0
&& tarHeader.Size == 0
&& Enum.IsDefined(typeof(EntryType), tarHeader.EntryType);
return readSucceeded || isEmptyArchive;
}
catch { }
return false;
}
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream sourceStream)
{
sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams
return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); //simple single volume or split, multivolume not supported
}
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private TarArchive(SourceStream sourceStream)
: base(ArchiveType.Tar, sourceStream) { }
private TarArchive()
: base(ArchiveType.Tar) { }
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
{
var stream = volumes.Single().Stream;
TarHeader? previousHeader = null;
foreach (
var header in TarHeaderFactory.ReadHeader(
StreamingMode.Seekable,
stream,
ReaderOptions.ArchiveEncoding
)
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new TarArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new TarArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
if (header != null)
{
if (header.EntryType == EntryType.LongName)
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
previousHeader = header;
}
return new TarArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
}
public static bool IsTarFile(string filePath) => IsTarFile(new FileInfo(filePath));
public static bool IsTarFile(FileInfo fileInfo)
{
if (!fileInfo.Exists)
else
{
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsTarFile(stream);
}
public static bool IsTarFile(Stream stream)
{
try
{
var tarHeader = new TarHeader(new ArchiveEncoding());
var readSucceeded = tarHeader.Read(new BinaryReader(stream));
var isEmptyArchive =
tarHeader.Name?.Length == 0
&& tarHeader.Size == 0
&& Enum.IsDefined(typeof(EntryType), tarHeader.EntryType);
return readSucceeded || isEmptyArchive;
}
catch { }
return false;
}
protected override IEnumerable<TarVolume> LoadVolumes(SourceStream sourceStream)
{
sourceStream.NotNull("SourceStream is null").LoadAllParts(); //request all streams
return new TarVolume(sourceStream, ReaderOptions, 1).AsEnumerable(); //simple single volume or split, multivolume not supported
}
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
private TarArchive(SourceStream sourceStream)
: base(ArchiveType.Tar, sourceStream) { }
private TarArchive()
: base(ArchiveType.Tar) { }
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
{
var stream = volumes.Single().Stream;
TarHeader? previousHeader = null;
foreach (
var header in TarHeaderFactory.ReadHeader(
StreamingMode.Seekable,
stream,
ReaderOptions.ArchiveEncoding
)
)
{
if (header != null)
{
if (header.EntryType == EntryType.LongName)
{
previousHeader = header;
}
else
{
if (previousHeader != null)
{
var entry = new TarArchiveEntry(
this,
new TarFilePart(previousHeader, stream),
CompressionType.None
);
var oldStreamPos = stream.Position;
using (var entryStream = entry.OpenEntryStream())
{
using var memoryStream = new MemoryStream();
entryStream.TransferTo(memoryStream);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();
header.Name = ReaderOptions.ArchiveEncoding.Decode(bytes).TrimNulls();
}
stream.Position = oldStreamPos;
previousHeader = null;
}
yield return new TarArchiveEntry(
this,
new TarFilePart(header, stream),
CompressionType.None
);
}
}
else
{
throw new IncompleteArchiveException("Failed to read TAR header");
}
}
}
public static TarArchive Create() => new();
protected override TarArchiveEntry CreateEntryInternal(
string filePath,
Stream source,
long size,
DateTime? modified,
bool closeStream
) =>
new TarWritableArchiveEntry(
this,
source,
CompressionType.Unknown,
filePath,
size,
modified,
closeStream
);
protected override void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries
)
{
using var writer = new TarWriter(stream, new TarWriterOptions(options));
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
using var entryStream = entry.OpenEntryStream();
writer.Write(
entry.Key.NotNull("Entry Key is null"),
entryStream,
entry.LastModifiedTime,
entry.Size
if (previousHeader != null)
{
var entry = new TarArchiveEntry(
this,
new TarFilePart(previousHeader, stream),
CompressionType.None
);
}
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return TarReader.Open(stream);
var oldStreamPos = stream.Position;
using (var entryStream = entry.OpenEntryStream())
{
using var memoryStream = new MemoryStream();
entryStream.TransferTo(memoryStream);
memoryStream.Position = 0;
var bytes = memoryStream.ToArray();
header.Name = ReaderOptions.ArchiveEncoding.Decode(bytes).TrimNulls();
}
stream.Position = oldStreamPos;
previousHeader = null;
}
yield return new TarArchiveEntry(
this,
new TarFilePart(header, stream),
CompressionType.None
);
}
}
else
{
throw new IncompleteArchiveException("Failed to read TAR header");
}
}
}
public static TarArchive Create() => new();
protected override TarArchiveEntry CreateEntryInternal(
string filePath,
Stream source,
long size,
DateTime? modified,
bool closeStream
) =>
new TarWritableArchiveEntry(
this,
source,
CompressionType.Unknown,
filePath,
size,
modified,
closeStream
);
protected override void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<TarArchiveEntry> oldEntries,
IEnumerable<TarArchiveEntry> newEntries
)
{
using var writer = new TarWriter(stream, new TarWriterOptions(options));
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
using var entryStream = entry.OpenEntryStream();
writer.Write(
entry.Key.NotNull("Entry Key is null"),
entryStream,
entry.LastModifiedTime,
entry.Size
);
}
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
stream.Position = 0;
return TarReader.Open(stream);
}
}

View File

@@ -7,19 +7,19 @@ namespace SharpCompress.Archives.Tar;
public class TarArchiveEntry : TarEntry, IArchiveEntry
{
internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType compressionType)
: base(part, compressionType)
{
Archive = archive;
}
internal TarArchiveEntry(TarArchive archive, TarFilePart? part, CompressionType compressionType)
: base(part, compressionType)
{
Archive = archive;
}
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
#region IArchiveEntry Members
#region IArchiveEntry Members
public IArchive Archive { get; }
public IArchive Archive { get; }
public bool IsComplete => true;
public bool IsComplete => true;
#endregion
#endregion
}

View File

@@ -8,64 +8,64 @@ namespace SharpCompress.Archives.Tar;
internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiveEntry
{
private readonly bool closeStream;
private readonly Stream stream;
private readonly bool closeStream;
private readonly Stream stream;
internal TarWritableArchiveEntry(
TarArchive archive,
Stream stream,
CompressionType compressionType,
string path,
long size,
DateTime? lastModified,
bool closeStream
)
: base(archive, null, compressionType)
internal TarWritableArchiveEntry(
TarArchive archive,
Stream stream,
CompressionType compressionType,
string path,
long size,
DateTime? lastModified,
bool closeStream
)
: base(archive, null, compressionType)
{
this.stream = stream;
Key = path;
Size = size;
LastModifiedTime = lastModified;
this.closeStream = closeStream;
}
public override long Crc => 0;
public override string Key { get; }
public override long CompressedSize => 0;
public override long Size { get; }
public override DateTime? LastModifiedTime { get; }
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
Stream IWritableArchiveEntry.Stream => stream;
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()
{
if (closeStream)
{
this.stream = stream;
Key = path;
Size = size;
LastModifiedTime = lastModified;
this.closeStream = closeStream;
}
public override long Crc => 0;
public override string Key { get; }
public override long CompressedSize => 0;
public override long Size { get; }
public override DateTime? LastModifiedTime { get; }
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
Stream IWritableArchiveEntry.Stream => stream;
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()
{
if (closeStream)
{
stream.Dispose();
}
stream.Dispose();
}
}
}

View File

@@ -16,322 +16,318 @@ namespace SharpCompress.Archives.Zip;
public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
{
private readonly SeekableZipHeaderFactory? headerFactory;
private readonly SeekableZipHeaderFactory? headerFactory;
/// <summary>
/// Gets or sets the compression level applied to files added to the archive,
/// if the compression method is set to deflate
/// </summary>
public CompressionLevel DeflateCompressionLevel { get; set; }
/// <summary>
/// Gets or sets the compression level applied to files added to the archive,
/// if the compression method is set to deflate
/// </summary>
public CompressionLevel DeflateCompressionLevel { get; set; }
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
internal ZipArchive(SourceStream sourceStream)
: base(ArchiveType.Zip, sourceStream)
/// <summary>
/// Constructor with a SourceStream able to handle FileInfo and Streams.
/// </summary>
/// <param name="sourceStream"></param>
internal ZipArchive(SourceStream sourceStream)
: base(ArchiveType.Zip, sourceStream)
{
headerFactory = new SeekableZipHeaderFactory(
sourceStream.ReaderOptions.Password,
sourceStream.ReaderOptions.ArchiveEncoding
);
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new ZipArchive(
new SourceStream(
fileInfo,
i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new ZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new ZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
{
stream.CheckNotNull(nameof(stream));
if (stream is not { CanSeek: true })
{
headerFactory = new SeekableZipHeaderFactory(
sourceStream.ReaderOptions.Password,
sourceStream.ReaderOptions.ArchiveEncoding
);
throw new ArgumentException("Stream must be seekable", nameof(stream));
}
/// <summary>
/// Constructor expects a filepath to an existing file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(string filePath, ReaderOptions? readerOptions = null)
return new ZipArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
}
public static bool IsZipFile(
string filePath,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => IsZipFile(new FileInfo(filePath), password, bufferSize);
public static bool IsZipFile(
FileInfo fileInfo,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
if (!fileInfo.Exists)
{
filePath.CheckNotNullOrEmpty(nameof(filePath));
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsZipFile(stream, password, bufferSize);
}
/// <summary>
/// Constructor with a FileInfo object to an existing file.
/// </summary>
/// <param name="fileInfo"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
public static bool IsZipFile(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
try
{
fileInfo.CheckNotNull(nameof(fileInfo));
return new ZipArchive(
new SourceStream(
fileInfo,
i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions()
)
);
if (stream is not SharpCompressStream)
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
var header = headerFactory
.ReadStreamHeader(stream)
.FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
if (header is null)
{
return false;
}
return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
}
/// <summary>
/// Constructor with all file parts passed in
/// </summary>
/// <param name="fileInfos"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(
IEnumerable<FileInfo> fileInfos,
ReaderOptions? readerOptions = null
)
catch (CryptographicException)
{
fileInfos.CheckNotNull(nameof(fileInfos));
var files = fileInfos.ToArray();
return new ZipArchive(
new SourceStream(
files[0],
i => i < files.Length ? files[i] : null,
readerOptions ?? new ReaderOptions()
)
);
return true;
}
/// <summary>
/// Constructor with all stream parts passed in
/// </summary>
/// <param name="streams"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(IEnumerable<Stream> streams, ReaderOptions? readerOptions = null)
catch
{
streams.CheckNotNull(nameof(streams));
var strms = streams.ToArray();
return new ZipArchive(
new SourceStream(
strms[0],
i => i < strms.Length ? strms[i] : null,
readerOptions ?? new ReaderOptions()
)
);
return false;
}
}
/// <summary>
/// Takes a seekable Stream as a source
/// </summary>
/// <param name="stream"></param>
/// <param name="readerOptions"></param>
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
public static bool IsZipMulti(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
try
{
stream.CheckNotNull(nameof(stream));
if (stream is not SharpCompressStream)
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
if (stream is not { CanSeek: true })
var header = headerFactory
.ReadStreamHeader(stream)
.FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
if (header is null)
{
if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe
{
throw new ArgumentException("Stream must be seekable", nameof(stream));
var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding());
var x = z.ReadSeekableHeader(stream).FirstOrDefault();
return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry;
}
return new ZipArchive(
new SourceStream(stream, i => null, readerOptions ?? new ReaderOptions())
);
}
public static bool IsZipFile(
string filePath,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
) => IsZipFile(new FileInfo(filePath), password, bufferSize);
public static bool IsZipFile(
FileInfo fileInfo,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
{
if (!fileInfo.Exists)
else
{
return false;
return false;
}
using Stream stream = fileInfo.OpenRead();
return IsZipFile(stream, password, bufferSize);
}
return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
}
catch (CryptographicException)
{
return true;
}
catch
{
return false;
}
}
protected override IEnumerable<ZipVolume> LoadVolumes(SourceStream stream)
{
stream.LoadAllParts(); //request all streams
stream.Position = 0;
var streams = stream.Streams.ToList();
var idx = 0;
if (streams.Count > 1) //test part 2 - true = multipart not split
{
streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
var isZip = IsZipFile(streams[1], ReaderOptions.Password, ReaderOptions.BufferSize);
streams[1].Position -= 4;
if (isZip)
{
stream.IsVolumes = true;
var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end
streams.RemoveAt(0);
streams.Add(tmp);
//streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
return streams.Select(a => new ZipVolume(a, ReaderOptions, idx++));
}
}
public static bool IsZipFile(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
//split mode or single file
return new ZipVolume(stream, ReaderOptions, idx++).AsEnumerable();
}
internal ZipArchive()
: base(ArchiveType.Zip) { }
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
{
var vols = volumes.ToArray();
foreach (var h in headerFactory.NotNull().ReadSeekableHeader(vols.Last().Stream))
{
var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
try
if (h != null)
{
switch (h.ZipHeaderType)
{
if (stream is not SharpCompressStream)
case ZipHeaderType.DirectoryEntry:
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
var deh = (DirectoryEntryHeader)h;
Stream s;
if (
deh.RelativeOffsetOfEntryHeader + deh.CompressedSize
> vols[deh.DiskNumberStart].Stream.Length
)
{
var v = vols.Skip(deh.DiskNumberStart).ToArray();
s = new SourceStream(
v[0].Stream,
i => i < v.Length ? v[i].Stream : null,
new ReaderOptions() { LeaveStreamOpen = true }
);
}
else
{
s = vols[deh.DiskNumberStart].Stream;
}
var header = headerFactory
.ReadStreamHeader(stream)
.FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
if (header is null)
{
return false;
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
);
}
return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
}
catch (CryptographicException)
{
return true;
}
catch
{
return false;
break;
case ZipHeaderType.DirectoryEnd:
{
var bytes = ((DirectoryEndHeader)h).Comment ?? [];
vols.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
yield break;
}
}
}
}
}
public static bool IsZipMulti(
Stream stream,
string? password = null,
int bufferSize = ReaderOptions.DefaultBufferSize
)
public void SaveTo(Stream stream) => SaveTo(stream, new WriterOptions(CompressionType.Deflate));
protected override void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries
)
{
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
var headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding(), null);
try
{
if (stream is not SharpCompressStream)
{
stream = new SharpCompressStream(stream, bufferSize: bufferSize);
}
var header = headerFactory
.ReadStreamHeader(stream)
.FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
if (header is null)
{
if (stream.CanSeek) //could be multipart. Test for central directory - might not be z64 safe
{
var z = new SeekableZipHeaderFactory(password, new ArchiveEncoding());
var x = z.ReadSeekableHeader(stream).FirstOrDefault();
return x?.ZipHeaderType == ZipHeaderType.DirectoryEntry;
}
else
{
return false;
}
}
return Enum.IsDefined(typeof(ZipHeaderType), header.ZipHeaderType);
}
catch (CryptographicException)
{
return true;
}
catch
{
return false;
}
using var entryStream = entry.OpenEntryStream();
writer.Write(entry.Key.NotNull("Entry Key is null"), entryStream, entry.LastModifiedTime);
}
}
protected override IEnumerable<ZipVolume> LoadVolumes(SourceStream stream)
{
stream.LoadAllParts(); //request all streams
stream.Position = 0;
protected override ZipArchiveEntry CreateEntryInternal(
string filePath,
Stream source,
long size,
DateTime? modified,
bool closeStream
) => new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
var streams = stream.Streams.ToList();
var idx = 0;
if (streams.Count > 1) //test part 2 - true = multipart not split
{
streams[1].Position += 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
var isZip = IsZipFile(streams[1], ReaderOptions.Password, ReaderOptions.BufferSize);
streams[1].Position -= 4;
if (isZip)
{
stream.IsVolumes = true;
public static ZipArchive Create() => new();
var tmp = streams[0]; //arcs as zip, z01 ... swap the zip the end
streams.RemoveAt(0);
streams.Add(tmp);
//streams[0].Position = 4; //skip the POST_DATA_DESCRIPTOR to prevent an exception
return streams.Select(a => new ZipVolume(a, ReaderOptions, idx++));
}
}
//split mode or single file
return new ZipVolume(stream, ReaderOptions, idx++).AsEnumerable();
}
internal ZipArchive()
: base(ArchiveType.Zip) { }
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
{
var vols = volumes.ToArray();
foreach (var h in headerFactory.NotNull().ReadSeekableHeader(vols.Last().Stream))
{
if (h != null)
{
switch (h.ZipHeaderType)
{
case ZipHeaderType.DirectoryEntry:
{
var deh = (DirectoryEntryHeader)h;
Stream s;
if (
deh.RelativeOffsetOfEntryHeader + deh.CompressedSize
> vols[deh.DiskNumberStart].Stream.Length
)
{
var v = vols.Skip(deh.DiskNumberStart).ToArray();
s = new SourceStream(
v[0].Stream,
i => i < v.Length ? v[i].Stream : null,
new ReaderOptions() { LeaveStreamOpen = true }
);
}
else
{
s = vols[deh.DiskNumberStart].Stream;
}
yield return new ZipArchiveEntry(
this,
new SeekableZipFilePart(headerFactory.NotNull(), deh, s)
);
}
break;
case ZipHeaderType.DirectoryEnd:
{
var bytes = ((DirectoryEndHeader)h).Comment ?? [];
vols.Last().Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
yield break;
}
}
}
}
}
public void SaveTo(Stream stream) => SaveTo(stream, new WriterOptions(CompressionType.Deflate));
protected override void SaveTo(
Stream stream,
WriterOptions options,
IEnumerable<ZipArchiveEntry> oldEntries,
IEnumerable<ZipArchiveEntry> newEntries
)
{
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
{
using var entryStream = entry.OpenEntryStream();
writer.Write(
entry.Key.NotNull("Entry Key is null"),
entryStream,
entry.LastModifiedTime
);
}
}
protected override ZipArchiveEntry CreateEntryInternal(
string filePath,
Stream source,
long size,
DateTime? modified,
bool closeStream
) => new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
public static ZipArchive Create() => new();
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
((IStreamStack)stream).StackSeek(0);
return ZipReader.Open(stream, ReaderOptions, Entries);
}
protected override IReader CreateReaderForSolidExtraction()
{
var stream = Volumes.Single().Stream;
((IStreamStack)stream).StackSeek(0);
return ZipReader.Open(stream, ReaderOptions, Entries);
}
}

View File

@@ -6,19 +6,19 @@ namespace SharpCompress.Archives.Zip;
public class ZipArchiveEntry : ZipEntry, IArchiveEntry
{
internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part)
: base(part)
{
Archive = archive;
}
internal ZipArchiveEntry(ZipArchive archive, SeekableZipFilePart? part)
: base(part)
{
Archive = archive;
}
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
public virtual Stream OpenEntryStream() => Parts.Single().GetCompressedStream().NotNull();
#region IArchiveEntry Members
#region IArchiveEntry Members
public IArchive Archive { get; }
public IArchive Archive { get; }
public bool IsComplete => true;
public bool IsComplete => true;
#endregion
#endregion
}

View File

@@ -6,36 +6,36 @@ namespace SharpCompress.Archives.Zip;
internal static class ZipArchiveVolumeFactory
{
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
internal static FileInfo? GetFilePart(int index, FileInfo part1) //base the name on the first part
{
FileInfo item;
//load files with zip/zipx first. Swapped to end once loaded in ZipArchive
//new style .zip, z01.. | .zipx, zx01 - if the numbers go beyond 99 then they use 100 ...1000 etc
var m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
{
FileInfo item;
//load files with zip/zipx first. Swapped to end once loaded in ZipArchive
//new style .zip, z01.. | .zipx, zx01 - if the numbers go beyond 99 then they use 100 ...1000 etc
var m = Regex.Match(part1.Name, @"^(.*\.)(zipx?|zx?[0-9]+)$", RegexOptions.IgnoreCase);
if (m.Success)
{
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
Regex.Replace(m.Groups[2].Value, @"[^xz]", ""),
index.ToString().PadLeft(2, '0')
)
)
);
}
else //split - 001, 002 ...
{
return ArchiveVolumeFactory.GetFilePart(index, part1);
}
if (item.Exists)
{
return item;
}
return null; //no more items
item = new FileInfo(
Path.Combine(
part1.DirectoryName!,
String.Concat(
m.Groups[1].Value,
Regex.Replace(m.Groups[2].Value, @"[^xz]", ""),
index.ToString().PadLeft(2, '0')
)
)
);
}
else //split - 001, 002 ...
{
return ArchiveVolumeFactory.GetFilePart(index, part1);
}
if (item.Exists)
{
return item;
}
return null; //no more items
}
}

View File

@@ -8,66 +8,66 @@ namespace SharpCompress.Archives.Zip;
internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
{
private readonly bool closeStream;
private readonly Stream stream;
private bool isDisposed;
private readonly bool closeStream;
private readonly Stream stream;
private bool isDisposed;
internal ZipWritableArchiveEntry(
ZipArchive archive,
Stream stream,
string path,
long size,
DateTime? lastModified,
bool closeStream
)
: base(archive, null)
internal ZipWritableArchiveEntry(
ZipArchive archive,
Stream stream,
string path,
long size,
DateTime? lastModified,
bool closeStream
)
: base(archive, null)
{
this.stream = stream;
Key = path;
Size = size;
LastModifiedTime = lastModified;
this.closeStream = closeStream;
}
public override long Crc => 0;
public override string Key { get; }
public override long CompressedSize => 0;
public override long Size { get; }
public override DateTime? LastModifiedTime { get; }
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
Stream IWritableArchiveEntry.Stream => stream;
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()
{
if (closeStream && !isDisposed)
{
this.stream = stream;
Key = path;
Size = size;
LastModifiedTime = lastModified;
this.closeStream = closeStream;
}
public override long Crc => 0;
public override string Key { get; }
public override long CompressedSize => 0;
public override long Size { get; }
public override DateTime? LastModifiedTime { get; }
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
Stream IWritableArchiveEntry.Stream => stream;
public override Stream OpenEntryStream()
{
//ensure new stream is at the start, this could be reset
stream.Seek(0, SeekOrigin.Begin);
return SharpCompressStream.Create(stream, leaveOpen: true);
}
internal override void Close()
{
if (closeStream && !isDisposed)
{
stream.Dispose();
isDisposed = true;
}
stream.Dispose();
isDisposed = true;
}
}
}

View File

@@ -3,5 +3,5 @@ using System.Runtime.CompilerServices;
[assembly: CLSCompliant(true)]
[assembly: InternalsVisibleTo(
"SharpCompress.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100158bebf1433f76dffc356733c138babea7a47536c65ed8009b16372c6f4edbb20554db74a62687f56b97c20a6ce8c4b123280279e33c894e7b3aa93ab3c573656fde4db576cfe07dba09619ead26375b25d2c4a8e43f7be257d712b0dd2eb546f67adb09281338618a58ac834fc038dd7e2740a7ab3591826252e4f4516306dc"
"SharpCompress.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100158bebf1433f76dffc356733c138babea7a47536c65ed8009b16372c6f4edbb20554db74a62687f56b97c20a6ce8c4b123280279e33c894e7b3aa93ab3c573656fde4db576cfe07dba09619ead26375b25d2c4a8e43f7be257d712b0dd2eb546f67adb09281338618a58ac834fc038dd7e2740a7ab3591826252e4f4516306dc"
)]

View File

@@ -5,51 +5,51 @@ namespace SharpCompress.Common.Arc;
public class ArcEntry : Entry
{
private readonly ArcFilePart? _filePart;
private readonly ArcFilePart? _filePart;
internal ArcEntry(ArcFilePart? filePart)
internal ArcEntry(ArcFilePart? filePart)
{
_filePart = filePart;
}
public override long Crc
{
get
{
_filePart = filePart;
if (_filePart == null)
{
return 0;
}
return _filePart.Header.Crc16;
}
}
public override long Crc
{
get
{
if (_filePart == null)
{
return 0;
}
return _filePart.Header.Crc16;
}
}
public override string? Key => _filePart?.Header.Name;
public override string? Key => _filePart?.Header.Name;
public override string? LinkTarget => null;
public override string? LinkTarget => null;
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;
public override CompressionType CompressionType =>
_filePart?.Header.CompressionMethod ?? CompressionType.Unknown;
public override CompressionType CompressionType =>
_filePart?.Header.CompressionMethod ?? CompressionType.Unknown;
#pragma warning disable CA1065
public override long Size => throw new NotImplementedException();
public override long Size => throw new NotImplementedException();
#pragma warning restore CA1065
public override DateTime? LastModifiedTime => null;
public override DateTime? LastModifiedTime => null;
public override DateTime? CreatedTime => null;
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
}

View File

@@ -6,66 +6,65 @@ namespace SharpCompress.Common.Arc;
public class ArcEntryHeader
{
public ArchiveEncoding ArchiveEncoding { get; }
public CompressionType CompressionMethod { get; private set; }
public string? Name { get; private set; }
public long CompressedSize { get; private set; }
public DateTime DateTime { get; private set; }
public int Crc16 { get; private set; }
public long OriginalSize { get; private set; }
public long DataStartPosition { get; private set; }
public ArchiveEncoding ArchiveEncoding { get; }
public CompressionType CompressionMethod { get; private set; }
public string? Name { get; private set; }
public long CompressedSize { get; private set; }
public DateTime DateTime { get; private set; }
public int Crc16 { get; private set; }
public long OriginalSize { get; private set; }
public long DataStartPosition { get; private set; }
public ArcEntryHeader(ArchiveEncoding archiveEncoding)
public ArcEntryHeader(ArchiveEncoding archiveEncoding)
{
ArchiveEncoding = archiveEncoding;
}
public ArcEntryHeader? ReadHeader(Stream stream)
{
byte[] headerBytes = new byte[29];
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
{
ArchiveEncoding = archiveEncoding;
return null;
}
DataStartPosition = stream.Position;
return LoadFrom(headerBytes);
}
public ArcEntryHeader? ReadHeader(Stream stream)
public ArcEntryHeader LoadFrom(byte[] headerBytes)
{
CompressionMethod = GetCompressionType(headerBytes[1]);
// Read name
int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator
Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12);
int offset = 15;
CompressedSize = BitConverter.ToUInt32(headerBytes, offset);
offset += 4;
uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset);
DateTime = ConvertToDateTime(rawDateTime);
offset += 4;
Crc16 = BitConverter.ToUInt16(headerBytes, offset);
offset += 2;
OriginalSize = BitConverter.ToUInt32(headerBytes, offset);
return this;
}
private CompressionType GetCompressionType(byte value) =>
value switch
{
byte[] headerBytes = new byte[29];
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
{
return null;
}
DataStartPosition = stream.Position;
return LoadFrom(headerBytes);
}
1 or 2 => CompressionType.None,
3 => CompressionType.RLE90,
4 => CompressionType.Squeezed,
5 or 6 or 7 or 8 => CompressionType.Crunched,
9 => CompressionType.Squashed,
10 => CompressionType.Crushed,
11 => CompressionType.Distilled,
_ => CompressionType.Unknown,
};
public ArcEntryHeader LoadFrom(byte[] headerBytes)
{
CompressionMethod = GetCompressionType(headerBytes[1]);
// Read name
int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator
Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12);
int offset = 15;
CompressedSize = BitConverter.ToUInt32(headerBytes, offset);
offset += 4;
uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset);
DateTime = ConvertToDateTime(rawDateTime);
offset += 4;
Crc16 = BitConverter.ToUInt16(headerBytes, offset);
offset += 2;
OriginalSize = BitConverter.ToUInt32(headerBytes, offset);
return this;
}
private CompressionType GetCompressionType(byte value) =>
value switch
{
1 or 2 => CompressionType.None,
3 => CompressionType.RLE90,
4 => CompressionType.Squeezed,
5 or 6 or 7 or 8 => CompressionType.Crunched,
9 => CompressionType.Squashed,
10 => CompressionType.Crushed,
11 => CompressionType.Distilled,
_ => CompressionType.Unknown,
};
public static DateTime ConvertToDateTime(long rawDateTime) =>
// Convert Unix timestamp to DateTime (UTC)
DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime;
public static DateTime ConvertToDateTime(long rawDateTime) =>
// Convert Unix timestamp to DateTime (UTC)
DateTimeOffset.FromUnixTimeSeconds(rawDateTime).UtcDateTime;
}

View File

@@ -9,51 +9,49 @@ namespace SharpCompress.Common.Arc;
public class ArcFilePart : FilePart
{
private readonly Stream? _stream;
private readonly Stream? _stream;
internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream)
: base(localArcHeader.ArchiveEncoding)
internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream)
: base(localArcHeader.ArchiveEncoding)
{
_stream = seekableStream;
Header = localArcHeader;
}
internal ArcEntryHeader Header { get; set; }
internal override string? FilePartName => Header.Name;
internal override Stream GetCompressedStream()
{
if (_stream != null)
{
_stream = seekableStream;
Header = localArcHeader;
Stream compressedStream;
switch (Header.CompressionMethod)
{
case CompressionType.None:
compressedStream = new ReadOnlySubStream(
_stream,
Header.DataStartPosition,
Header.CompressedSize
);
break;
case CompressionType.RLE90:
compressedStream = new RunLength90Stream(_stream, (int)Header.CompressedSize);
break;
case CompressionType.Squeezed:
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
break;
case CompressionType.Crunched:
compressedStream = new ArcLzwStream(_stream, (int)Header.CompressedSize);
break;
default:
throw new NotSupportedException("CompressionMethod: " + Header.CompressionMethod);
}
return compressedStream;
}
return _stream.NotNull();
}
internal ArcEntryHeader Header { get; set; }
internal override string? FilePartName => Header.Name;
internal override Stream GetCompressedStream()
{
if (_stream != null)
{
Stream compressedStream;
switch (Header.CompressionMethod)
{
case CompressionType.None:
compressedStream = new ReadOnlySubStream(
_stream,
Header.DataStartPosition,
Header.CompressedSize
);
break;
case CompressionType.RLE90:
compressedStream = new RunLength90Stream(_stream, (int)Header.CompressedSize);
break;
case CompressionType.Squeezed:
compressedStream = new SqueezeStream(_stream, (int)Header.CompressedSize);
break;
case CompressionType.Crunched:
compressedStream = new ArcLzwStream(_stream, (int)Header.CompressedSize);
break;
default:
throw new NotSupportedException(
"CompressionMethod: " + Header.CompressionMethod
);
}
return compressedStream;
}
return _stream.NotNull();
}
internal override Stream? GetRawStream() => _stream;
internal override Stream? GetRawStream() => _stream;
}

View File

@@ -5,6 +5,6 @@ namespace SharpCompress.Common.Arc;
public class ArcVolume : Volume
{
public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
: base(stream, readerOptions, index) { }
public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
: base(stream, readerOptions, index) { }
}

View File

@@ -5,56 +5,56 @@ namespace SharpCompress.Common;
public class ArchiveEncoding
{
/// <summary>
/// Default encoding to use when archive format doesn't specify one.
/// </summary>
public Encoding? Default { get; set; }
/// <summary>
/// Default encoding to use when archive format doesn't specify one.
/// </summary>
public Encoding? Default { get; set; }
/// <summary>
/// ArchiveEncoding used by encryption schemes which don't comply with RFC 2898.
/// </summary>
public Encoding? Password { get; set; }
/// <summary>
/// ArchiveEncoding used by encryption schemes which don't comply with RFC 2898.
/// </summary>
public Encoding? Password { get; set; }
/// <summary>
/// Set this encoding when you want to force it for all encoding operations.
/// </summary>
public Encoding? Forced { get; set; }
/// <summary>
/// Set this encoding when you want to force it for all encoding operations.
/// </summary>
public Encoding? Forced { get; set; }
/// <summary>
/// Set this when you want to use a custom method for all decoding operations.
/// </summary>
/// <returns>string Func(bytes, index, length)</returns>
public Func<byte[], int, int, string>? CustomDecoder { get; set; }
/// <summary>
/// Set this when you want to use a custom method for all decoding operations.
/// </summary>
/// <returns>string Func(bytes, index, length)</returns>
public Func<byte[], int, int, string>? CustomDecoder { get; set; }
public ArchiveEncoding()
: this(Encoding.Default, Encoding.Default) { }
public ArchiveEncoding()
: this(Encoding.Default, Encoding.Default) { }
public ArchiveEncoding(Encoding def, Encoding password)
{
Default = def;
Password = password;
}
public ArchiveEncoding(Encoding def, Encoding password)
{
Default = def;
Password = password;
}
#if !NETFRAMEWORK
static ArchiveEncoding()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
static ArchiveEncoding()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}
#endif
public string Decode(byte[] bytes) => Decode(bytes, 0, bytes.Length);
public string Decode(byte[] bytes) => Decode(bytes, 0, bytes.Length);
public string Decode(byte[] bytes, int start, int length) =>
GetDecoder().Invoke(bytes, start, length);
public string Decode(byte[] bytes, int start, int length) =>
GetDecoder().Invoke(bytes, start, length);
public string DecodeUTF8(byte[] bytes) => Encoding.UTF8.GetString(bytes, 0, bytes.Length);
public string DecodeUTF8(byte[] bytes) => Encoding.UTF8.GetString(bytes, 0, bytes.Length);
public byte[] Encode(string str) => GetEncoding().GetBytes(str);
public byte[] Encode(string str) => GetEncoding().GetBytes(str);
public Encoding GetEncoding() => Forced ?? Default ?? Encoding.UTF8;
public Encoding GetEncoding() => Forced ?? Default ?? Encoding.UTF8;
public Encoding GetPasswordEncoding() => Password ?? Encoding.UTF8;
public Encoding GetPasswordEncoding() => Password ?? Encoding.UTF8;
public Func<byte[], int, int, string> GetDecoder() =>
CustomDecoder ?? ((bytes, index, count) => GetEncoding().GetString(bytes, index, count));
public Func<byte[], int, int, string> GetDecoder() =>
CustomDecoder ?? ((bytes, index, count) => GetEncoding().GetString(bytes, index, count));
}

View File

@@ -4,10 +4,10 @@ namespace SharpCompress.Common;
public class ArchiveExtractionEventArgs<T> : EventArgs
{
internal ArchiveExtractionEventArgs(T entry)
{
Item = entry;
}
internal ArchiveExtractionEventArgs(T entry)
{
Item = entry;
}
public T Item { get; }
public T Item { get; }
}

View File

@@ -2,10 +2,10 @@ namespace SharpCompress.Common;
public enum ArchiveType
{
Rar,
Zip,
Tar,
SevenZip,
GZip,
Arc,
Rar,
Zip,
Tar,
SevenZip,
GZip,
Arc,
}

View File

@@ -4,22 +4,22 @@ namespace SharpCompress.Common;
public sealed class CompressedBytesReadEventArgs : EventArgs
{
public CompressedBytesReadEventArgs(
long compressedBytesRead,
long currentFilePartCompressedBytesRead
)
{
CompressedBytesRead = compressedBytesRead;
CurrentFilePartCompressedBytesRead = currentFilePartCompressedBytesRead;
}
public CompressedBytesReadEventArgs(
long compressedBytesRead,
long currentFilePartCompressedBytesRead
)
{
CompressedBytesRead = compressedBytesRead;
CurrentFilePartCompressedBytesRead = currentFilePartCompressedBytesRead;
}
/// <summary>
/// Compressed bytes read for the current entry
/// </summary>
public long CompressedBytesRead { get; }
/// <summary>
/// Compressed bytes read for the current entry
/// </summary>
public long CompressedBytesRead { get; }
/// <summary>
/// Current file part read for Multipart files (e.g. Rar)
/// </summary>
public long CurrentFilePartCompressedBytesRead { get; }
/// <summary>
/// Current file part read for Multipart files (e.g. Rar)
/// </summary>
public long CurrentFilePartCompressedBytesRead { get; }
}

View File

@@ -2,31 +2,31 @@ namespace SharpCompress.Common;
public enum CompressionType
{
None,
GZip,
BZip2,
PPMd,
Deflate,
Rar,
LZMA,
BCJ,
BCJ2,
LZip,
Xz,
Unknown,
Deflate64,
Shrink,
Lzw,
Reduce1,
Reduce2,
Reduce3,
Reduce4,
Explode,
Squeezed,
RLE90,
Crunched,
Squashed,
Crushed,
Distilled,
ZStandard,
None,
GZip,
BZip2,
PPMd,
Deflate,
Rar,
LZMA,
BCJ,
BCJ2,
LZip,
Xz,
Unknown,
Deflate64,
Shrink,
Lzw,
Reduce1,
Reduce2,
Reduce3,
Reduce4,
Explode,
Squeezed,
RLE90,
Crunched,
Squashed,
Crushed,
Distilled,
ZStandard,
}

View File

@@ -6,85 +6,85 @@ namespace SharpCompress.Common;
public abstract class Entry : IEntry
{
/// <summary>
/// The File's 32 bit CRC Hash
/// </summary>
public abstract long Crc { get; }
/// <summary>
/// The File's 32 bit CRC Hash
/// </summary>
public abstract long Crc { get; }
/// <summary>
/// The string key of the file internal to the Archive.
/// </summary>
public abstract string? Key { get; }
/// <summary>
/// The string key of the file internal to the Archive.
/// </summary>
public abstract string? Key { get; }
/// <summary>
/// The target of a symlink entry internal to the Archive. Will be null if not a symlink.
/// </summary>
public abstract string? LinkTarget { get; }
/// <summary>
/// The target of a symlink entry internal to the Archive. Will be null if not a symlink.
/// </summary>
public abstract string? LinkTarget { get; }
/// <summary>
/// The compressed file size
/// </summary>
public abstract long CompressedSize { get; }
/// <summary>
/// The compressed file size
/// </summary>
public abstract long CompressedSize { get; }
/// <summary>
/// The compression type
/// </summary>
public abstract CompressionType CompressionType { get; }
/// <summary>
/// The compression type
/// </summary>
public abstract CompressionType CompressionType { get; }
/// <summary>
/// The uncompressed file size
/// </summary>
public abstract long Size { get; }
/// <summary>
/// The uncompressed file size
/// </summary>
public abstract long Size { get; }
/// <summary>
/// The entry last modified time in the archive, if recorded
/// </summary>
public abstract DateTime? LastModifiedTime { get; }
/// <summary>
/// The entry last modified time in the archive, if recorded
/// </summary>
public abstract DateTime? LastModifiedTime { get; }
/// <summary>
/// The entry create time in the archive, if recorded
/// </summary>
public abstract DateTime? CreatedTime { get; }
/// <summary>
/// The entry create time in the archive, if recorded
/// </summary>
public abstract DateTime? CreatedTime { get; }
/// <summary>
/// The entry last accessed time in the archive, if recorded
/// </summary>
public abstract DateTime? LastAccessedTime { get; }
/// <summary>
/// The entry last accessed time in the archive, if recorded
/// </summary>
public abstract DateTime? LastAccessedTime { get; }
/// <summary>
/// The entry time when archived, if recorded
/// </summary>
public abstract DateTime? ArchivedTime { get; }
/// <summary>
/// The entry time when archived, if recorded
/// </summary>
public abstract DateTime? ArchivedTime { get; }
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public abstract bool IsEncrypted { get; }
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public abstract bool IsEncrypted { get; }
/// <summary>
/// Entry is directory.
/// </summary>
public abstract bool IsDirectory { get; }
/// <summary>
/// Entry is directory.
/// </summary>
public abstract bool IsDirectory { get; }
/// <summary>
/// Entry is split among multiple volumes
/// </summary>
public abstract bool IsSplitAfter { get; }
/// <summary>
/// Entry is split among multiple volumes
/// </summary>
public abstract bool IsSplitAfter { get; }
public int VolumeIndexFirst => Parts.FirstOrDefault()?.Index ?? 0;
public int VolumeIndexLast => Parts.LastOrDefault()?.Index ?? 0;
public int VolumeIndexFirst => Parts.FirstOrDefault()?.Index ?? 0;
public int VolumeIndexLast => Parts.LastOrDefault()?.Index ?? 0;
/// <inheritdoc/>
public override string ToString() => Key ?? "Entry";
/// <inheritdoc/>
public override string ToString() => Key ?? "Entry";
internal abstract IEnumerable<FilePart> Parts { get; }
internal abstract IEnumerable<FilePart> Parts { get; }
public bool IsSolid { get; set; }
public bool IsSolid { get; set; }
internal virtual void Close() { }
internal virtual void Close() { }
/// <summary>
/// Entry file attribute.
/// </summary>
public virtual int? Attrib => throw new NotImplementedException();
/// <summary>
/// Entry file attribute.
/// </summary>
public virtual int? Attrib => throw new NotImplementedException();
}

View File

@@ -8,120 +8,120 @@ namespace SharpCompress.Common;
public class EntryStream : Stream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
long IStreamStack.InstanceId { get; set; }
#endif
int IStreamStack.DefaultBufferSize { get; set; }
int IStreamStack.DefaultBufferSize { get; set; }
Stream IStreamStack.BaseStream() => _stream;
Stream IStreamStack.BaseStream() => _stream;
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
void IStreamStack.SetPosition(long position) { }
void IStreamStack.SetPosition(long position) { }
private readonly IReader _reader;
private readonly Stream _stream;
private bool _completed;
private bool _isDisposed;
private readonly IReader _reader;
private readonly Stream _stream;
private bool _completed;
private bool _isDisposed;
internal EntryStream(IReader reader, Stream stream)
{
_reader = reader;
_stream = stream;
internal EntryStream(IReader reader, Stream stream)
{
_reader = reader;
_stream = stream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(EntryStream));
this.DebugConstruct(typeof(EntryStream));
#endif
}
/// <summary>
/// When reading a stream from OpenEntryStream, the stream must be completed so use this to finish reading the entire entry.
/// </summary>
public void SkipEntry()
{
this.Skip();
_completed = true;
}
protected override void Dispose(bool disposing)
{
if (!(_completed || _reader.Cancelled))
{
SkipEntry();
}
/// <summary>
/// When reading a stream from OpenEntryStream, the stream must be completed so use this to finish reading the entire entry.
/// </summary>
public void SkipEntry()
//Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised
if (_stream is IStreamStack ss)
{
this.Skip();
_completed = true;
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
{
deflateStream.Flush(); //Deflate over reads. Knock it back
}
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
{
lzmaStream.Flush(); //Lzma over reads. Knock it back
}
}
protected override void Dispose(bool disposing)
if (_isDisposed)
{
if (!(_completed || _reader.Cancelled))
{
SkipEntry();
}
//Need a safe standard approach to this - it's okay for compression to overreads. Handling needs to be standardised
if (_stream is IStreamStack ss)
{
if (ss.BaseStream() is SharpCompress.Compressors.Deflate.DeflateStream deflateStream)
{
deflateStream.Flush(); //Deflate over reads. Knock it back
}
else if (ss.BaseStream() is SharpCompress.Compressors.LZMA.LzmaStream lzmaStream)
{
lzmaStream.Flush(); //Lzma over reads. Knock it back
}
}
if (_isDisposed)
{
return;
}
_isDisposed = true;
return;
}
_isDisposed = true;
#if DEBUG_STREAMS
this.DebugDispose(typeof(EntryStream));
this.DebugDispose(typeof(EntryStream));
#endif
base.Dispose(disposing);
_stream.Dispose();
}
base.Dispose(disposing);
_stream.Dispose();
}
public override bool CanRead => true;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override bool CanWrite => false;
public override void Flush() { }
public override void Flush() { }
public override long Length => _stream.Length;
public override long Length => _stream.Length;
public override long Position
public override long Position
{
get => _stream.Position; //throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
var read = _stream.Read(buffer, offset, count);
if (read <= 0)
{
get => _stream.Position; //throw new NotSupportedException();
set => throw new NotSupportedException();
_completed = true;
}
return read;
}
public override int Read(byte[] buffer, int offset, int count)
public override int ReadByte()
{
var value = _stream.ReadByte();
if (value == -1)
{
var read = _stream.Read(buffer, offset, count);
if (read <= 0)
{
_completed = true;
}
return read;
_completed = true;
}
return value;
}
public override int ReadByte()
{
var value = _stream.ReadByte();
if (value == -1)
{
_completed = true;
}
return value;
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
}

View File

@@ -5,115 +5,110 @@ namespace SharpCompress.Common;
internal static class ExtractionMethods
{
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteEntryToDirectory(
IEntry entry,
string destinationDirectory,
ExtractionOptions? options,
Action<string, ExtractionOptions?> write
/// <summary>
/// Extract to specific directory, retaining filename
/// </summary>
public static void WriteEntryToDirectory(
IEntry entry,
string destinationDirectory,
ExtractionOptions? options,
Action<string, ExtractionOptions?> write
)
{
string destinationFileName;
var fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
//check for trailing slash.
if (
fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1]
!= Path.DirectorySeparatorChar
)
{
string destinationFileName;
var fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
//check for trailing slash.
if (
fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1]
!= Path.DirectorySeparatorChar
)
{
fullDestinationDirectoryPath += Path.DirectorySeparatorChar;
}
if (!Directory.Exists(fullDestinationDirectoryPath))
{
throw new ExtractionException(
$"Directory does not exist to extract to: {fullDestinationDirectoryPath}"
);
}
options ??= new ExtractionOptions() { Overwrite = true };
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
file = Utility.ReplaceInvalidFileNameChars(file);
if (options.ExtractFullPath)
{
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
.NotNull("Directory is null");
var destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
if (!Directory.Exists(destdir))
{
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
{
throw new ExtractionException(
"Entry is trying to create a directory outside of the destination directory."
);
}
Directory.CreateDirectory(destdir);
}
destinationFileName = Path.Combine(destdir, file);
}
else
{
destinationFileName = Path.Combine(fullDestinationDirectoryPath, file);
}
if (!entry.IsDirectory)
{
destinationFileName = Path.GetFullPath(destinationFileName);
if (
!destinationFileName.StartsWith(
fullDestinationDirectoryPath,
StringComparison.Ordinal
)
)
{
throw new ExtractionException(
"Entry is trying to write a file outside of the destination directory."
);
}
write(destinationFileName, options);
}
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
{
Directory.CreateDirectory(destinationFileName);
}
fullDestinationDirectoryPath += Path.DirectorySeparatorChar;
}
public static void WriteEntryToFile(
IEntry entry,
string destinationFileName,
ExtractionOptions? options,
Action<string, FileMode> openAndWrite
)
if (!Directory.Exists(fullDestinationDirectoryPath))
{
if (entry.LinkTarget != null)
{
if (options?.WriteSymbolicLink is null)
{
throw new ExtractionException(
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
);
}
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
}
else
{
var fm = FileMode.Create;
options ??= new ExtractionOptions() { Overwrite = true };
if (!options.Overwrite)
{
fm = FileMode.CreateNew;
}
openAndWrite(destinationFileName, fm);
entry.PreserveExtractionOptions(destinationFileName, options);
}
throw new ExtractionException(
$"Directory does not exist to extract to: {fullDestinationDirectoryPath}"
);
}
options ??= new ExtractionOptions() { Overwrite = true };
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
file = Utility.ReplaceInvalidFileNameChars(file);
if (options.ExtractFullPath)
{
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
.NotNull("Directory is null");
var destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
if (!Directory.Exists(destdir))
{
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
{
throw new ExtractionException(
"Entry is trying to create a directory outside of the destination directory."
);
}
Directory.CreateDirectory(destdir);
}
destinationFileName = Path.Combine(destdir, file);
}
else
{
destinationFileName = Path.Combine(fullDestinationDirectoryPath, file);
}
if (!entry.IsDirectory)
{
destinationFileName = Path.GetFullPath(destinationFileName);
if (!destinationFileName.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
{
throw new ExtractionException(
"Entry is trying to write a file outside of the destination directory."
);
}
write(destinationFileName, options);
}
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
{
Directory.CreateDirectory(destinationFileName);
}
}
public static void WriteEntryToFile(
IEntry entry,
string destinationFileName,
ExtractionOptions? options,
Action<string, FileMode> openAndWrite
)
{
if (entry.LinkTarget != null)
{
if (options?.WriteSymbolicLink is null)
{
throw new ExtractionException(
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
);
}
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
}
else
{
var fm = FileMode.Create;
options ??= new ExtractionOptions() { Overwrite = true };
if (!options.Overwrite)
{
fm = FileMode.CreateNew;
}
openAndWrite(destinationFileName, fm);
entry.PreserveExtractionOptions(destinationFileName, options);
}
}
}

View File

@@ -4,37 +4,37 @@ namespace SharpCompress.Common;
public class ExtractionOptions
{
/// <summary>
/// overwrite target if it exists
/// </summary>
public bool Overwrite { get; set; }
/// <summary>
/// overwrite target if it exists
/// </summary>
public bool Overwrite { get; set; }
/// <summary>
/// extract with internal directory structure
/// </summary>
public bool ExtractFullPath { get; set; }
/// <summary>
/// extract with internal directory structure
/// </summary>
public bool ExtractFullPath { get; set; }
/// <summary>
/// preserve file time
/// </summary>
public bool PreserveFileTime { get; set; }
/// <summary>
/// preserve file time
/// </summary>
public bool PreserveFileTime { get; set; }
/// <summary>
/// preserve windows file attributes
/// </summary>
public bool PreserveAttributes { get; set; }
/// <summary>
/// preserve windows file attributes
/// </summary>
public bool PreserveAttributes { get; set; }
/// <summary>
/// Delegate for writing symbolic links to disk.
/// sourcePath is where the symlink is created.
/// targetPath is what the symlink refers to.
/// </summary>
public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath);
/// <summary>
/// Delegate for writing symbolic links to disk.
/// sourcePath is where the symlink is created.
/// targetPath is what the symlink refers to.
/// </summary>
public delegate void SymbolicLinkWriterDelegate(string sourcePath, string targetPath);
public SymbolicLinkWriterDelegate WriteSymbolicLink = (sourcePath, targetPath) =>
{
Console.WriteLine(
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
);
};
public SymbolicLinkWriterDelegate WriteSymbolicLink = (sourcePath, targetPath) =>
{
Console.WriteLine(
$"Could not write symlink {sourcePath} -> {targetPath}, for more information please see https://github.com/dotnet/runtime/issues/24271"
);
};
}

View File

@@ -4,17 +4,17 @@ namespace SharpCompress.Common;
public abstract class FilePart
{
protected FilePart(ArchiveEncoding archiveEncoding)
{
ArchiveEncoding = archiveEncoding;
}
protected FilePart(ArchiveEncoding archiveEncoding)
{
ArchiveEncoding = archiveEncoding;
}
internal ArchiveEncoding ArchiveEncoding { get; }
internal ArchiveEncoding ArchiveEncoding { get; }
internal abstract string? FilePartName { get; }
public int Index { get; set; }
internal abstract string? FilePartName { get; }
public int Index { get; set; }
internal abstract Stream? GetCompressedStream();
internal abstract Stream? GetRawStream();
internal bool Skipped { get; set; }
internal abstract Stream? GetCompressedStream();
internal abstract Stream? GetRawStream();
internal bool Skipped { get; set; }
}

View File

@@ -4,25 +4,25 @@ namespace SharpCompress.Common;
public sealed class FilePartExtractionBeginEventArgs : EventArgs
{
public FilePartExtractionBeginEventArgs(string name, long size, long compressedSize)
{
Name = name;
Size = size;
CompressedSize = compressedSize;
}
public FilePartExtractionBeginEventArgs(string name, long size, long compressedSize)
{
Name = name;
Size = size;
CompressedSize = compressedSize;
}
/// <summary>
/// File name for the part for the current entry
/// </summary>
public string Name { get; }
/// <summary>
/// File name for the part for the current entry
/// </summary>
public string Name { get; }
/// <summary>
/// Uncompressed size of the current entry in the part
/// </summary>
public long Size { get; }
/// <summary>
/// Uncompressed size of the current entry in the part
/// </summary>
public long Size { get; }
/// <summary>
/// Compressed size of the current entry in the part
/// </summary>
public long CompressedSize { get; }
/// <summary>
/// Compressed size of the current entry in the part
/// </summary>
public long CompressedSize { get; }
}

View File

@@ -4,83 +4,83 @@ namespace SharpCompress.Common;
internal static class FlagUtility
{
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(long bitField, T flag)
where T : struct => HasFlag(bitField, flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(long bitField, T flag)
where T : struct => HasFlag(bitField, flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(ulong bitField, T flag)
where T : struct => HasFlag(bitField, flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(ulong bitField, T flag)
where T : struct => HasFlag(bitField, flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag(ulong bitField, ulong flag) => ((bitField & flag) == flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag(ulong bitField, ulong flag) => ((bitField & flag) == flag);
public static bool HasFlag(short bitField, short flag) => ((bitField & flag) == flag);
public static bool HasFlag(short bitField, short flag) => ((bitField & flag) == flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(T bitField, T flag)
where T : struct => HasFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag));
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag<T>(T bitField, T flag)
where T : struct => HasFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag));
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag(long bitField, long flag) => ((bitField & flag) == flag);
/// <summary>
/// Returns true if the flag is set on the specified bit field.
/// Currently only works with 32-bit bitfields.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to test</param>
/// <returns></returns>
public static bool HasFlag(long bitField, long flag) => ((bitField & flag) == flag);
/// <summary>
/// Sets a bit-field to either on or off for the specified flag.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to change</param>
/// <param name="on">bool</param>
/// <returns>The flagged variable with the flag changed</returns>
public static long SetFlag(long bitField, long flag, bool on)
/// <summary>
/// Sets a bit-field to either on or off for the specified flag.
/// </summary>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to change</param>
/// <param name="on">bool</param>
/// <returns>The flagged variable with the flag changed</returns>
public static long SetFlag(long bitField, long flag, bool on)
{
if (on)
{
if (on)
{
return bitField | flag;
}
return bitField & (~flag);
return bitField | flag;
}
return bitField & (~flag);
}
/// <summary>
/// Sets a bit-field to either on or off for the specified flag.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to change</param>
/// <param name="on">bool</param>
/// <returns>The flagged variable with the flag changed</returns>
public static long SetFlag<T>(T bitField, T flag, bool on)
where T : struct => SetFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag), on);
/// <summary>
/// Sets a bit-field to either on or off for the specified flag.
/// </summary>
/// <typeparam name="T">Enumeration with Flags attribute</typeparam>
/// <param name="bitField">Flagged variable</param>
/// <param name="flag">Flag to change</param>
/// <param name="on">bool</param>
/// <returns>The flagged variable with the flag changed</returns>
public static long SetFlag<T>(T bitField, T flag, bool on)
where T : struct => SetFlag(Convert.ToInt64(bitField), Convert.ToInt64(flag), on);
}

View File

@@ -6,43 +6,43 @@ namespace SharpCompress.Common.GZip;
public class GZipEntry : Entry
{
private readonly GZipFilePart? _filePart;
private readonly GZipFilePart? _filePart;
internal GZipEntry(GZipFilePart? filePart)
{
_filePart = filePart;
}
internal GZipEntry(GZipFilePart? filePart)
{
_filePart = filePart;
}
public override CompressionType CompressionType => CompressionType.GZip;
public override CompressionType CompressionType => CompressionType.GZip;
public override long Crc => _filePart?.Crc ?? 0;
public override long Crc => _filePart?.Crc ?? 0;
public override string? Key => _filePart?.FilePartName;
public override string? Key => _filePart?.FilePartName;
public override string? LinkTarget => null;
public override string? LinkTarget => null;
public override long CompressedSize => 0;
public override long CompressedSize => 0;
public override long Size => _filePart?.UncompressedSize ?? 0;
public override long Size => _filePart?.UncompressedSize ?? 0;
public override DateTime? LastModifiedTime => _filePart?.DateModified;
public override DateTime? LastModifiedTime => _filePart?.DateModified;
public override DateTime? CreatedTime => null;
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => false;
public override bool IsEncrypted => false;
public override bool IsDirectory => false;
public override bool IsDirectory => false;
public override bool IsSplitAfter => false;
public override bool IsSplitAfter => false;
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
internal override IEnumerable<FilePart> Parts => _filePart.Empty();
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, OptionsBase options)
{
yield return new GZipEntry(new GZipFilePart(stream, options.ArchiveEncoding));
}
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, OptionsBase options)
{
yield return new GZipEntry(new GZipFilePart(stream, options.ArchiveEncoding));
}
}

View File

@@ -10,122 +10,122 @@ namespace SharpCompress.Common.GZip;
internal sealed class GZipFilePart : FilePart
{
private string? _name;
private readonly Stream _stream;
private string? _name;
private readonly Stream _stream;
internal GZipFilePart(Stream stream, ArchiveEncoding archiveEncoding)
: base(archiveEncoding)
internal GZipFilePart(Stream stream, ArchiveEncoding archiveEncoding)
: base(archiveEncoding)
{
_stream = stream;
ReadAndValidateGzipHeader();
if (stream.CanSeek)
{
_stream = stream;
ReadAndValidateGzipHeader();
if (stream.CanSeek)
{
var position = stream.Position;
stream.Position = stream.Length - 8;
ReadTrailer();
stream.Position = position;
}
EntryStartPosition = stream.Position;
var position = stream.Position;
stream.Position = stream.Length - 8;
ReadTrailer();
stream.Position = position;
}
EntryStartPosition = stream.Position;
}
internal long EntryStartPosition { get; }
internal DateTime? DateModified { get; private set; }
internal uint? Crc { get; private set; }
internal uint? UncompressedSize { get; private set; }
internal override string? FilePartName => _name;
internal override Stream GetCompressedStream() =>
new DeflateStream(_stream, CompressionMode.Decompress);
internal override Stream GetRawStream() => _stream;
private void ReadTrailer()
{
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
Span<byte> trailer = stackalloc byte[8];
var n = _stream.Read(trailer);
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4));
}
private void ReadAndValidateGzipHeader()
{
// read the header on the first read
Span<byte> header = stackalloc byte[10];
var n = _stream.Read(header);
// workitem 8501: handle edge case (decompress empty stream)
if (n == 0)
{
return;
}
internal long EntryStartPosition { get; }
internal DateTime? DateModified { get; private set; }
internal uint? Crc { get; private set; }
internal uint? UncompressedSize { get; private set; }
internal override string? FilePartName => _name;
internal override Stream GetCompressedStream() =>
new DeflateStream(_stream, CompressionMode.Decompress);
internal override Stream GetRawStream() => _stream;
private void ReadTrailer()
if (n != 10)
{
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
Span<byte> trailer = stackalloc byte[8];
var n = _stream.Read(trailer);
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4));
throw new ZlibException("Not a valid GZIP stream.");
}
private void ReadAndValidateGzipHeader()
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
{
// read the header on the first read
Span<byte> header = stackalloc byte[10];
var n = _stream.Read(header);
// workitem 8501: handle edge case (decompress empty stream)
if (n == 0)
{
return;
}
if (n != 10)
{
throw new ZlibException("Not a valid GZIP stream.");
}
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
{
throw new ZlibException("Bad GZIP header.");
}
var timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4));
DateModified = TarHeader.EPOCH.AddSeconds(timet);
if ((header[3] & 0x04) == 0x04)
{
// read and discard extra field
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
var extraLength = (short)(header[0] + (header[1] * 256));
var extra = new byte[extraLength];
if (!_stream.ReadFully(extra))
{
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
}
n = extraLength;
}
if ((header[3] & 0x08) == 0x08)
{
_name = ReadZeroTerminatedString(_stream);
}
if ((header[3] & 0x10) == 0x010)
{
ReadZeroTerminatedString(_stream);
}
if ((header[3] & 0x02) == 0x02)
{
_stream.ReadByte(); // CRC16, ignore
}
throw new ZlibException("Bad GZIP header.");
}
private string ReadZeroTerminatedString(Stream stream)
var timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4));
DateModified = TarHeader.EPOCH.AddSeconds(timet);
if ((header[3] & 0x04) == 0x04)
{
Span<byte> buf1 = stackalloc byte[1];
var list = new List<byte>();
var done = false;
do
{
// workitem 7740
var n = stream.Read(buf1);
if (n != 1)
{
throw new ZlibException("Unexpected EOF reading GZIP header.");
}
if (buf1[0] == 0)
{
done = true;
}
else
{
list.Add(buf1[0]);
}
} while (!done);
var buffer = list.ToArray();
return ArchiveEncoding.Decode(buffer);
// read and discard extra field
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
var extraLength = (short)(header[0] + (header[1] * 256));
var extra = new byte[extraLength];
if (!_stream.ReadFully(extra))
{
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
}
n = extraLength;
}
if ((header[3] & 0x08) == 0x08)
{
_name = ReadZeroTerminatedString(_stream);
}
if ((header[3] & 0x10) == 0x010)
{
ReadZeroTerminatedString(_stream);
}
if ((header[3] & 0x02) == 0x02)
{
_stream.ReadByte(); // CRC16, ignore
}
}
private string ReadZeroTerminatedString(Stream stream)
{
Span<byte> buf1 = stackalloc byte[1];
var list = new List<byte>();
var done = false;
do
{
// workitem 7740
var n = stream.Read(buf1);
if (n != 1)
{
throw new ZlibException("Unexpected EOF reading GZIP header.");
}
if (buf1[0] == 0)
{
done = true;
}
else
{
list.Add(buf1[0]);
}
} while (!done);
var buffer = list.ToArray();
return ArchiveEncoding.Decode(buffer);
}
}

View File

@@ -5,16 +5,16 @@ namespace SharpCompress.Common.GZip;
public class GZipVolume : Volume
{
public GZipVolume(Stream stream, ReaderOptions? options, int index)
: base(stream, options, index) { }
public GZipVolume(Stream stream, ReaderOptions? options, int index)
: base(stream, options, index) { }
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
: base(fileInfo.OpenRead(), options)
{
options.LeaveStreamOpen = false;
}
public GZipVolume(FileInfo fileInfo, ReaderOptions options)
: base(fileInfo.OpenRead(), options)
{
options.LeaveStreamOpen = false;
}
public override bool IsFirstVolume => true;
public override bool IsFirstVolume => true;
public override bool IsMultiVolume => true;
public override bool IsMultiVolume => true;
}

View File

@@ -4,47 +4,47 @@ namespace SharpCompress.Common;
internal static class EntryExtensions
{
internal static void PreserveExtractionOptions(
this IEntry entry,
string destinationFileName,
ExtractionOptions options
)
internal static void PreserveExtractionOptions(
this IEntry entry,
string destinationFileName,
ExtractionOptions options
)
{
if (options.PreserveFileTime || options.PreserveAttributes)
{
if (options.PreserveFileTime || options.PreserveAttributes)
var nf = new FileInfo(destinationFileName);
if (!nf.Exists)
{
return;
}
// update file time to original packed time
if (options.PreserveFileTime)
{
if (entry.CreatedTime.HasValue)
{
var nf = new FileInfo(destinationFileName);
if (!nf.Exists)
{
return;
}
// update file time to original packed time
if (options.PreserveFileTime)
{
if (entry.CreatedTime.HasValue)
{
nf.CreationTime = entry.CreatedTime.Value;
}
if (entry.LastModifiedTime.HasValue)
{
nf.LastWriteTime = entry.LastModifiedTime.Value;
}
if (entry.LastAccessedTime.HasValue)
{
nf.LastAccessTime = entry.LastAccessedTime.Value;
}
}
if (options.PreserveAttributes)
{
if (entry.Attrib.HasValue)
{
nf.Attributes = (FileAttributes)
System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
}
}
nf.CreationTime = entry.CreatedTime.Value;
}
if (entry.LastModifiedTime.HasValue)
{
nf.LastWriteTime = entry.LastModifiedTime.Value;
}
if (entry.LastAccessedTime.HasValue)
{
nf.LastAccessTime = entry.LastAccessedTime.Value;
}
}
if (options.PreserveAttributes)
{
if (entry.Attrib.HasValue)
{
nf.Attributes = (FileAttributes)
System.Enum.ToObject(typeof(FileAttributes), entry.Attrib.Value);
}
}
}
}
}

View File

@@ -4,21 +4,21 @@ namespace SharpCompress.Common;
public interface IEntry
{
CompressionType CompressionType { get; }
DateTime? ArchivedTime { get; }
long CompressedSize { get; }
long Crc { get; }
DateTime? CreatedTime { get; }
string? Key { get; }
string? LinkTarget { get; }
bool IsDirectory { get; }
bool IsEncrypted { get; }
bool IsSplitAfter { get; }
bool IsSolid { get; }
int VolumeIndexFirst { get; }
int VolumeIndexLast { get; }
DateTime? LastAccessedTime { get; }
DateTime? LastModifiedTime { get; }
long Size { get; }
int? Attrib { get; }
CompressionType CompressionType { get; }
DateTime? ArchivedTime { get; }
long CompressedSize { get; }
long Crc { get; }
DateTime? CreatedTime { get; }
string? Key { get; }
string? LinkTarget { get; }
bool IsDirectory { get; }
bool IsEncrypted { get; }
bool IsSplitAfter { get; }
bool IsSolid { get; }
int VolumeIndexFirst { get; }
int VolumeIndexLast { get; }
DateTime? LastAccessedTime { get; }
DateTime? LastModifiedTime { get; }
long Size { get; }
int? Attrib { get; }
}

View File

@@ -2,6 +2,6 @@ namespace SharpCompress.Common;
public interface IExtractionListener
{
void FireFilePartExtractionBegin(string name, long size, long compressedSize);
void FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes);
void FireFilePartExtractionBegin(string name, long size, long compressedSize);
void FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes);
}

View File

@@ -4,7 +4,7 @@ namespace SharpCompress.Common;
public interface IVolume : IDisposable
{
int Index { get; }
int Index { get; }
string? FileName { get; }
string? FileName { get; }
}

View File

@@ -2,10 +2,10 @@ namespace SharpCompress.Common;
public class OptionsBase
{
/// <summary>
/// SharpCompress will keep the supplied streams open. Default is true.
/// </summary>
public bool LeaveStreamOpen { get; set; } = true;
/// <summary>
/// SharpCompress will keep the supplied streams open. Default is true.
/// </summary>
public bool LeaveStreamOpen { get; set; } = true;
public ArchiveEncoding ArchiveEncoding { get; set; } = new();
public ArchiveEncoding ArchiveEncoding { get; set; } = new();
}

View File

@@ -8,80 +8,80 @@ namespace SharpCompress.Common.Rar;
internal class CryptKey3 : ICryptKey
{
const int AES_128 = 128;
const int AES_128 = 128;
private string _password;
private string _password;
public CryptKey3(string password)
public CryptKey3(string password)
{
_password = password ?? "";
}
public ICryptoTransform Transformer(byte[] salt)
{
var aesIV = new byte[EncryptionConstV5.SIZE_INITV];
var rawLength = 2 * _password.Length;
var rawPassword = new byte[rawLength + EncryptionConstV5.SIZE_SALT30];
var passwordBytes = Encoding.UTF8.GetBytes(_password);
for (var i = 0; i < _password.Length; i++)
{
_password = password ?? "";
rawPassword[i * 2] = passwordBytes[i];
rawPassword[(i * 2) + 1] = 0;
}
public ICryptoTransform Transformer(byte[] salt)
for (var i = 0; i < salt.Length; i++)
{
var aesIV = new byte[EncryptionConstV5.SIZE_INITV];
var rawLength = 2 * _password.Length;
var rawPassword = new byte[rawLength + EncryptionConstV5.SIZE_SALT30];
var passwordBytes = Encoding.UTF8.GetBytes(_password);
for (var i = 0; i < _password.Length; i++)
{
rawPassword[i * 2] = passwordBytes[i];
rawPassword[(i * 2) + 1] = 0;
}
for (var i = 0; i < salt.Length; i++)
{
rawPassword[i + rawLength] = salt[i];
}
var msgDigest = SHA1.Create();
const int noOfRounds = (1 << 18);
const int iblock = 3;
byte[] digest;
var data = new byte[(rawPassword.Length + iblock) * noOfRounds];
//TODO slow code below, find ways to optimize
for (var i = 0; i < noOfRounds; i++)
{
rawPassword.CopyTo(data, i * (rawPassword.Length + iblock));
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i;
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8);
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)(i >> 16);
if (i % (noOfRounds / EncryptionConstV5.SIZE_INITV) == 0)
{
digest = msgDigest.ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock));
aesIV[i / (noOfRounds / EncryptionConstV5.SIZE_INITV)] = digest[19];
}
}
digest = msgDigest.ComputeHash(data);
//slow code ends
var aesKey = new byte[EncryptionConstV5.SIZE_INITV];
for (var i = 0; i < 4; i++)
{
for (var j = 0; j < 4; j++)
{
aesKey[(i * 4) + j] = (byte)(
(
((digest[i * 4] * 0x1000000) & 0xff000000)
| (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000)
| (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00)
| (uint)(digest[(i * 4) + 3] & 0xff)
) >> (j * 8)
);
}
}
var aes = Aes.Create();
aes.KeySize = AES_128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
aes.Key = aesKey;
aes.IV = aesIV;
return aes.CreateDecryptor();
rawPassword[i + rawLength] = salt[i];
}
var msgDigest = SHA1.Create();
const int noOfRounds = (1 << 18);
const int iblock = 3;
byte[] digest;
var data = new byte[(rawPassword.Length + iblock) * noOfRounds];
//TODO slow code below, find ways to optimize
for (var i = 0; i < noOfRounds; i++)
{
rawPassword.CopyTo(data, i * (rawPassword.Length + iblock));
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i;
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8);
data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)(i >> 16);
if (i % (noOfRounds / EncryptionConstV5.SIZE_INITV) == 0)
{
digest = msgDigest.ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock));
aesIV[i / (noOfRounds / EncryptionConstV5.SIZE_INITV)] = digest[19];
}
}
digest = msgDigest.ComputeHash(data);
//slow code ends
var aesKey = new byte[EncryptionConstV5.SIZE_INITV];
for (var i = 0; i < 4; i++)
{
for (var j = 0; j < 4; j++)
{
aesKey[(i * 4) + j] = (byte)(
(
((digest[i * 4] * 0x1000000) & 0xff000000)
| (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000)
| (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00)
| (uint)(digest[(i * 4) + 3] & 0xff)
) >> (j * 8)
);
}
}
var aes = Aes.Create();
aes.KeySize = AES_128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
aes.Key = aesKey;
aes.IV = aesIV;
return aes.CreateDecryptor();
}
}

View File

@@ -8,88 +8,88 @@ namespace SharpCompress.Common.Rar;
internal class CryptKey5 : ICryptKey
{
private const int AES_256 = 256;
private const int AES_256 = 256;
private const int DERIVED_KEY_LENGTH = 0x10;
private const int SHA256_DIGEST_SIZE = 32;
private const int SHA256_DIGEST_SIZE = 32;
private string _password;
private Rar5CryptoInfo _cryptoInfo;
private byte[] _pswCheck = [];
private byte[] _hashKey = [];
private string _password;
private Rar5CryptoInfo _cryptoInfo;
private byte[] _pswCheck = [];
private byte[] _hashKey = [];
public CryptKey5(string? password, Rar5CryptoInfo rar5CryptoInfo)
public CryptKey5(string? password, Rar5CryptoInfo rar5CryptoInfo)
{
_password = password ?? "";
_cryptoInfo = rar5CryptoInfo;
}
public byte[] PswCheck => _pswCheck;
public byte[] HashKey => _hashKey;
private static List<byte[]> GenerateRarPBKDF2Key(
string password,
byte[] salt,
int iterations,
int keyLength
)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password));
var block = hmac.ComputeHash(salt);
var finalHash = (byte[])block.Clone();
var loop = new int[] { iterations, 17, 17 };
var res = new List<byte[]> { };
for (var x = 0; x < 3; x++)
{
_password = password ?? "";
_cryptoInfo = rar5CryptoInfo;
for (var i = 1; i < loop[x]; i++)
{
block = hmac.ComputeHash(block);
for (var j = 0; j < finalHash.Length; j++)
{
finalHash[j] ^= block[j];
}
}
res.Add((byte[])finalHash.Clone());
}
public byte[] PswCheck => _pswCheck;
return res;
}
public byte[] HashKey => _hashKey;
public ICryptoTransform Transformer(byte[] salt)
{
var iterations = (1 << _cryptoInfo.LG2Count); // Adjust the number of iterations as needed
private static List<byte[]> GenerateRarPBKDF2Key(
string password,
byte[] salt,
int iterations,
int keyLength
)
var salt_rar5 = salt.Concat(new byte[] { 0, 0, 0, 1 });
var derivedKey = GenerateRarPBKDF2Key(
_password,
salt_rar5.ToArray(),
iterations,
DERIVED_KEY_LENGTH
);
_hashKey = derivedKey[1];
_pswCheck = new byte[EncryptionConstV5.SIZE_PSWCHECK];
for (var i = 0; i < SHA256_DIGEST_SIZE; i++)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password));
var block = hmac.ComputeHash(salt);
var finalHash = (byte[])block.Clone();
var loop = new int[] { iterations, 17, 17 };
var res = new List<byte[]> { };
for (var x = 0; x < 3; x++)
{
for (var i = 1; i < loop[x]; i++)
{
block = hmac.ComputeHash(block);
for (var j = 0; j < finalHash.Length; j++)
{
finalHash[j] ^= block[j];
}
}
res.Add((byte[])finalHash.Clone());
}
return res;
_pswCheck[i % EncryptionConstV5.SIZE_PSWCHECK] ^= derivedKey[2][i];
}
public ICryptoTransform Transformer(byte[] salt)
if (_cryptoInfo.UsePswCheck && !_cryptoInfo.PswCheck.SequenceEqual(_pswCheck))
{
var iterations = (1 << _cryptoInfo.LG2Count); // Adjust the number of iterations as needed
var salt_rar5 = salt.Concat(new byte[] { 0, 0, 0, 1 });
var derivedKey = GenerateRarPBKDF2Key(
_password,
salt_rar5.ToArray(),
iterations,
DERIVED_KEY_LENGTH
);
_hashKey = derivedKey[1];
_pswCheck = new byte[EncryptionConstV5.SIZE_PSWCHECK];
for (var i = 0; i < SHA256_DIGEST_SIZE; i++)
{
_pswCheck[i % EncryptionConstV5.SIZE_PSWCHECK] ^= derivedKey[2][i];
}
if (_cryptoInfo.UsePswCheck && !_cryptoInfo.PswCheck.SequenceEqual(_pswCheck))
{
throw new CryptographicException("The password did not match.");
}
var aes = Aes.Create();
aes.KeySize = AES_256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
aes.Key = derivedKey[0];
aes.IV = _cryptoInfo.InitV;
return aes.CreateDecryptor();
throw new CryptographicException("The password did not match.");
}
var aes = Aes.Create();
aes.KeySize = AES_256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
aes.Key = derivedKey[0];
aes.IV = _cryptoInfo.InitV;
return aes.CreateDecryptor();
}
}

View File

@@ -4,28 +4,28 @@ namespace SharpCompress.Common.Rar.Headers;
internal class AvHeader : RarHeader
{
public AvHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Av)
public AvHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Av)
{
if (IsRar5)
{
if (IsRar5)
{
throw new InvalidFormatException("unexpected rar5 record");
}
throw new InvalidFormatException("unexpected rar5 record");
}
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
UnpackVersion = reader.ReadByte();
Method = reader.ReadByte();
AvVersion = reader.ReadByte();
AvInfoCrc = reader.ReadInt32();
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
UnpackVersion = reader.ReadByte();
Method = reader.ReadByte();
AvVersion = reader.ReadByte();
AvInfoCrc = reader.ReadInt32();
}
internal int AvInfoCrc { get; private set; }
internal int AvInfoCrc { get; private set; }
internal byte UnpackVersion { get; private set; }
internal byte UnpackVersion { get; private set; }
internal byte Method { get; private set; }
internal byte Method { get; private set; }
internal byte AvVersion { get; private set; }
internal byte AvVersion { get; private set; }
}

View File

@@ -6,11 +6,11 @@ namespace SharpCompress.Common.Rar.Headers;
internal class ArchiveCryptHeader : RarHeader
{
public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Crypt) { }
public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Crypt) { }
public Rar5CryptoInfo CryptInfo = new();
public Rar5CryptoInfo CryptInfo = new();
protected override void ReadFinish(MarkingBinaryReader reader) =>
CryptInfo = new Rar5CryptoInfo(reader, false);
protected override void ReadFinish(MarkingBinaryReader reader) =>
CryptInfo = new Rar5CryptoInfo(reader, false);
}

View File

@@ -4,56 +4,55 @@ namespace SharpCompress.Common.Rar.Headers;
internal sealed class ArchiveHeader : RarHeader
{
public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Archive) { }
public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Archive) { }
protected override void ReadFinish(MarkingBinaryReader reader)
protected override void ReadFinish(MarkingBinaryReader reader)
{
if (IsRar5)
{
if (IsRar5)
{
Flags = reader.ReadRarVIntUInt16();
if (HasFlag(ArchiveFlagsV5.HAS_VOLUME_NUMBER))
{
VolumeNumber = (int)reader.ReadRarVIntUInt32();
}
// later: we may have a locator record if we need it
//if (ExtraSize != 0) {
// ReadLocator(reader);
//}
}
else
{
Flags = HeaderFlags;
HighPosAv = reader.ReadInt16();
PosAv = reader.ReadInt32();
if (HasFlag(ArchiveFlagsV4.ENCRYPT_VER))
{
EncryptionVersion = reader.ReadByte();
}
}
Flags = reader.ReadRarVIntUInt16();
if (HasFlag(ArchiveFlagsV5.HAS_VOLUME_NUMBER))
{
VolumeNumber = (int)reader.ReadRarVIntUInt32();
}
// later: we may have a locator record if we need it
//if (ExtraSize != 0) {
// ReadLocator(reader);
//}
}
else
{
Flags = HeaderFlags;
HighPosAv = reader.ReadInt16();
PosAv = reader.ReadInt32();
if (HasFlag(ArchiveFlagsV4.ENCRYPT_VER))
{
EncryptionVersion = reader.ReadByte();
}
}
}
private ushort Flags { get; set; }
private ushort Flags { get; set; }
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
internal int? VolumeNumber { get; private set; }
internal int? VolumeNumber { get; private set; }
internal short? HighPosAv { get; private set; }
internal short? HighPosAv { get; private set; }
internal int? PosAv { get; private set; }
internal int? PosAv { get; private set; }
private byte? EncryptionVersion { get; set; }
private byte? EncryptionVersion { get; set; }
public bool? IsEncrypted => IsRar5 ? null : HasFlag(ArchiveFlagsV4.PASSWORD);
public bool? IsEncrypted => IsRar5 ? null : HasFlag(ArchiveFlagsV4.PASSWORD);
public bool OldNumberingFormat => !IsRar5 && !HasFlag(ArchiveFlagsV4.NEW_NUMBERING);
public bool OldNumberingFormat => !IsRar5 && !HasFlag(ArchiveFlagsV4.NEW_NUMBERING);
public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.VOLUME : ArchiveFlagsV4.VOLUME);
public bool IsVolume => HasFlag(IsRar5 ? ArchiveFlagsV5.VOLUME : ArchiveFlagsV4.VOLUME);
// RAR5: Volume number field is present. True for all volumes except first.
public bool IsFirstVolume =>
IsRar5 ? VolumeNumber is null : HasFlag(ArchiveFlagsV4.FIRST_VOLUME);
// RAR5: Volume number field is present. True for all volumes except first.
public bool IsFirstVolume => IsRar5 ? VolumeNumber is null : HasFlag(ArchiveFlagsV4.FIRST_VOLUME);
public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.SOLID : ArchiveFlagsV4.SOLID);
public bool IsSolid => HasFlag(IsRar5 ? ArchiveFlagsV5.SOLID : ArchiveFlagsV4.SOLID);
}

View File

@@ -4,27 +4,27 @@ namespace SharpCompress.Common.Rar.Headers;
internal class CommentHeader : RarHeader
{
protected CommentHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Comment)
protected CommentHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Comment)
{
if (IsRar5)
{
if (IsRar5)
{
throw new InvalidFormatException("unexpected rar5 record");
}
throw new InvalidFormatException("unexpected rar5 record");
}
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
UnpSize = reader.ReadInt16();
UnpVersion = reader.ReadByte();
UnpMethod = reader.ReadByte();
CommCrc = reader.ReadInt16();
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
UnpSize = reader.ReadInt16();
UnpVersion = reader.ReadByte();
UnpMethod = reader.ReadByte();
CommCrc = reader.ReadInt16();
}
internal short UnpSize { get; private set; }
internal short UnpSize { get; private set; }
internal byte UnpVersion { get; private set; }
internal byte UnpVersion { get; private set; }
internal byte UnpMethod { get; private set; }
internal short CommCrc { get; private set; }
internal byte UnpMethod { get; private set; }
internal short CommCrc { get; private set; }
}

View File

@@ -4,34 +4,34 @@ namespace SharpCompress.Common.Rar.Headers;
internal class EndArchiveHeader : RarHeader
{
public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.EndArchive) { }
public EndArchiveHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.EndArchive) { }
protected override void ReadFinish(MarkingBinaryReader reader)
protected override void ReadFinish(MarkingBinaryReader reader)
{
if (IsRar5)
{
if (IsRar5)
{
Flags = reader.ReadRarVIntUInt16();
}
else
{
Flags = HeaderFlags;
if (HasFlag(EndArchiveFlagsV4.DATA_CRC))
{
ArchiveCrc = reader.ReadInt32();
}
if (HasFlag(EndArchiveFlagsV4.VOLUME_NUMBER))
{
VolumeNumber = reader.ReadInt16();
}
}
Flags = reader.ReadRarVIntUInt16();
}
else
{
Flags = HeaderFlags;
if (HasFlag(EndArchiveFlagsV4.DATA_CRC))
{
ArchiveCrc = reader.ReadInt32();
}
if (HasFlag(EndArchiveFlagsV4.VOLUME_NUMBER))
{
VolumeNumber = reader.ReadInt16();
}
}
}
private ushort Flags { get; set; }
private ushort Flags { get; set; }
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
internal int? ArchiveCrc { get; private set; }
internal int? ArchiveCrc { get; private set; }
internal short? VolumeNumber { get; private set; }
internal short? VolumeNumber { get; private set; }
}

View File

@@ -15,472 +15,467 @@ namespace SharpCompress.Common.Rar.Headers;
internal class FileHeader : RarHeader
{
private byte[]? _hash;
private byte[]? _hash;
public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
: base(header, reader, headerType) { }
public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
: base(header, reader, headerType) { }
protected override void ReadFinish(MarkingBinaryReader reader)
protected override void ReadFinish(MarkingBinaryReader reader)
{
if (IsRar5)
{
if (IsRar5)
{
ReadFromReaderV5(reader);
}
else
{
ReadFromReaderV4(reader);
}
ReadFromReaderV5(reader);
}
else
{
ReadFromReaderV4(reader);
}
}
private void ReadFromReaderV5(MarkingBinaryReader reader)
{
Flags = reader.ReadRarVIntUInt16();
var lvalue = checked((long)reader.ReadRarVInt());
// long.MaxValue causes the unpack code to finish when the input stream is exhausted
UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue;
FileAttributes = reader.ReadRarVIntUInt32();
if (HasFlag(FileFlagsV5.HAS_MOD_TIME))
{
FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32());
}
private void ReadFromReaderV5(MarkingBinaryReader reader)
if (HasFlag(FileFlagsV5.HAS_CRC32))
{
Flags = reader.ReadRarVIntUInt16();
FileCrc = reader.ReadBytes(4);
}
var lvalue = checked((long)reader.ReadRarVInt());
var compressionInfo = reader.ReadRarVIntUInt16();
// long.MaxValue causes the unpack code to finish when the input stream is exhausted
UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue;
// Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0.
// "+ 50" to not mix with old RAR format algorithms. For example,
// we may need to use the compression algorithm 15 in the future,
// but it was already used in RAR 1.5 and Unpack needs to distinguish
// them.
CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50);
FileAttributes = reader.ReadRarVIntUInt32();
// 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files.
// It can be set only for file headers and is never set for service headers.
IsSolid = (compressionInfo & 0x40) == 0x40;
if (HasFlag(FileFlagsV5.HAS_MOD_TIME))
{
FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32());
}
// Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression.
CompressionMethod = (byte)((compressionInfo >> 7) & 0x7);
if (HasFlag(FileFlagsV5.HAS_CRC32))
{
FileCrc = reader.ReadBytes(4);
}
// Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB.
WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo >> 10) & 0xf);
var compressionInfo = reader.ReadRarVIntUInt16();
HostOs = reader.ReadRarVIntByte();
// Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0.
// "+ 50" to not mix with old RAR format algorithms. For example,
// we may need to use the compression algorithm 15 in the future,
// but it was already used in RAR 1.5 and Unpack needs to distinguish
// them.
CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50);
var nameSize = reader.ReadRarVIntUInt16();
// 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files.
// It can be set only for file headers and is never set for service headers.
IsSolid = (compressionInfo & 0x40) == 0x40;
// Variable length field containing Name length bytes in UTF-8 format without trailing zero.
// For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names.
// Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field.
//
// TODO: not sure if anything needs to be done to handle the following:
// If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8
// we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character
// to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting.
// Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not
// portable and can be correctly unpacked only on the same system where they were created.
//
// For service header this field contains a name of service header. Now the following names are used:
// CMT Archive comment
// QO Archive quick open data
// ACL NTFS file permissions
// STM NTFS alternate data stream
// RR Recovery record
var b = reader.ReadBytes(nameSize);
FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length));
// Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression.
CompressionMethod = (byte)((compressionInfo >> 7) & 0x7);
// extra size seems to be redudant since we know the total header size
if (ExtraSize != RemainingHeaderBytes(reader))
{
throw new InvalidFormatException("rar5 header size / extra size inconsistency");
}
// Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB.
WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo >> 10) & 0xf);
const ushort FHEXTRA_CRYPT = 0x01;
const ushort FHEXTRA_HASH = 0x02;
const ushort FHEXTRA_HTIME = 0x03;
// const ushort FHEXTRA_VERSION = 0x04;
const ushort FHEXTRA_REDIR = 0x05;
// const ushort FHEXTRA_UOWNER = 0x06;
// const ushort FHEXTRA_SUBDATA = 0x07;
HostOs = reader.ReadRarVIntByte();
while (RemainingHeaderBytes(reader) > 0)
{
var size = reader.ReadRarVIntUInt16();
var n = RemainingHeaderBytes(reader);
var type = reader.ReadRarVIntUInt16();
switch (type)
{
case FHEXTRA_CRYPT: // file encryption
{
Rar5CryptoInfo = new Rar5CryptoInfo(reader, true);
var nameSize = reader.ReadRarVIntUInt16();
// Variable length field containing Name length bytes in UTF-8 format without trailing zero.
// For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names.
// Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field.
if (Rar5CryptoInfo.PswCheck.All(singleByte => singleByte == 0))
{
Rar5CryptoInfo = null;
}
}
break;
case FHEXTRA_HASH:
{
const uint FHEXTRA_HASH_BLAKE2 = 0x0;
// const uint HASH_BLAKE2 = 0x03;
const int BLAKE2_DIGEST_SIZE = 0x20;
if ((uint)reader.ReadRarVInt() == FHEXTRA_HASH_BLAKE2)
{
// var hash = HASH_BLAKE2;
_hash = reader.ReadBytes(BLAKE2_DIGEST_SIZE);
}
// enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2};
}
break;
case FHEXTRA_HTIME: // file time
{
var flags = reader.ReadRarVIntUInt16();
var isWindowsTime = (flags & 1) == 0;
if ((flags & 0x2) == 0x2)
{
FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime);
}
if ((flags & 0x4) == 0x4)
{
FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime);
}
if ((flags & 0x8) == 0x8)
{
FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime);
}
}
break;
//TODO
// case FHEXTRA_VERSION: // file version
// {
//
// TODO: not sure if anything needs to be done to handle the following:
// If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8
// we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character
// to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting.
// Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not
// portable and can be correctly unpacked only on the same system where they were created.
// }
// break;
case FHEXTRA_REDIR: // file system redirection
{
RedirType = reader.ReadRarVIntByte();
RedirFlags = reader.ReadRarVIntByte();
var nn = reader.ReadRarVIntUInt16();
var bb = reader.ReadBytes(nn);
RedirTargetName = ConvertPathV5(Encoding.UTF8.GetString(bb, 0, bb.Length));
}
break;
//TODO
// case FHEXTRA_UOWNER: // unix owner
// {
//
// For service header this field contains a name of service header. Now the following names are used:
// CMT Archive comment
// QO Archive quick open data
// ACL NTFS file permissions
// STM NTFS alternate data stream
// RR Recovery record
var b = reader.ReadBytes(nameSize);
FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length));
// }
// break;
// case FHEXTRA_SUBDATA: // service data
// {
//
// }
// break;
// extra size seems to be redudant since we know the total header size
if (ExtraSize != RemainingHeaderBytes(reader))
default:
// skip unknown record types to allow new record types to be added in the future
//Console.WriteLine($"unhandled rar header field type {type}");
break;
}
// drain any trailing bytes of extra record
var did = n - RemainingHeaderBytes(reader);
var drain = size - did;
if (drain > 0)
{
reader.ReadBytes(drain);
}
}
if (AdditionalDataSize != 0)
{
CompressedSize = AdditionalDataSize;
}
}
private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime)
{
if (isWindowsTime)
{
return DateTime.FromFileTime(reader.ReadInt64());
}
else
{
return Utility.UnixTimeToDateTime(reader.ReadUInt32());
}
}
private static string ConvertPathV5(string path)
{
if (Path.DirectorySeparatorChar == '\\')
{
// replace embedded \\ with valid filename char
return path.Replace('\\', '-').Replace('/', '\\');
}
return path;
}
private void ReadFromReaderV4(MarkingBinaryReader reader)
{
Flags = HeaderFlags;
IsSolid = HasFlag(FileFlagsV4.SOLID);
WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5);
var lowUncompressedSize = reader.ReadUInt32();
HostOs = reader.ReadByte();
FileCrc = reader.ReadBytes(4);
FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32());
CompressionAlgorithm = reader.ReadByte();
CompressionMethod = (byte)(reader.ReadByte() - 0x30);
var nameSize = reader.ReadInt16();
FileAttributes = reader.ReadUInt32();
uint highCompressedSize = 0;
uint highUncompressedkSize = 0;
if (HasFlag(FileFlagsV4.LARGE))
{
highCompressedSize = reader.ReadUInt32();
highUncompressedkSize = reader.ReadUInt32();
}
else
{
if (lowUncompressedSize == 0xffffffff)
{
lowUncompressedSize = 0xffffffff;
highUncompressedkSize = int.MaxValue;
}
}
CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize));
UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize);
nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize;
var fileNameBytes = reader.ReadBytes(nameSize);
const int newLhdSize = 32;
switch (HeaderCode)
{
case HeaderCodeV.RAR4_FILE_HEADER:
{
throw new InvalidFormatException("rar5 header size / extra size inconsistency");
}
const ushort FHEXTRA_CRYPT = 0x01;
const ushort FHEXTRA_HASH = 0x02;
const ushort FHEXTRA_HTIME = 0x03;
// const ushort FHEXTRA_VERSION = 0x04;
const ushort FHEXTRA_REDIR = 0x05;
// const ushort FHEXTRA_UOWNER = 0x06;
// const ushort FHEXTRA_SUBDATA = 0x07;
while (RemainingHeaderBytes(reader) > 0)
{
var size = reader.ReadRarVIntUInt16();
var n = RemainingHeaderBytes(reader);
var type = reader.ReadRarVIntUInt16();
switch (type)
if (HasFlag(FileFlagsV4.UNICODE))
{
var length = 0;
while (length < fileNameBytes.Length && fileNameBytes[length] != 0)
{
case FHEXTRA_CRYPT: // file encryption
{
Rar5CryptoInfo = new Rar5CryptoInfo(reader, true);
if (Rar5CryptoInfo.PswCheck.All(singleByte => singleByte == 0))
{
Rar5CryptoInfo = null;
}
}
break;
case FHEXTRA_HASH:
{
const uint FHEXTRA_HASH_BLAKE2 = 0x0;
// const uint HASH_BLAKE2 = 0x03;
const int BLAKE2_DIGEST_SIZE = 0x20;
if ((uint)reader.ReadRarVInt() == FHEXTRA_HASH_BLAKE2)
{
// var hash = HASH_BLAKE2;
_hash = reader.ReadBytes(BLAKE2_DIGEST_SIZE);
}
// enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2};
}
break;
case FHEXTRA_HTIME: // file time
{
var flags = reader.ReadRarVIntUInt16();
var isWindowsTime = (flags & 1) == 0;
if ((flags & 0x2) == 0x2)
{
FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime);
}
if ((flags & 0x4) == 0x4)
{
FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime);
}
if ((flags & 0x8) == 0x8)
{
FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime);
}
}
break;
//TODO
// case FHEXTRA_VERSION: // file version
// {
//
// }
// break;
case FHEXTRA_REDIR: // file system redirection
{
RedirType = reader.ReadRarVIntByte();
RedirFlags = reader.ReadRarVIntByte();
var nn = reader.ReadRarVIntUInt16();
var bb = reader.ReadBytes(nn);
RedirTargetName = ConvertPathV5(Encoding.UTF8.GetString(bb, 0, bb.Length));
}
break;
//TODO
// case FHEXTRA_UOWNER: // unix owner
// {
//
// }
// break;
// case FHEXTRA_SUBDATA: // service data
// {
//
// }
// break;
default:
// skip unknown record types to allow new record types to be added in the future
//Console.WriteLine($"unhandled rar header field type {type}");
break;
length++;
}
// drain any trailing bytes of extra record
var did = n - RemainingHeaderBytes(reader);
var drain = size - did;
if (drain > 0)
if (length != nameSize)
{
reader.ReadBytes(drain);
length++;
FileName = FileNameDecoder.Decode(fileNameBytes, length);
}
}
if (AdditionalDataSize != 0)
{
CompressedSize = AdditionalDataSize;
}
}
private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime)
{
if (isWindowsTime)
{
return DateTime.FromFileTime(reader.ReadInt64());
}
else
{
return Utility.UnixTimeToDateTime(reader.ReadUInt32());
}
}
private static string ConvertPathV5(string path)
{
if (Path.DirectorySeparatorChar == '\\')
{
// replace embedded \\ with valid filename char
return path.Replace('\\', '-').Replace('/', '\\');
}
return path;
}
private void ReadFromReaderV4(MarkingBinaryReader reader)
{
Flags = HeaderFlags;
IsSolid = HasFlag(FileFlagsV4.SOLID);
WindowSize = IsDirectory
? 0U
: ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5);
var lowUncompressedSize = reader.ReadUInt32();
HostOs = reader.ReadByte();
FileCrc = reader.ReadBytes(4);
FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32());
CompressionAlgorithm = reader.ReadByte();
CompressionMethod = (byte)(reader.ReadByte() - 0x30);
var nameSize = reader.ReadInt16();
FileAttributes = reader.ReadUInt32();
uint highCompressedSize = 0;
uint highUncompressedkSize = 0;
if (HasFlag(FileFlagsV4.LARGE))
{
highCompressedSize = reader.ReadUInt32();
highUncompressedkSize = reader.ReadUInt32();
}
else
{
if (lowUncompressedSize == 0xffffffff)
else
{
lowUncompressedSize = 0xffffffff;
highUncompressedkSize = int.MaxValue;
FileName = ArchiveEncoding.Decode(fileNameBytes);
}
}
else
{
FileName = ArchiveEncoding.Decode(fileNameBytes);
}
FileName = ConvertPathV4(FileName);
}
CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize));
UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize);
nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize;
var fileNameBytes = reader.ReadBytes(nameSize);
const int newLhdSize = 32;
switch (HeaderCode)
break;
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
{
case HeaderCodeV.RAR4_FILE_HEADER:
{
if (HasFlag(FileFlagsV4.UNICODE))
{
var length = 0;
while (length < fileNameBytes.Length && fileNameBytes[length] != 0)
{
length++;
}
if (length != nameSize)
{
length++;
FileName = FileNameDecoder.Decode(fileNameBytes, length);
}
else
{
FileName = ArchiveEncoding.Decode(fileNameBytes);
}
}
else
{
FileName = ArchiveEncoding.Decode(fileNameBytes);
}
FileName = ConvertPathV4(FileName);
}
break;
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
{
var datasize = HeaderSize - newLhdSize - nameSize;
if (HasFlag(FileFlagsV4.SALT))
{
datasize -= EncryptionConstV5.SIZE_SALT30;
}
if (datasize > 0)
{
SubData = reader.ReadBytes(datasize);
}
var datasize = HeaderSize - newLhdSize - nameSize;
if (HasFlag(FileFlagsV4.SALT))
{
datasize -= EncryptionConstV5.SIZE_SALT30;
}
if (datasize > 0)
{
SubData = reader.ReadBytes(datasize);
}
if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
{
if (SubData is null)
{
throw new InvalidFormatException();
}
RecoverySectors =
SubData[8]
+ (SubData[9] << 8)
+ (SubData[10] << 16)
+ (SubData[11] << 24);
}
}
break;
}
if (HasFlag(FileFlagsV4.SALT))
{
R4Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT30);
}
if (HasFlag(FileFlagsV4.EXT_TIME))
{
// verify that the end of the header hasn't been reached before reading the Extended Time.
// some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate.
if (RemainingHeaderBytes(reader) >= 2)
if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
{
if (SubData is null)
{
var extendedFlags = reader.ReadUInt16();
if (FileLastModifiedTime is not null)
{
FileLastModifiedTime = ProcessExtendedTimeV4(
extendedFlags,
FileLastModifiedTime,
reader,
0
);
}
FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1);
FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2);
FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3);
throw new InvalidFormatException();
}
RecoverySectors =
SubData[8] + (SubData[9] << 8) + (SubData[10] << 16) + (SubData[11] << 24);
}
}
break;
}
private static long UInt32To64(uint x, uint y)
if (HasFlag(FileFlagsV4.SALT))
{
long l = x;
l <<= 32;
return l + y;
R4Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT30);
}
private static DateTime? ProcessExtendedTimeV4(
ushort extendedFlags,
DateTime? time,
MarkingBinaryReader reader,
int i
)
if (HasFlag(FileFlagsV4.EXT_TIME))
{
var rmode = (uint)extendedFlags >> ((3 - i) * 4);
if ((rmode & 8) == 0)
// verify that the end of the header hasn't been reached before reading the Extended Time.
// some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate.
if (RemainingHeaderBytes(reader) >= 2)
{
var extendedFlags = reader.ReadUInt16();
if (FileLastModifiedTime is not null)
{
return null;
}
if (i != 0)
{
var dosTime = reader.ReadUInt32();
time = Utility.DosDateToDateTime(dosTime);
}
if ((rmode & 4) == 0 && time is not null)
{
time = time.Value.AddSeconds(1);
}
uint nanosecondHundreds = 0;
var count = (int)rmode & 3;
for (var j = 0; j < count; j++)
{
var b = reader.ReadByte();
nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8));
FileLastModifiedTime = ProcessExtendedTimeV4(
extendedFlags,
FileLastModifiedTime,
reader,
0
);
}
//10^-7 to 10^-3
if (time is not null)
{
return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
}
return null;
FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1);
FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2);
FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3);
}
}
}
private static string ConvertPathV4(string path)
private static long UInt32To64(uint x, uint y)
{
long l = x;
l <<= 32;
return l + y;
}
private static DateTime? ProcessExtendedTimeV4(
ushort extendedFlags,
DateTime? time,
MarkingBinaryReader reader,
int i
)
{
var rmode = (uint)extendedFlags >> ((3 - i) * 4);
if ((rmode & 8) == 0)
{
if (Path.DirectorySeparatorChar == '/')
{
return path.Replace('\\', '/');
}
else if (Path.DirectorySeparatorChar == '\\')
{
return path.Replace('/', '\\');
}
return path;
return null;
}
public override string ToString() => FileName ?? "FileHeader";
private ushort Flags { get; set; }
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
internal byte[]? FileCrc
if (i != 0)
{
get => _hash;
private set => _hash = value;
var dosTime = reader.ReadUInt32();
time = Utility.DosDateToDateTime(dosTime);
}
if ((rmode & 4) == 0 && time is not null)
{
time = time.Value.AddSeconds(1);
}
uint nanosecondHundreds = 0;
var count = (int)rmode & 3;
for (var j = 0; j < count; j++)
{
var b = reader.ReadByte();
nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8));
}
// 0 - storing
// 1 - fastest compression
// 2 - fast compression
// 3 - normal compression
// 4 - good compression
// 5 - best compression
internal byte CompressionMethod { get; private set; }
internal bool IsStored => CompressionMethod == 0;
//10^-7 to 10^-3
if (time is not null)
{
return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
}
return null;
}
// eg (see DoUnpack())
//case 15: // rar 1.5 compression
//case 20: // rar 2.x compression
//case 26: // files larger than 2GB
//case 29: // rar 3.x compression
//case 50: // RAR 5.0 compression algorithm.
internal byte CompressionAlgorithm { get; private set; }
private static string ConvertPathV4(string path)
{
if (Path.DirectorySeparatorChar == '/')
{
return path.Replace('\\', '/');
}
else if (Path.DirectorySeparatorChar == '\\')
{
return path.Replace('/', '\\');
}
return path;
}
public bool IsSolid { get; private set; }
public override string ToString() => FileName ?? "FileHeader";
public byte RedirType { get; private set; }
public bool IsRedir => RedirType != 0;
public byte RedirFlags { get; private set; }
public bool IsRedirDirectory => (RedirFlags & RedirFlagV5.DIRECTORY) != 0;
public string? RedirTargetName { get; private set; }
private ushort Flags { get; set; }
// unused for UnpackV1 implementation (limitation)
internal size_t WindowSize { get; private set; }
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
internal byte[]? R4Salt { get; private set; }
internal Rar5CryptoInfo? Rar5CryptoInfo { get; private set; }
private byte HostOs { get; set; }
internal uint FileAttributes { get; private set; }
internal long CompressedSize { get; private set; }
internal long UncompressedSize { get; private set; }
internal string? FileName { get; private set; }
internal byte[]? SubData { get; private set; }
internal int RecoverySectors { get; private set; }
internal long DataStartPosition { get; set; }
public Stream? PackedStream { get; set; }
internal byte[]? FileCrc
{
get => _hash;
private set => _hash = value;
}
public bool IsSplitBefore =>
IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_BEFORE) : HasFlag(FileFlagsV4.SPLIT_BEFORE);
public bool IsSplitAfter =>
IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER);
// 0 - storing
// 1 - fastest compression
// 2 - fast compression
// 3 - normal compression
// 4 - good compression
// 5 - best compression
internal byte CompressionMethod { get; private set; }
internal bool IsStored => CompressionMethod == 0;
public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY);
// eg (see DoUnpack())
//case 15: // rar 1.5 compression
//case 20: // rar 2.x compression
//case 26: // files larger than 2GB
//case 29: // rar 3.x compression
//case 50: // RAR 5.0 compression algorithm.
internal byte CompressionAlgorithm { get; private set; }
public bool IsEncrypted => IsRar5 ? Rar5CryptoInfo != null : HasFlag(FileFlagsV4.PASSWORD);
public bool IsSolid { get; private set; }
internal DateTime? FileLastModifiedTime { get; private set; }
public byte RedirType { get; private set; }
public bool IsRedir => RedirType != 0;
public byte RedirFlags { get; private set; }
public bool IsRedirDirectory => (RedirFlags & RedirFlagV5.DIRECTORY) != 0;
public string? RedirTargetName { get; private set; }
internal DateTime? FileCreatedTime { get; private set; }
// unused for UnpackV1 implementation (limitation)
internal size_t WindowSize { get; private set; }
internal DateTime? FileLastAccessedTime { get; private set; }
internal byte[]? R4Salt { get; private set; }
internal Rar5CryptoInfo? Rar5CryptoInfo { get; private set; }
private byte HostOs { get; set; }
internal uint FileAttributes { get; private set; }
internal long CompressedSize { get; private set; }
internal long UncompressedSize { get; private set; }
internal string? FileName { get; private set; }
internal byte[]? SubData { get; private set; }
internal int RecoverySectors { get; private set; }
internal long DataStartPosition { get; set; }
public Stream? PackedStream { get; set; }
internal DateTime? FileArchivedTime { get; private set; }
public bool IsSplitBefore =>
IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_BEFORE) : HasFlag(FileFlagsV4.SPLIT_BEFORE);
public bool IsSplitAfter =>
IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER);
public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY);
public bool IsEncrypted => IsRar5 ? Rar5CryptoInfo != null : HasFlag(FileFlagsV4.PASSWORD);
internal DateTime? FileLastModifiedTime { get; private set; }
internal DateTime? FileCreatedTime { get; private set; }
internal DateTime? FileLastAccessedTime { get; private set; }
internal DateTime? FileArchivedTime { get; private set; }
}

View File

@@ -7,72 +7,72 @@ namespace SharpCompress.Common.Rar.Headers;
/// </summary>
internal static class FileNameDecoder
{
internal static int GetChar(byte[] name, int pos) => name[pos] & 0xff;
internal static int GetChar(byte[] name, int pos) => name[pos] & 0xff;
internal static string Decode(byte[] name, int encPos)
internal static string Decode(byte[] name, int encPos)
{
var decPos = 0;
var flags = 0;
var flagBits = 0;
var low = 0;
var high = 0;
var highByte = GetChar(name, encPos++);
var buf = new StringBuilder();
while (encPos < name.Length)
{
var decPos = 0;
var flags = 0;
var flagBits = 0;
if (flagBits == 0)
{
flags = GetChar(name, encPos++);
flagBits = 8;
}
switch (flags >> 6)
{
case 0:
buf.Append((char)(GetChar(name, encPos++)));
++decPos;
break;
var low = 0;
var high = 0;
var highByte = GetChar(name, encPos++);
var buf = new StringBuilder();
while (encPos < name.Length)
{
if (flagBits == 0)
case 1:
buf.Append((char)(GetChar(name, encPos++) + (highByte << 8)));
++decPos;
break;
case 2:
low = GetChar(name, encPos);
high = GetChar(name, encPos + 1);
buf.Append((char)((high << 8) + low));
++decPos;
encPos += 2;
break;
case 3:
var length = GetChar(name, encPos++);
if ((length & 0x80) != 0)
{
var correction = GetChar(name, encPos++);
for (
length = (length & 0x7f) + 2;
length > 0 && decPos < name.Length;
length--, decPos++
)
{
flags = GetChar(name, encPos++);
flagBits = 8;
low = (GetChar(name, decPos) + correction) & 0xff;
buf.Append((char)((highByte << 8) + low));
}
switch (flags >> 6)
}
else
{
for (length += 2; length > 0 && decPos < name.Length; length--, decPos++)
{
case 0:
buf.Append((char)(GetChar(name, encPos++)));
++decPos;
break;
case 1:
buf.Append((char)(GetChar(name, encPos++) + (highByte << 8)));
++decPos;
break;
case 2:
low = GetChar(name, encPos);
high = GetChar(name, encPos + 1);
buf.Append((char)((high << 8) + low));
++decPos;
encPos += 2;
break;
case 3:
var length = GetChar(name, encPos++);
if ((length & 0x80) != 0)
{
var correction = GetChar(name, encPos++);
for (
length = (length & 0x7f) + 2;
length > 0 && decPos < name.Length;
length--, decPos++
)
{
low = (GetChar(name, decPos) + correction) & 0xff;
buf.Append((char)((highByte << 8) + low));
}
}
else
{
for (length += 2; length > 0 && decPos < name.Length; length--, decPos++)
{
buf.Append((char)(GetChar(name, decPos)));
}
}
break;
buf.Append((char)(GetChar(name, decPos)));
}
flags = (flags << 2) & 0xff;
flagBits -= 2;
}
return buf.ToString();
}
break;
}
flags = (flags << 2) & 0xff;
flagBits -= 2;
}
return buf.ToString();
}
}

View File

@@ -2,172 +2,172 @@ namespace SharpCompress.Common.Rar.Headers;
public enum HeaderType : byte
{
Null,
Mark,
Archive,
File,
Service,
Comment,
Av,
Protect,
Sign,
NewSub,
EndArchive,
Crypt,
Null,
Mark,
Archive,
File,
Service,
Comment,
Av,
Protect,
Sign,
NewSub,
EndArchive,
Crypt,
}
internal static class HeaderCodeV
{
public const byte RAR4_MARK_HEADER = 0x72;
public const byte RAR4_ARCHIVE_HEADER = 0x73;
public const byte RAR4_FILE_HEADER = 0x74;
public const byte RAR4_COMMENT_HEADER = 0x75;
public const byte RAR4_AV_HEADER = 0x76;
public const byte RAR4_SUB_HEADER = 0x77;
public const byte RAR4_PROTECT_HEADER = 0x78;
public const byte RAR4_SIGN_HEADER = 0x79;
public const byte RAR4_NEW_SUB_HEADER = 0x7a;
public const byte RAR4_END_ARCHIVE_HEADER = 0x7b;
public const byte RAR4_MARK_HEADER = 0x72;
public const byte RAR4_ARCHIVE_HEADER = 0x73;
public const byte RAR4_FILE_HEADER = 0x74;
public const byte RAR4_COMMENT_HEADER = 0x75;
public const byte RAR4_AV_HEADER = 0x76;
public const byte RAR4_SUB_HEADER = 0x77;
public const byte RAR4_PROTECT_HEADER = 0x78;
public const byte RAR4_SIGN_HEADER = 0x79;
public const byte RAR4_NEW_SUB_HEADER = 0x7a;
public const byte RAR4_END_ARCHIVE_HEADER = 0x7b;
public const byte RAR5_ARCHIVE_HEADER = 0x01;
public const byte RAR5_FILE_HEADER = 0x02;
public const byte RAR5_SERVICE_HEADER = 0x03;
public const byte RAR5_ARCHIVE_ENCRYPTION_HEADER = 0x04;
public const byte RAR5_END_ARCHIVE_HEADER = 0x05;
public const byte RAR5_ARCHIVE_HEADER = 0x01;
public const byte RAR5_FILE_HEADER = 0x02;
public const byte RAR5_SERVICE_HEADER = 0x03;
public const byte RAR5_ARCHIVE_ENCRYPTION_HEADER = 0x04;
public const byte RAR5_END_ARCHIVE_HEADER = 0x05;
}
internal static class HeaderFlagsV4
{
public const ushort HAS_DATA = 0x8000;
public const ushort HAS_DATA = 0x8000;
}
internal static class EncryptionFlagsV5
{
// RAR 5.0 archive encryption header specific flags.
public const uint CHFL_CRYPT_PSWCHECK = 0x01; // Password check data is present.
// RAR 5.0 archive encryption header specific flags.
public const uint CHFL_CRYPT_PSWCHECK = 0x01; // Password check data is present.
public const uint FHEXTRA_CRYPT_PSWCHECK = 0x01; // Password check data is present.
public const uint FHEXTRA_CRYPT_HASHMAC = 0x02;
public const uint FHEXTRA_CRYPT_PSWCHECK = 0x01; // Password check data is present.
public const uint FHEXTRA_CRYPT_HASHMAC = 0x02;
}
internal static class EncryptionConstV5
{
public const int VERSION = 0;
public const uint CRYPT5_KDF_LG2_COUNT_MAX = 0x24;
public const int SIZE_SALT30 = 0x08;
public const int SIZE_SALT50 = 0x10;
public const int SIZE_INITV = 0x10;
public const int SIZE_PSWCHECK = 0x08;
public const int SIZE_PSWCHECK_CSUM = 0x04;
public const int VERSION = 0;
public const uint CRYPT5_KDF_LG2_COUNT_MAX = 0x24;
public const int SIZE_SALT30 = 0x08;
public const int SIZE_SALT50 = 0x10;
public const int SIZE_INITV = 0x10;
public const int SIZE_PSWCHECK = 0x08;
public const int SIZE_PSWCHECK_CSUM = 0x04;
}
internal static class HeaderFlagsV5
{
public const ushort HAS_EXTRA = 0x0001;
public const ushort HAS_DATA = 0x0002;
public const ushort KEEP = 0x0004; // block must be kept during an update
public const ushort SPLIT_BEFORE = 0x0008;
public const ushort SPLIT_AFTER = 0x0010;
public const ushort CHILD = 0x0020; // ??? Block depends on preceding file block.
public const ushort PRESERVE_CHILD = 0x0040; // ???? Preserve a child block if host block is modified
public const ushort HAS_EXTRA = 0x0001;
public const ushort HAS_DATA = 0x0002;
public const ushort KEEP = 0x0004; // block must be kept during an update
public const ushort SPLIT_BEFORE = 0x0008;
public const ushort SPLIT_AFTER = 0x0010;
public const ushort CHILD = 0x0020; // ??? Block depends on preceding file block.
public const ushort PRESERVE_CHILD = 0x0040; // ???? Preserve a child block if host block is modified
}
internal static class ArchiveFlagsV4
{
public const ushort VOLUME = 0x0001;
public const ushort COMMENT = 0x0002;
public const ushort LOCK = 0x0004;
public const ushort SOLID = 0x0008;
public const ushort NEW_NUMBERING = 0x0010;
public const ushort AV = 0x0020;
public const ushort PROTECT = 0x0040;
public const ushort PASSWORD = 0x0080;
public const ushort FIRST_VOLUME = 0x0100;
public const ushort ENCRYPT_VER = 0x0200;
public const ushort VOLUME = 0x0001;
public const ushort COMMENT = 0x0002;
public const ushort LOCK = 0x0004;
public const ushort SOLID = 0x0008;
public const ushort NEW_NUMBERING = 0x0010;
public const ushort AV = 0x0020;
public const ushort PROTECT = 0x0040;
public const ushort PASSWORD = 0x0080;
public const ushort FIRST_VOLUME = 0x0100;
public const ushort ENCRYPT_VER = 0x0200;
}
internal static class ArchiveFlagsV5
{
public const ushort VOLUME = 0x0001;
public const ushort HAS_VOLUME_NUMBER = 0x0002;
public const ushort SOLID = 0x0004;
public const ushort PROTECT = 0x0008;
public const ushort LOCK = 0x0010;
public const ushort VOLUME = 0x0001;
public const ushort HAS_VOLUME_NUMBER = 0x0002;
public const ushort SOLID = 0x0004;
public const ushort PROTECT = 0x0008;
public const ushort LOCK = 0x0010;
}
internal static class HostOsV4
{
public const byte MS_DOS = 0;
public const byte OS2 = 1;
public const byte WIN32 = 2;
public const byte UNIX = 3;
public const byte MAC_OS = 4;
public const byte BE_OS = 5;
public const byte MS_DOS = 0;
public const byte OS2 = 1;
public const byte WIN32 = 2;
public const byte UNIX = 3;
public const byte MAC_OS = 4;
public const byte BE_OS = 5;
}
internal static class HostOsV5
{
public const byte WINDOWS = 0;
public const byte UNIX = 1;
public const byte WINDOWS = 0;
public const byte UNIX = 1;
}
internal static class FileFlagsV4
{
public const ushort SPLIT_BEFORE = 0x0001;
public const ushort SPLIT_AFTER = 0x0002;
public const ushort PASSWORD = 0x0004;
public const ushort COMMENT = 0x0008;
public const ushort SOLID = 0x0010;
public const ushort SPLIT_BEFORE = 0x0001;
public const ushort SPLIT_AFTER = 0x0002;
public const ushort PASSWORD = 0x0004;
public const ushort COMMENT = 0x0008;
public const ushort SOLID = 0x0010;
public const ushort WINDOW_MASK = 0x00e0;
public const ushort WINDOW64 = 0x0000;
public const ushort WINDOW128 = 0x0020;
public const ushort WINDOW256 = 0x0040;
public const ushort WINDOW512 = 0x0060;
public const ushort WINDOW1024 = 0x0080;
public const ushort WINDOW2048 = 0x00a0;
public const ushort WINDOW4096 = 0x00c0;
public const ushort DIRECTORY = 0x00e0;
public const ushort WINDOW_MASK = 0x00e0;
public const ushort WINDOW64 = 0x0000;
public const ushort WINDOW128 = 0x0020;
public const ushort WINDOW256 = 0x0040;
public const ushort WINDOW512 = 0x0060;
public const ushort WINDOW1024 = 0x0080;
public const ushort WINDOW2048 = 0x00a0;
public const ushort WINDOW4096 = 0x00c0;
public const ushort DIRECTORY = 0x00e0;
public const ushort LARGE = 0x0100;
public const ushort UNICODE = 0x0200;
public const ushort SALT = 0x0400;
public const ushort VERSION = 0x0800;
public const ushort EXT_TIME = 0x1000;
public const ushort EXT_FLAGS = 0x2000;
public const ushort LARGE = 0x0100;
public const ushort UNICODE = 0x0200;
public const ushort SALT = 0x0400;
public const ushort VERSION = 0x0800;
public const ushort EXT_TIME = 0x1000;
public const ushort EXT_FLAGS = 0x2000;
}
internal static class FileFlagsV5
{
public const ushort DIRECTORY = 0x0001;
public const ushort HAS_MOD_TIME = 0x0002;
public const ushort HAS_CRC32 = 0x0004;
public const ushort UNPACKED_SIZE_UNKNOWN = 0x0008;
public const ushort DIRECTORY = 0x0001;
public const ushort HAS_MOD_TIME = 0x0002;
public const ushort HAS_CRC32 = 0x0004;
public const ushort UNPACKED_SIZE_UNKNOWN = 0x0008;
}
internal static class EndArchiveFlagsV4
{
public const ushort NEXT_VOLUME = 0x0001;
public const ushort DATA_CRC = 0x0002;
public const ushort REV_SPACE = 0x0004;
public const ushort VOLUME_NUMBER = 0x0008;
public const ushort NEXT_VOLUME = 0x0001;
public const ushort DATA_CRC = 0x0002;
public const ushort REV_SPACE = 0x0004;
public const ushort VOLUME_NUMBER = 0x0008;
}
internal static class EndArchiveFlagsV5
{
public const ushort HAS_NEXT_VOLUME = 0x0001;
public const ushort HAS_NEXT_VOLUME = 0x0001;
}
internal static class RedirTypeV5
{
public const byte UNIX_SYMLINK = 0x0001;
public const byte WIN_SYMLINK = 0x0002;
public const byte WIN_JUNCTION = 0x0003;
public const byte HARD_LINK = 0x0004;
public const byte FILE_COPY = 0x0005;
public const byte UNIX_SYMLINK = 0x0001;
public const byte WIN_SYMLINK = 0x0002;
public const byte WIN_JUNCTION = 0x0003;
public const byte HARD_LINK = 0x0004;
public const byte FILE_COPY = 0x0005;
}
internal static class RedirFlagV5
{
public const byte DIRECTORY = 0x0001;
public const byte DIRECTORY = 0x0001;
}

View File

@@ -2,5 +2,5 @@ namespace SharpCompress.Common.Rar.Headers;
public interface IRarHeader
{
HeaderType HeaderType { get; }
HeaderType HeaderType { get; }
}

View File

@@ -5,131 +5,129 @@ namespace SharpCompress.Common.Rar.Headers;
internal class MarkHeader : IRarHeader
{
private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136
private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136
internal bool OldNumberingFormat { get; private set; }
internal bool OldNumberingFormat { get; private set; }
public bool IsRar5 { get; }
public bool IsRar5 { get; }
private MarkHeader(bool isRar5)
private MarkHeader(bool isRar5)
{
IsRar5 = isRar5;
}
public HeaderType HeaderType => HeaderType.Mark;
private static byte GetByte(Stream stream)
{
var b = stream.ReadByte();
if (b != -1)
{
IsRar5 = isRar5;
return (byte)b;
}
throw new EndOfStreamException();
}
public HeaderType HeaderType => HeaderType.Mark;
private static byte GetByte(Stream stream)
public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader)
{
var maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0;
try
{
var b = stream.ReadByte();
if (b != -1)
var start = -1;
var b = GetByte(stream);
start++;
while (start <= maxScanIndex)
{
// Rar old signature: 52 45 7E 5E
// Rar4 signature: 52 61 72 21 1A 07 00
// Rar5 signature: 52 61 72 21 1A 07 01 00
if (b == 0x52)
{
return (byte)b;
}
throw new EndOfStreamException();
}
public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader)
{
var maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0;
try
{
var start = -1;
var b = GetByte(stream);
b = GetByte(stream);
start++;
if (b == 0x61)
{
b = GetByte(stream);
start++;
while (start <= maxScanIndex)
if (b != 0x72)
{
// Rar old signature: 52 45 7E 5E
// Rar4 signature: 52 61 72 21 1A 07 00
// Rar5 signature: 52 61 72 21 1A 07 01 00
if (b == 0x52)
{
b = GetByte(stream);
start++;
if (b == 0x61)
{
b = GetByte(stream);
start++;
if (b != 0x72)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x21)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x1a)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x07)
{
continue;
}
b = GetByte(stream);
start++;
if (b == 1)
{
b = GetByte(stream);
start++;
if (b != 0)
{
continue;
}
return new MarkHeader(true); // Rar5
}
else if (b == 0)
{
return new MarkHeader(false); // Rar4
}
}
else if (b == 0x45)
{
b = GetByte(stream);
start++;
if (b != 0x7e)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x5e)
{
continue;
}
throw new InvalidFormatException(
"Rar format version pre-4 is unsupported."
);
}
}
else
{
b = GetByte(stream);
start++;
}
continue;
}
b = GetByte(stream);
start++;
if (b != 0x21)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x1a)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x07)
{
continue;
}
b = GetByte(stream);
start++;
if (b == 1)
{
b = GetByte(stream);
start++;
if (b != 0)
{
continue;
}
return new MarkHeader(true); // Rar5
}
else if (b == 0)
{
return new MarkHeader(false); // Rar4
}
}
else if (b == 0x45)
{
b = GetByte(stream);
start++;
if (b != 0x7e)
{
continue;
}
b = GetByte(stream);
start++;
if (b != 0x5e)
{
continue;
}
throw new InvalidFormatException("Rar format version pre-4 is unsupported.");
}
}
catch (Exception e)
else
{
if (!leaveStreamOpen)
{
stream.Dispose();
}
throw new InvalidFormatException("Error trying to read rar signature.", e);
b = GetByte(stream);
start++;
}
throw new InvalidFormatException("Rar signature not found");
}
}
catch (Exception e)
{
if (!leaveStreamOpen)
{
stream.Dispose();
}
throw new InvalidFormatException("Error trying to read rar signature.", e);
}
throw new InvalidFormatException("Rar signature not found");
}
}

View File

@@ -4,46 +4,46 @@ namespace SharpCompress.Common.Rar.Headers;
internal sealed class NewSubHeaderType : IEquatable<NewSubHeaderType>
{
internal static readonly NewSubHeaderType SUBHEAD_TYPE_CMT = new('C', 'M', 'T');
internal static readonly NewSubHeaderType SUBHEAD_TYPE_CMT = new('C', 'M', 'T');
//internal static final NewSubHeaderType SUBHEAD_TYPE_ACL = new (new byte[]{'A','C','L'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_ACL = new (new byte[]{'A','C','L'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_STREAM = new (new byte[]{'S','T','M'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_STREAM = new (new byte[]{'S','T','M'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_UOWNER = new (new byte[]{'U','O','W'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_UOWNER = new (new byte[]{'U','O','W'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_AV = new (new byte[]{'A','V'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_AV = new (new byte[]{'A','V'});
internal static readonly NewSubHeaderType SUBHEAD_TYPE_RR = new('R', 'R');
internal static readonly NewSubHeaderType SUBHEAD_TYPE_RR = new('R', 'R');
//internal static final NewSubHeaderType SUBHEAD_TYPE_OS2EA = new (new byte[]{'E','A','2'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_OS2EA = new (new byte[]{'E','A','2'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_BEOSEA = new (new byte[]{'E','A','B','E'});
//internal static final NewSubHeaderType SUBHEAD_TYPE_BEOSEA = new (new byte[]{'E','A','B','E'});
private readonly byte[] _bytes;
private readonly byte[] _bytes;
private NewSubHeaderType(params char[] chars)
private NewSubHeaderType(params char[] chars)
{
_bytes = new byte[chars.Length];
for (var i = 0; i < chars.Length; ++i)
{
_bytes = new byte[chars.Length];
for (var i = 0; i < chars.Length; ++i)
{
_bytes[i] = (byte)chars[i];
}
_bytes[i] = (byte)chars[i];
}
}
internal bool Equals(byte[] bytes)
{
if (_bytes.Length != bytes.Length)
{
return false;
}
internal bool Equals(byte[] bytes)
{
if (_bytes.Length != bytes.Length)
{
return false;
}
return _bytes.AsSpan().SequenceEqual(bytes);
}
return _bytes.AsSpan().SequenceEqual(bytes);
}
public bool Equals(NewSubHeaderType? other) => other is not null && Equals(other._bytes);
public bool Equals(NewSubHeaderType? other) => other is not null && Equals(other._bytes);
public override bool Equals(object? obj) => obj is NewSubHeaderType other && Equals(other);
public override bool Equals(object? obj) => obj is NewSubHeaderType other && Equals(other);
public override int GetHashCode() => _bytes.GetHashCode();
public override int GetHashCode() => _bytes.GetHashCode();
}

View File

@@ -5,26 +5,26 @@ namespace SharpCompress.Common.Rar.Headers;
// ProtectHeader is part of the Recovery Record feature
internal sealed class ProtectHeader : RarHeader
{
public ProtectHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Protect)
public ProtectHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Protect)
{
if (IsRar5)
{
if (IsRar5)
{
throw new InvalidFormatException("unexpected rar5 record");
}
throw new InvalidFormatException("unexpected rar5 record");
}
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
Version = reader.ReadByte();
RecSectors = reader.ReadUInt16();
TotalBlocks = reader.ReadUInt32();
Mark = reader.ReadBytes(8);
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
Version = reader.ReadByte();
RecSectors = reader.ReadUInt16();
TotalBlocks = reader.ReadUInt32();
Mark = reader.ReadBytes(8);
}
internal uint DataSize => checked((uint)AdditionalDataSize);
internal byte Version { get; private set; }
internal ushort RecSectors { get; private set; }
internal uint TotalBlocks { get; private set; }
internal byte[]? Mark { get; private set; }
internal uint DataSize => checked((uint)AdditionalDataSize);
internal byte Version { get; private set; }
internal ushort RecSectors { get; private set; }
internal uint TotalBlocks { get; private set; }
internal byte[]? Mark { get; private set; }
}

View File

@@ -7,123 +7,123 @@ namespace SharpCompress.Common.Rar.Headers;
// https://www.rarlab.com/technote.htm
internal class RarHeader : IRarHeader
{
private readonly HeaderType _headerType;
private readonly bool _isRar5;
private readonly HeaderType _headerType;
private readonly bool _isRar5;
internal static RarHeader? TryReadBase(
RarCrcBinaryReader reader,
bool isRar5,
ArchiveEncoding archiveEncoding
)
internal static RarHeader? TryReadBase(
RarCrcBinaryReader reader,
bool isRar5,
ArchiveEncoding archiveEncoding
)
{
try
{
try
{
return new RarHeader(reader, isRar5, archiveEncoding);
}
catch (InvalidFormatException)
{
return null;
}
return new RarHeader(reader, isRar5, archiveEncoding);
}
catch (InvalidFormatException)
{
return null;
}
}
private RarHeader(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding)
{
_headerType = HeaderType.Null;
_isRar5 = isRar5;
ArchiveEncoding = archiveEncoding;
if (IsRar5)
{
HeaderCrc = reader.ReadUInt32();
reader.ResetCrc();
HeaderSize = (int)reader.ReadRarVIntUInt32(3);
reader.Mark();
HeaderCode = reader.ReadRarVIntByte();
HeaderFlags = reader.ReadRarVIntUInt16(2);
if (HasHeaderFlag(HeaderFlagsV5.HAS_EXTRA))
{
ExtraSize = reader.ReadRarVIntUInt32();
}
if (HasHeaderFlag(HeaderFlagsV5.HAS_DATA))
{
AdditionalDataSize = (long)reader.ReadRarVInt();
}
}
else
{
reader.Mark();
HeaderCrc = reader.ReadUInt16();
reader.ResetCrc();
HeaderCode = reader.ReadByte();
HeaderFlags = reader.ReadUInt16();
HeaderSize = reader.ReadInt16();
if (HasHeaderFlag(HeaderFlagsV4.HAS_DATA))
{
AdditionalDataSize = reader.ReadUInt32();
}
}
}
protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
{
_headerType = headerType;
_isRar5 = header.IsRar5;
HeaderCrc = header.HeaderCrc;
HeaderCode = header.HeaderCode;
HeaderFlags = header.HeaderFlags;
HeaderSize = header.HeaderSize;
ExtraSize = header.ExtraSize;
AdditionalDataSize = header.AdditionalDataSize;
ArchiveEncoding = header.ArchiveEncoding;
ReadFinish(reader);
var n = RemainingHeaderBytes(reader);
if (n > 0)
{
reader.ReadBytes(n);
}
private RarHeader(RarCrcBinaryReader reader, bool isRar5, ArchiveEncoding archiveEncoding)
VerifyHeaderCrc(reader.GetCrc32());
}
protected int RemainingHeaderBytes(MarkingBinaryReader reader) =>
checked(HeaderSize - (int)reader.CurrentReadByteCount);
protected virtual void ReadFinish(MarkingBinaryReader reader) =>
throw new NotImplementedException();
private void VerifyHeaderCrc(uint crc32)
{
var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc;
if (!b)
{
_headerType = HeaderType.Null;
_isRar5 = isRar5;
ArchiveEncoding = archiveEncoding;
if (IsRar5)
{
HeaderCrc = reader.ReadUInt32();
reader.ResetCrc();
HeaderSize = (int)reader.ReadRarVIntUInt32(3);
reader.Mark();
HeaderCode = reader.ReadRarVIntByte();
HeaderFlags = reader.ReadRarVIntUInt16(2);
if (HasHeaderFlag(HeaderFlagsV5.HAS_EXTRA))
{
ExtraSize = reader.ReadRarVIntUInt32();
}
if (HasHeaderFlag(HeaderFlagsV5.HAS_DATA))
{
AdditionalDataSize = (long)reader.ReadRarVInt();
}
}
else
{
reader.Mark();
HeaderCrc = reader.ReadUInt16();
reader.ResetCrc();
HeaderCode = reader.ReadByte();
HeaderFlags = reader.ReadUInt16();
HeaderSize = reader.ReadInt16();
if (HasHeaderFlag(HeaderFlagsV4.HAS_DATA))
{
AdditionalDataSize = reader.ReadUInt32();
}
}
throw new InvalidFormatException("rar header crc mismatch");
}
}
protected RarHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
{
_headerType = headerType;
_isRar5 = header.IsRar5;
HeaderCrc = header.HeaderCrc;
HeaderCode = header.HeaderCode;
HeaderFlags = header.HeaderFlags;
HeaderSize = header.HeaderSize;
ExtraSize = header.ExtraSize;
AdditionalDataSize = header.AdditionalDataSize;
ArchiveEncoding = header.ArchiveEncoding;
ReadFinish(reader);
public HeaderType HeaderType => _headerType;
var n = RemainingHeaderBytes(reader);
if (n > 0)
{
reader.ReadBytes(n);
}
protected bool IsRar5 => _isRar5;
VerifyHeaderCrc(reader.GetCrc32());
}
protected uint HeaderCrc { get; }
protected int RemainingHeaderBytes(MarkingBinaryReader reader) =>
checked(HeaderSize - (int)reader.CurrentReadByteCount);
internal byte HeaderCode { get; }
protected virtual void ReadFinish(MarkingBinaryReader reader) =>
throw new NotImplementedException();
protected ushort HeaderFlags { get; }
private void VerifyHeaderCrc(uint crc32)
{
var b = (IsRar5 ? crc32 : (ushort)crc32) == HeaderCrc;
if (!b)
{
throw new InvalidFormatException("rar header crc mismatch");
}
}
protected bool HasHeaderFlag(ushort flag) => (HeaderFlags & flag) == flag;
public HeaderType HeaderType => _headerType;
protected int HeaderSize { get; }
protected bool IsRar5 => _isRar5;
internal ArchiveEncoding ArchiveEncoding { get; }
protected uint HeaderCrc { get; }
/// <summary>
/// Extra header size.
/// </summary>
protected uint ExtraSize { get; }
internal byte HeaderCode { get; }
protected ushort HeaderFlags { get; }
protected bool HasHeaderFlag(ushort flag) => (HeaderFlags & flag) == flag;
protected int HeaderSize { get; }
internal ArchiveEncoding ArchiveEncoding { get; }
/// <summary>
/// Extra header size.
/// </summary>
protected uint ExtraSize { get; }
/// <summary>
/// Size of additional data (eg file contents)
/// </summary>
protected long AdditionalDataSize { get; }
/// <summary>
/// Size of additional data (eg file contents)
/// </summary>
protected long AdditionalDataSize { get; }
}

View File

@@ -7,217 +7,210 @@ namespace SharpCompress.Common.Rar.Headers;
public class RarHeaderFactory
{
private bool _isRar5;
private bool _isRar5;
private Rar5CryptoInfo? _cryptInfo;
private Rar5CryptoInfo? _cryptInfo;
public RarHeaderFactory(StreamingMode mode, ReaderOptions options)
public RarHeaderFactory(StreamingMode mode, ReaderOptions options)
{
StreamingMode = mode;
Options = options;
}
public ReaderOptions Options { get; }
public StreamingMode StreamingMode { get; }
public bool IsEncrypted { get; private set; }
public IEnumerable<IRarHeader> ReadHeaders(Stream stream)
{
var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader);
_isRar5 = markHeader.IsRar5;
yield return markHeader;
RarHeader? header;
while ((header = TryReadNextHeader(stream)) != null)
{
StreamingMode = mode;
Options = options;
yield return header;
if (header.HeaderType == HeaderType.EndArchive)
{
// End of archive marker. RAR does not read anything after this header letting to use third
// party tools to add extra information such as a digital signature to archive.
yield break;
}
}
}
private RarHeader? TryReadNextHeader(Stream stream)
{
RarCrcBinaryReader reader;
if (!IsEncrypted)
{
reader = new RarCrcBinaryReader(stream);
}
else
{
if (Options.Password is null)
{
throw new CryptographicException("Encrypted Rar archive has no password specified.");
}
if (_isRar5 && _cryptInfo != null)
{
_cryptInfo.ReadInitV(new MarkingBinaryReader(stream));
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
reader = new RarCryptoBinaryReader(stream, _headerKey, _cryptInfo.Salt);
}
else
{
var key = new CryptKey3(Options.Password);
reader = new RarCryptoBinaryReader(stream, key);
}
}
public ReaderOptions Options { get; }
public StreamingMode StreamingMode { get; }
public bool IsEncrypted { get; private set; }
public IEnumerable<IRarHeader> ReadHeaders(Stream stream)
var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding);
if (header is null)
{
var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader);
_isRar5 = markHeader.IsRar5;
yield return markHeader;
RarHeader? header;
while ((header = TryReadNextHeader(stream)) != null)
return null;
}
switch (header.HeaderCode)
{
case HeaderCodeV.RAR5_ARCHIVE_HEADER:
case HeaderCodeV.RAR4_ARCHIVE_HEADER:
{
var ah = new ArchiveHeader(header, reader);
if (ah.IsEncrypted == true)
{
yield return header;
if (header.HeaderType == HeaderType.EndArchive)
{
// End of archive marker. RAR does not read anything after this header letting to use third
// party tools to add extra information such as a digital signature to archive.
yield break;
}
//!!! rar5 we don't know yet
IsEncrypted = true;
}
}
return ah;
}
private RarHeader? TryReadNextHeader(Stream stream)
{
RarCrcBinaryReader reader;
if (!IsEncrypted)
case HeaderCodeV.RAR4_PROTECT_HEADER:
{
var ph = new ProtectHeader(header, reader);
// skip the recovery record data, we do not use it.
switch (StreamingMode)
{
reader = new RarCrcBinaryReader(stream);
case StreamingMode.Seekable:
{
reader.BaseStream.Position += ph.DataSize;
}
break;
case StreamingMode.Streaming:
{
reader.BaseStream.Skip(ph.DataSize);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return ph;
}
case HeaderCodeV.RAR5_SERVICE_HEADER:
{
var fh = new FileHeader(header, reader, HeaderType.Service);
if (fh.FileName == "CMT")
{
fh.PackedStream = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
}
else
{
if (Options.Password is null)
{
throw new CryptographicException(
"Encrypted Rar archive has no password specified."
);
}
if (_isRar5 && _cryptInfo != null)
{
_cryptInfo.ReadInitV(new MarkingBinaryReader(stream));
var _headerKey = new CryptKey5(Options.Password!, _cryptInfo);
reader = new RarCryptoBinaryReader(stream, _headerKey, _cryptInfo.Salt);
}
else
{
var key = new CryptKey3(Options.Password);
reader = new RarCryptoBinaryReader(stream, key);
}
SkipData(fh, reader);
}
return fh;
}
var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding);
if (header is null)
{
return null;
}
switch (header.HeaderCode)
{
case HeaderCodeV.RAR5_ARCHIVE_HEADER:
case HeaderCodeV.RAR4_ARCHIVE_HEADER:
{
var ah = new ArchiveHeader(header, reader);
if (ah.IsEncrypted == true)
{
//!!! rar5 we don't know yet
IsEncrypted = true;
}
return ah;
}
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
{
var fh = new FileHeader(header, reader, HeaderType.NewSub);
SkipData(fh, reader);
return fh;
}
case HeaderCodeV.RAR4_PROTECT_HEADER:
{
var ph = new ProtectHeader(header, reader);
// skip the recovery record data, we do not use it.
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
reader.BaseStream.Position += ph.DataSize;
}
break;
case StreamingMode.Streaming:
{
reader.BaseStream.Skip(ph.DataSize);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return ph;
}
case HeaderCodeV.RAR5_SERVICE_HEADER:
{
var fh = new FileHeader(header, reader, HeaderType.Service);
if (fh.FileName == "CMT")
{
fh.PackedStream = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
}
else
{
SkipData(fh, reader);
}
return fh;
}
case HeaderCodeV.RAR4_NEW_SUB_HEADER:
{
var fh = new FileHeader(header, reader, HeaderType.NewSub);
SkipData(fh, reader);
return fh;
}
case HeaderCodeV.RAR5_FILE_HEADER:
case HeaderCodeV.RAR4_FILE_HEADER:
{
var fh = new FileHeader(header, reader, HeaderType.File);
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
if (fh.R4Salt is null && fh.Rar5CryptoInfo is null)
{
fh.PackedStream = ms;
}
else
{
fh.PackedStream = new RarCryptoWrapper(
ms,
fh.R4Salt is null
? fh.Rar5CryptoInfo.NotNull().Salt
: fh.R4Salt,
fh.R4Salt is null
? new CryptKey5(
Options.Password,
fh.Rar5CryptoInfo.NotNull()
)
: new CryptKey3(Options.Password)
);
}
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return fh;
}
case HeaderCodeV.RAR5_END_ARCHIVE_HEADER:
case HeaderCodeV.RAR4_END_ARCHIVE_HEADER:
{
return new EndArchiveHeader(header, reader);
}
case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER:
{
var cryptoHeader = new ArchiveCryptHeader(header, reader);
IsEncrypted = true;
_cryptInfo = cryptoHeader.CryptInfo;
return cryptoHeader;
}
default:
{
throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode);
}
}
}
private void SkipData(FileHeader fh, RarCrcBinaryReader reader)
{
case HeaderCodeV.RAR5_FILE_HEADER:
case HeaderCodeV.RAR4_FILE_HEADER:
{
var fh = new FileHeader(header, reader, HeaderType.File);
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
//skip the data because it's useless?
reader.BaseStream.Skip(fh.CompressedSize);
}
break;
default:
case StreamingMode.Seekable:
{
throw new InvalidFormatException("Invalid StreamingMode");
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
if (fh.R4Salt is null && fh.Rar5CryptoInfo is null)
{
fh.PackedStream = ms;
}
else
{
fh.PackedStream = new RarCryptoWrapper(
ms,
fh.R4Salt is null ? fh.Rar5CryptoInfo.NotNull().Salt : fh.R4Salt,
fh.R4Salt is null
? new CryptKey5(Options.Password, fh.Rar5CryptoInfo.NotNull())
: new CryptKey3(Options.Password)
);
}
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
return fh;
}
case HeaderCodeV.RAR5_END_ARCHIVE_HEADER:
case HeaderCodeV.RAR4_END_ARCHIVE_HEADER:
{
return new EndArchiveHeader(header, reader);
}
case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER:
{
var cryptoHeader = new ArchiveCryptHeader(header, reader);
IsEncrypted = true;
_cryptInfo = cryptoHeader.CryptInfo;
return cryptoHeader;
}
default:
{
throw new InvalidFormatException("Unknown Rar Header: " + header.HeaderCode);
}
}
}
private void SkipData(FileHeader fh, RarCrcBinaryReader reader)
{
switch (StreamingMode)
{
case StreamingMode.Seekable:
{
fh.DataStartPosition = reader.BaseStream.Position;
reader.BaseStream.Position += fh.CompressedSize;
}
break;
case StreamingMode.Streaming:
{
//skip the data because it's useless?
reader.BaseStream.Skip(fh.CompressedSize);
}
break;
default:
{
throw new InvalidFormatException("Invalid StreamingMode");
}
}
}
}

View File

@@ -4,25 +4,25 @@ namespace SharpCompress.Common.Rar.Headers;
internal class SignHeader : RarHeader
{
protected SignHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Sign)
protected SignHeader(RarHeader header, RarCrcBinaryReader reader)
: base(header, reader, HeaderType.Sign)
{
if (IsRar5)
{
if (IsRar5)
{
throw new InvalidFormatException("unexpected rar5 record");
}
throw new InvalidFormatException("unexpected rar5 record");
}
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
CreationTime = reader.ReadInt32();
ArcNameSize = reader.ReadInt16();
UserNameSize = reader.ReadInt16();
}
protected override void ReadFinish(MarkingBinaryReader reader)
{
CreationTime = reader.ReadInt32();
ArcNameSize = reader.ReadInt16();
UserNameSize = reader.ReadInt16();
}
internal int CreationTime { get; private set; }
internal int CreationTime { get; private set; }
internal short ArcNameSize { get; private set; }
internal short ArcNameSize { get; private set; }
internal short UserNameSize { get; private set; }
internal short UserNameSize { get; private set; }
}

View File

@@ -4,5 +4,5 @@ namespace SharpCompress.Common.Rar;
internal interface ICryptKey
{
ICryptoTransform Transformer(byte[] salt);
ICryptoTransform Transformer(byte[] salt);
}

View File

@@ -7,51 +7,51 @@ namespace SharpCompress.Common.Rar;
internal class Rar5CryptoInfo
{
public Rar5CryptoInfo() { }
public Rar5CryptoInfo() { }
public Rar5CryptoInfo(MarkingBinaryReader reader, bool readInitV)
public Rar5CryptoInfo(MarkingBinaryReader reader, bool readInitV)
{
var cryptVersion = reader.ReadRarVIntUInt32();
if (cryptVersion > EncryptionConstV5.VERSION)
{
var cryptVersion = reader.ReadRarVIntUInt32();
if (cryptVersion > EncryptionConstV5.VERSION)
{
throw new CryptographicException($"Unsupported crypto version of {cryptVersion}");
}
var encryptionFlags = reader.ReadRarVIntUInt32();
UsePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
LG2Count = reader.ReadRarVIntByte(1);
throw new CryptographicException($"Unsupported crypto version of {cryptVersion}");
}
var encryptionFlags = reader.ReadRarVIntUInt32();
UsePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
LG2Count = reader.ReadRarVIntByte(1);
if (LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
{
throw new CryptographicException($"Unsupported LG2 count of {LG2Count}.");
}
Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT50);
if (readInitV) // File header needs to read IV here
{
ReadInitV(reader);
}
if (UsePswCheck)
{
PswCheck = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK);
var _pswCheckCsm = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK_CSUM);
var sha = SHA256.Create();
UsePswCheck = sha.ComputeHash(PswCheck).AsSpan().StartsWith(_pswCheckCsm.AsSpan());
}
if (LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX)
{
throw new CryptographicException($"Unsupported LG2 count of {LG2Count}.");
}
public void ReadInitV(MarkingBinaryReader reader) =>
InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV);
Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT50);
public bool UsePswCheck = false;
if (readInitV) // File header needs to read IV here
{
ReadInitV(reader);
}
public int LG2Count = 0;
if (UsePswCheck)
{
PswCheck = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK);
var _pswCheckCsm = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK_CSUM);
public byte[] InitV = [];
var sha = SHA256.Create();
UsePswCheck = sha.ComputeHash(PswCheck).AsSpan().StartsWith(_pswCheckCsm.AsSpan());
}
}
public byte[] Salt = [];
public void ReadInitV(MarkingBinaryReader reader) =>
InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV);
public byte[] PswCheck = [];
public bool UsePswCheck = false;
public int LG2Count = 0;
public byte[] InitV = [];
public byte[] Salt = [];
public byte[] PswCheck = [];
}

View File

@@ -6,30 +6,30 @@ namespace SharpCompress.Common.Rar;
internal class RarCrcBinaryReader : MarkingBinaryReader
{
private uint _currentCrc;
private uint _currentCrc;
public RarCrcBinaryReader(Stream stream)
: base(stream) { }
public RarCrcBinaryReader(Stream stream)
: base(stream) { }
public uint GetCrc32() => ~_currentCrc;
public uint GetCrc32() => ~_currentCrc;
public void ResetCrc() => _currentCrc = 0xffffffff;
public void ResetCrc() => _currentCrc = 0xffffffff;
protected void UpdateCrc(byte b) => _currentCrc = RarCRC.CheckCrc(_currentCrc, b);
protected void UpdateCrc(byte b) => _currentCrc = RarCRC.CheckCrc(_currentCrc, b);
protected byte[] ReadBytesNoCrc(int count) => base.ReadBytes(count);
protected byte[] ReadBytesNoCrc(int count) => base.ReadBytes(count);
public override byte ReadByte()
{
var b = base.ReadByte();
_currentCrc = RarCRC.CheckCrc(_currentCrc, b);
return b;
}
public override byte ReadByte()
{
var b = base.ReadByte();
_currentCrc = RarCRC.CheckCrc(_currentCrc, b);
return b;
}
public override byte[] ReadBytes(int count)
{
var result = base.ReadBytes(count);
_currentCrc = RarCRC.CheckCrc(_currentCrc, result, 0, result.Length);
return result;
}
public override byte[] ReadBytes(int count)
{
var result = base.ReadBytes(count);
_currentCrc = RarCRC.CheckCrc(_currentCrc, result, 0, result.Length);
return result;
}
}

View File

@@ -9,79 +9,79 @@ namespace SharpCompress.Common.Rar;
internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader
{
private BlockTransformer _rijndael;
private readonly Queue<byte> _data = new();
private long _readCount;
private BlockTransformer _rijndael;
private readonly Queue<byte> _data = new();
private long _readCount;
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey)
: base(stream)
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey)
: base(stream)
{
var salt = base.ReadBytes(EncryptionConstV5.SIZE_SALT30);
_readCount += EncryptionConstV5.SIZE_SALT30;
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
}
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey, byte[] salt)
: base(stream)
{
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
}
// track read count ourselves rather than using the underlying stream since we buffer
public override long CurrentReadByteCount
{
get => _readCount;
protected set
{
var salt = base.ReadBytes(EncryptionConstV5.SIZE_SALT30);
_readCount += EncryptionConstV5.SIZE_SALT30;
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
// ignore
}
}
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey, byte[] salt)
: base(stream)
{
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
}
public override void Mark() => _readCount = 0;
// track read count ourselves rather than using the underlying stream since we buffer
public override long CurrentReadByteCount
public override byte ReadByte() => ReadAndDecryptBytes(1)[0];
public override byte[] ReadBytes(int count) => ReadAndDecryptBytes(count);
private byte[] ReadAndDecryptBytes(int count)
{
var queueSize = _data.Count;
var sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
get => _readCount;
protected set
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (var i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
var cipherText = ReadBytesNoCrc(16);
var readBytes = _rijndael.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
{
// ignore
_data.Enqueue(readByte);
}
}
}
public override void Mark() => _readCount = 0;
var decryptedBytes = new byte[count];
public override byte ReadByte() => ReadAndDecryptBytes(1)[0];
public override byte[] ReadBytes(int count) => ReadAndDecryptBytes(count);
private byte[] ReadAndDecryptBytes(int count)
for (var i = 0; i < count; i++)
{
var queueSize = _data.Count;
var sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (var i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
var cipherText = ReadBytesNoCrc(16);
var readBytes = _rijndael.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
{
_data.Enqueue(readByte);
}
}
}
var decryptedBytes = new byte[count];
for (var i = 0; i < count; i++)
{
var b = _data.Dequeue();
decryptedBytes[i] = b;
UpdateCrc(b);
}
_readCount += count;
return decryptedBytes;
var b = _data.Dequeue();
decryptedBytes[i] = b;
UpdateCrc(b);
}
public void ClearQueue() => _data.Clear();
_readCount += count;
return decryptedBytes;
}
public void SkipQueue()
{
var position = BaseStream.Position;
BaseStream.Position = position + _data.Count;
ClearQueue();
}
public void ClearQueue() => _data.Clear();
public void SkipQueue()
{
var position = BaseStream.Position;
BaseStream.Position = position + _data.Count;
ClearQueue();
}
}

View File

@@ -7,74 +7,74 @@ namespace SharpCompress.Common.Rar;
internal sealed class RarCryptoWrapper : Stream
{
private readonly Stream _actualStream;
private BlockTransformer _rijndael;
private readonly Queue<byte> _data = new();
private readonly Stream _actualStream;
private BlockTransformer _rijndael;
private readonly Queue<byte> _data = new();
public RarCryptoWrapper(Stream actualStream, byte[] salt, ICryptKey key)
public RarCryptoWrapper(Stream actualStream, byte[] salt, ICryptKey key)
{
_actualStream = actualStream;
_rijndael = new BlockTransformer(key.Transformer(salt));
}
public override void Flush() { }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override int Read(byte[] buffer, int offset, int count) =>
ReadAndDecrypt(buffer, offset, count);
public int ReadAndDecrypt(byte[] buffer, int offset, int count)
{
var queueSize = _data.Count;
var sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
_actualStream = actualStream;
_rijndael = new BlockTransformer(key.Transformer(salt));
}
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
Span<byte> cipherText = stackalloc byte[16];
for (var i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
_actualStream.Read(cipherText);
public override void Flush() { }
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override int Read(byte[] buffer, int offset, int count) =>
ReadAndDecrypt(buffer, offset, count);
public int ReadAndDecrypt(byte[] buffer, int offset, int count)
{
var queueSize = _data.Count;
var sizeToRead = count - queueSize;
if (sizeToRead > 0)
var readBytes = _rijndael.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
{
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
Span<byte> cipherText = stackalloc byte[16];
for (var i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
_actualStream.Read(cipherText);
var readBytes = _rijndael.ProcessBlock(cipherText);
foreach (var readByte in readBytes)
{
_data.Enqueue(readByte);
}
}
for (var i = 0; i < count; i++)
{
buffer[offset + i] = _data.Dequeue();
}
_data.Enqueue(readByte);
}
return count;
}
for (var i = 0; i < count; i++)
{
buffer[offset + i] = _data.Dequeue();
}
}
return count;
}
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
public override bool CanRead => true;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override bool CanWrite => false;
public override long Length => throw new NotSupportedException();
public override long Length => throw new NotSupportedException();
public override long Position { get; set; }
public override long Position { get; set; }
protected override void Dispose(bool disposing)
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (disposing)
{
_rijndael.Dispose();
}
base.Dispose(disposing);
_rijndael.Dispose();
}
base.Dispose(disposing);
}
}

View File

@@ -5,77 +5,77 @@ namespace SharpCompress.Common.Rar;
public abstract class RarEntry : Entry
{
internal abstract FileHeader FileHeader { get; }
internal abstract FileHeader FileHeader { get; }
/// <summary>
/// As the V2017 port isn't complete, add this check to use the legacy Rar code.
/// </summary>
internal bool IsRarV3 =>
FileHeader.CompressionAlgorithm == 15
|| FileHeader.CompressionAlgorithm == 20
|| FileHeader.CompressionAlgorithm == 26
|| FileHeader.CompressionAlgorithm == 29
|| FileHeader.CompressionAlgorithm == 36; //Nanook - Added 20+26 as Test arc from WinRar2.8 (algo 20) was failing with 2017 code
/// <summary>
/// As the V2017 port isn't complete, add this check to use the legacy Rar code.
/// </summary>
internal bool IsRarV3 =>
FileHeader.CompressionAlgorithm == 15
|| FileHeader.CompressionAlgorithm == 20
|| FileHeader.CompressionAlgorithm == 26
|| FileHeader.CompressionAlgorithm == 29
|| FileHeader.CompressionAlgorithm == 36; //Nanook - Added 20+26 as Test arc from WinRar2.8 (algo 20) was failing with 2017 code
/// <summary>
/// The File's 32 bit CRC Hash
/// </summary>
public override long Crc => BitConverter.ToUInt32(FileHeader.FileCrc.NotNull(), 0);
/// <summary>
/// The File's 32 bit CRC Hash
/// </summary>
public override long Crc => BitConverter.ToUInt32(FileHeader.FileCrc.NotNull(), 0);
/// <summary>
/// The path of the file internal to the Rar Archive.
/// </summary>
public override string? Key => FileHeader.FileName;
/// <summary>
/// The path of the file internal to the Rar Archive.
/// </summary>
public override string? Key => FileHeader.FileName;
public override string? LinkTarget => null;
public override string? LinkTarget => null;
/// <summary>
/// The entry last modified time in the archive, if recorded
/// </summary>
public override DateTime? LastModifiedTime => FileHeader.FileLastModifiedTime;
/// <summary>
/// The entry last modified time in the archive, if recorded
/// </summary>
public override DateTime? LastModifiedTime => FileHeader.FileLastModifiedTime;
/// <summary>
/// The entry create time in the archive, if recorded
/// </summary>
public override DateTime? CreatedTime => FileHeader.FileCreatedTime;
/// <summary>
/// The entry create time in the archive, if recorded
/// </summary>
public override DateTime? CreatedTime => FileHeader.FileCreatedTime;
/// <summary>
/// The entry last accessed time in the archive, if recorded
/// </summary>
public override DateTime? LastAccessedTime => FileHeader.FileLastAccessedTime;
/// <summary>
/// The entry last accessed time in the archive, if recorded
/// </summary>
public override DateTime? LastAccessedTime => FileHeader.FileLastAccessedTime;
/// <summary>
/// The entry time whend archived, if recorded
/// </summary>
public override DateTime? ArchivedTime => FileHeader.FileArchivedTime;
/// <summary>
/// The entry time whend archived, if recorded
/// </summary>
public override DateTime? ArchivedTime => FileHeader.FileArchivedTime;
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public override bool IsEncrypted => FileHeader.IsEncrypted;
/// <summary>
/// Entry is password protected and encrypted and cannot be extracted.
/// </summary>
public override bool IsEncrypted => FileHeader.IsEncrypted;
/// <summary>
/// Entry Windows file attributes
/// </summary>
public override int? Attrib => (int)FileHeader.FileAttributes;
/// <summary>
/// Entry Windows file attributes
/// </summary>
public override int? Attrib => (int)FileHeader.FileAttributes;
/// <summary>
/// Entry is a directory
/// </summary>
public override bool IsDirectory => FileHeader.IsDirectory;
/// <summary>
/// Entry is a directory
/// </summary>
public override bool IsDirectory => FileHeader.IsDirectory;
public override bool IsSplitAfter => FileHeader.IsSplitAfter;
public override bool IsSplitAfter => FileHeader.IsSplitAfter;
public bool IsRedir => FileHeader.IsRedir;
public bool IsRedir => FileHeader.IsRedir;
public string? RedirTargetName => FileHeader.RedirTargetName;
public string? RedirTargetName => FileHeader.RedirTargetName;
public override string ToString() =>
string.Format(
"Entry Path: {0} Compressed Size: {1} Uncompressed Size: {2} CRC: {3}",
Key,
CompressedSize,
Size,
Crc
);
public override string ToString() =>
string.Format(
"Entry Path: {0} Compressed Size: {1} Uncompressed Size: {2} CRC: {3}",
Key,
CompressedSize,
Size,
Crc
);
}

View File

@@ -8,17 +8,17 @@ namespace SharpCompress.Common.Rar;
/// </summary>
internal abstract class RarFilePart : FilePart
{
internal RarFilePart(MarkHeader mh, FileHeader fh, int index)
: base(fh.ArchiveEncoding)
{
MarkHeader = mh;
FileHeader = fh;
Index = index;
}
internal RarFilePart(MarkHeader mh, FileHeader fh, int index)
: base(fh.ArchiveEncoding)
{
MarkHeader = mh;
FileHeader = fh;
Index = index;
}
internal MarkHeader MarkHeader { get; }
internal MarkHeader MarkHeader { get; }
internal FileHeader FileHeader { get; }
internal FileHeader FileHeader { get; }
internal override Stream? GetRawStream() => null;
internal override Stream? GetRawStream() => null;
}

View File

@@ -14,168 +14,168 @@ namespace SharpCompress.Common.Rar;
/// </summary>
public abstract class RarVolume : Volume
{
private readonly RarHeaderFactory _headerFactory;
private int _maxCompressionAlgorithm;
private readonly RarHeaderFactory _headerFactory;
private int _maxCompressionAlgorithm;
internal RarVolume(StreamingMode mode, Stream stream, ReaderOptions options, int index)
: base(stream, options, index)
internal RarVolume(StreamingMode mode, Stream stream, ReaderOptions options, int index)
: base(stream, options, index)
{
_headerFactory = new RarHeaderFactory(mode, options);
}
private ArchiveHeader? ArchiveHeader { get; set; }
private StreamingMode Mode => _headerFactory.StreamingMode;
internal abstract IEnumerable<RarFilePart> ReadFileParts();
internal abstract RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader);
internal IEnumerable<RarFilePart> GetVolumeFileParts()
{
MarkHeader? lastMarkHeader = null;
foreach (var header in _headerFactory.ReadHeaders(Stream))
{
_headerFactory = new RarHeaderFactory(mode, options);
switch (header.HeaderType)
{
case HeaderType.Mark:
{
lastMarkHeader = (MarkHeader)header;
}
break;
case HeaderType.Archive:
{
ArchiveHeader = (ArchiveHeader)header;
}
break;
case HeaderType.File:
{
var fh = (FileHeader)header;
if (_maxCompressionAlgorithm < fh.CompressionAlgorithm)
{
_maxCompressionAlgorithm = fh.CompressionAlgorithm;
}
yield return CreateFilePart(lastMarkHeader!, fh);
}
break;
case HeaderType.Service:
{
var fh = (FileHeader)header;
if (fh.FileName == "CMT")
{
var buffer = new byte[fh.CompressedSize];
fh.PackedStream.NotNull().ReadFully(buffer);
Comment = Encoding.UTF8.GetString(buffer, 0, buffer.Length - 1);
}
}
break;
}
}
}
private ArchiveHeader? ArchiveHeader { get; set; }
private StreamingMode Mode => _headerFactory.StreamingMode;
internal abstract IEnumerable<RarFilePart> ReadFileParts();
internal abstract RarFilePart CreateFilePart(MarkHeader markHeader, FileHeader fileHeader);
internal IEnumerable<RarFilePart> GetVolumeFileParts()
private void EnsureArchiveHeaderLoaded()
{
if (ArchiveHeader is null)
{
MarkHeader? lastMarkHeader = null;
foreach (var header in _headerFactory.ReadHeaders(Stream))
{
switch (header.HeaderType)
{
case HeaderType.Mark:
{
lastMarkHeader = (MarkHeader)header;
}
break;
case HeaderType.Archive:
{
ArchiveHeader = (ArchiveHeader)header;
}
break;
case HeaderType.File:
{
var fh = (FileHeader)header;
if (_maxCompressionAlgorithm < fh.CompressionAlgorithm)
{
_maxCompressionAlgorithm = fh.CompressionAlgorithm;
}
if (Mode == StreamingMode.Streaming)
{
throw new InvalidOperationException(
"ArchiveHeader should never been null in a streaming read."
);
}
yield return CreateFilePart(lastMarkHeader!, fh);
}
break;
case HeaderType.Service:
{
var fh = (FileHeader)header;
if (fh.FileName == "CMT")
{
var buffer = new byte[fh.CompressedSize];
fh.PackedStream.NotNull().ReadFully(buffer);
Comment = Encoding.UTF8.GetString(buffer, 0, buffer.Length - 1);
}
}
break;
}
}
// we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream
GetVolumeFileParts().First();
Stream.Position = 0;
}
}
private void EnsureArchiveHeaderLoaded()
/// <summary>
/// RarArchive is the first volume of a multi-part archive.
/// Only Rar 3.0 format and higher
/// </summary>
public override bool IsFirstVolume
{
get
{
if (ArchiveHeader is null)
{
if (Mode == StreamingMode.Streaming)
{
throw new InvalidOperationException(
"ArchiveHeader should never been null in a streaming read."
);
}
// we only want to load the archive header to avoid overhead but have to do the nasty thing and reset the stream
GetVolumeFileParts().First();
Stream.Position = 0;
}
EnsureArchiveHeaderLoaded();
return ArchiveHeader?.IsFirstVolume ?? false;
}
}
/// <summary>
/// RarArchive is the first volume of a multi-part archive.
/// Only Rar 3.0 format and higher
/// </summary>
public override bool IsFirstVolume
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public override bool IsMultiVolume
{
get
{
get
{
EnsureArchiveHeaderLoaded();
return ArchiveHeader?.IsFirstVolume ?? false;
}
EnsureArchiveHeaderLoaded();
return ArchiveHeader?.IsVolume ?? false;
}
}
/// <summary>
/// RarArchive is part of a multi-part archive.
/// </summary>
public override bool IsMultiVolume
/// <summary>
/// RarArchive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// Currently, SharpCompress cannot decompress SOLID archives.
/// </summary>
public bool IsSolidArchive
{
get
{
get
{
EnsureArchiveHeaderLoaded();
return ArchiveHeader?.IsVolume ?? false;
}
EnsureArchiveHeaderLoaded();
return ArchiveHeader?.IsSolid ?? false;
}
}
/// <summary>
/// RarArchive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
/// Currently, SharpCompress cannot decompress SOLID archives.
/// </summary>
public bool IsSolidArchive
public int MinVersion
{
get
{
get
{
EnsureArchiveHeaderLoaded();
return ArchiveHeader?.IsSolid ?? false;
}
EnsureArchiveHeaderLoaded();
if (_maxCompressionAlgorithm >= 50)
{
return 5; //5-6
}
else if (_maxCompressionAlgorithm >= 29)
{
return 3; //3-4
}
else if (_maxCompressionAlgorithm >= 20)
{
return 2; //2
}
else
{
return 1;
}
}
}
public int MinVersion
public int MaxVersion
{
get
{
get
{
EnsureArchiveHeaderLoaded();
if (_maxCompressionAlgorithm >= 50)
{
return 5; //5-6
}
else if (_maxCompressionAlgorithm >= 29)
{
return 3; //3-4
}
else if (_maxCompressionAlgorithm >= 20)
{
return 2; //2
}
else
{
return 1;
}
}
EnsureArchiveHeaderLoaded();
if (_maxCompressionAlgorithm >= 50)
{
return 6; //5-6
}
else if (_maxCompressionAlgorithm >= 29)
{
return 4; //3-4
}
else if (_maxCompressionAlgorithm >= 20)
{
return 2; //2
}
else
{
return 1;
}
}
}
public int MaxVersion
{
get
{
EnsureArchiveHeaderLoaded();
if (_maxCompressionAlgorithm >= 50)
{
return 6; //5-6
}
else if (_maxCompressionAlgorithm >= 29)
{
return 4; //3-4
}
else if (_maxCompressionAlgorithm >= 20)
{
return 2; //2
}
else
{
return 1;
}
}
}
public string? Comment { get; internal set; }
public string? Comment { get; internal set; }
}

View File

@@ -5,13 +5,13 @@ namespace SharpCompress.Common;
public sealed class ReaderExtractionEventArgs<T> : EventArgs
{
internal ReaderExtractionEventArgs(T entry, ReaderProgress? readerProgress = null)
{
Item = entry;
ReaderProgress = readerProgress;
}
internal ReaderExtractionEventArgs(T entry, ReaderProgress? readerProgress = null)
{
Item = entry;
ReaderProgress = readerProgress;
}
public T Item { get; }
public T Item { get; }
public ReaderProgress? ReaderProgress { get; }
public ReaderProgress? ReaderProgress { get; }
}

View File

@@ -10,161 +10,161 @@ namespace SharpCompress.Common.SevenZip;
internal class ArchiveDatabase
{
internal byte _majorVersion;
internal byte _minorVersion;
internal long _startPositionAfterHeader;
internal long _dataStartPosition;
internal byte _majorVersion;
internal byte _minorVersion;
internal long _startPositionAfterHeader;
internal long _dataStartPosition;
internal List<long> _packSizes = [];
internal List<uint?> _packCrCs = [];
internal List<CFolder> _folders = [];
internal List<int> _numUnpackStreamsVector;
internal List<CFileItem> _files = [];
internal List<long> _packSizes = [];
internal List<uint?> _packCrCs = [];
internal List<CFolder> _folders = [];
internal List<int> _numUnpackStreamsVector;
internal List<CFileItem> _files = [];
internal List<long> _packStreamStartPositions = [];
internal List<int> _folderStartFileIndex = [];
internal List<int> _fileIndexToFolderIndexMap = [];
internal List<long> _packStreamStartPositions = [];
internal List<int> _folderStartFileIndex = [];
internal List<int> _fileIndexToFolderIndexMap = [];
internal IPasswordProvider PasswordProvider { get; }
internal IPasswordProvider PasswordProvider { get; }
public ArchiveDatabase(IPasswordProvider passwordProvider)
public ArchiveDatabase(IPasswordProvider passwordProvider)
{
PasswordProvider = passwordProvider;
}
internal void Clear()
{
_packSizes.Clear();
_packCrCs.Clear();
_folders.Clear();
_numUnpackStreamsVector = null;
_files.Clear();
_packStreamStartPositions.Clear();
_folderStartFileIndex.Clear();
_fileIndexToFolderIndexMap.Clear();
}
internal bool IsEmpty() =>
_packSizes.Count == 0
&& _packCrCs.Count == 0
&& _folders.Count == 0
&& _numUnpackStreamsVector.Count == 0
&& _files.Count == 0;
private void FillStartPos()
{
_packStreamStartPositions.Clear();
long startPos = 0;
for (var i = 0; i < _packSizes.Count; i++)
{
PasswordProvider = passwordProvider;
_packStreamStartPositions.Add(startPos);
startPos += _packSizes[i];
}
}
internal void Clear()
private void FillFolderStartFileIndex()
{
_folderStartFileIndex.Clear();
_fileIndexToFolderIndexMap.Clear();
var folderIndex = 0;
var indexInFolder = 0;
for (var i = 0; i < _files.Count; i++)
{
_packSizes.Clear();
_packCrCs.Clear();
_folders.Clear();
_numUnpackStreamsVector = null;
_files.Clear();
var file = _files[i];
_packStreamStartPositions.Clear();
_folderStartFileIndex.Clear();
_fileIndexToFolderIndexMap.Clear();
}
var emptyStream = !file.HasStream;
internal bool IsEmpty() =>
_packSizes.Count == 0
&& _packCrCs.Count == 0
&& _folders.Count == 0
&& _numUnpackStreamsVector.Count == 0
&& _files.Count == 0;
if (emptyStream && indexInFolder == 0)
{
_fileIndexToFolderIndexMap.Add(-1);
continue;
}
private void FillStartPos()
{
_packStreamStartPositions.Clear();
long startPos = 0;
for (var i = 0; i < _packSizes.Count; i++)
if (indexInFolder == 0)
{
// v3.13 incorrectly worked with empty folders
// v4.07: Loop for skipping empty folders
for (; ; )
{
_packStreamStartPositions.Add(startPos);
startPos += _packSizes[i];
if (folderIndex >= _folders.Count)
{
throw new InvalidOperationException();
}
_folderStartFileIndex.Add(i); // check it
if (_numUnpackStreamsVector![folderIndex] != 0)
{
break;
}
folderIndex++;
}
}
}
private void FillFolderStartFileIndex()
_fileIndexToFolderIndexMap.Add(folderIndex);
if (emptyStream)
{
continue;
}
indexInFolder++;
if (indexInFolder >= _numUnpackStreamsVector![folderIndex])
{
folderIndex++;
indexInFolder = 0;
}
}
}
public void Fill()
{
FillStartPos();
FillFolderStartFileIndex();
}
internal long GetFolderStreamPos(CFolder folder, int indexInFolder)
{
var index = folder._firstPackStreamId + indexInFolder;
return _dataStartPosition + _packStreamStartPositions[index];
}
internal long GetFolderFullPackSize(int folderIndex)
{
var packStreamIndex = _folders[folderIndex]._firstPackStreamId;
var folder = _folders[folderIndex];
long size = 0;
for (var i = 0; i < folder._packStreams.Count; i++)
{
_folderStartFileIndex.Clear();
_fileIndexToFolderIndexMap.Clear();
var folderIndex = 0;
var indexInFolder = 0;
for (var i = 0; i < _files.Count; i++)
{
var file = _files[i];
var emptyStream = !file.HasStream;
if (emptyStream && indexInFolder == 0)
{
_fileIndexToFolderIndexMap.Add(-1);
continue;
}
if (indexInFolder == 0)
{
// v3.13 incorrectly worked with empty folders
// v4.07: Loop for skipping empty folders
for (; ; )
{
if (folderIndex >= _folders.Count)
{
throw new InvalidOperationException();
}
_folderStartFileIndex.Add(i); // check it
if (_numUnpackStreamsVector![folderIndex] != 0)
{
break;
}
folderIndex++;
}
}
_fileIndexToFolderIndexMap.Add(folderIndex);
if (emptyStream)
{
continue;
}
indexInFolder++;
if (indexInFolder >= _numUnpackStreamsVector![folderIndex])
{
folderIndex++;
indexInFolder = 0;
}
}
size += _packSizes[packStreamIndex + i];
}
public void Fill()
return size;
}
internal Stream GetFolderStream(Stream stream, CFolder folder, IPasswordProvider pw)
{
var packStreamIndex = folder._firstPackStreamId;
var folderStartPackPos = GetFolderStreamPos(folder, 0);
var count = folder._packStreams.Count;
var packSizes = new long[count];
for (var j = 0; j < count; j++)
{
FillStartPos();
FillFolderStartFileIndex();
packSizes[j] = _packSizes[packStreamIndex + j];
}
internal long GetFolderStreamPos(CFolder folder, int indexInFolder)
{
var index = folder._firstPackStreamId + indexInFolder;
return _dataStartPosition + _packStreamStartPositions[index];
}
internal long GetFolderFullPackSize(int folderIndex)
{
var packStreamIndex = _folders[folderIndex]._firstPackStreamId;
var folder = _folders[folderIndex];
long size = 0;
for (var i = 0; i < folder._packStreams.Count; i++)
{
size += _packSizes[packStreamIndex + i];
}
return size;
}
internal Stream GetFolderStream(Stream stream, CFolder folder, IPasswordProvider pw)
{
var packStreamIndex = folder._firstPackStreamId;
var folderStartPackPos = GetFolderStreamPos(folder, 0);
var count = folder._packStreams.Count;
var packSizes = new long[count];
for (var j = 0; j < count; j++)
{
packSizes[j] = _packSizes[packStreamIndex + j];
}
return DecoderStreamHelper.CreateDecoderStream(
stream,
folderStartPackPos,
packSizes,
folder,
pw
);
}
return DecoderStreamHelper.CreateDecoderStream(
stream,
folderStartPackPos,
packSizes,
folder,
pw
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,6 @@ namespace SharpCompress.Common.SevenZip;
internal class CBindPair
{
internal int _inIndex;
internal int _outIndex;
internal int _inIndex;
internal int _outIndex;
}

View File

@@ -4,8 +4,8 @@ namespace SharpCompress.Common.SevenZip;
internal class CCoderInfo
{
internal CMethodId _methodId;
internal byte[] _props;
internal int _numInStreams;
internal int _numOutStreams;
internal CMethodId _methodId;
internal byte[] _props;
internal int _numInStreams;
internal int _numOutStreams;
}

View File

@@ -6,24 +6,24 @@ namespace SharpCompress.Common.SevenZip;
internal class CFileItem
{
public long Size { get; internal set; }
public uint? Attrib { get; internal set; }
public uint? ExtendedAttrib { get; internal set; }
public uint? Crc { get; internal set; }
public string Name { get; internal set; }
public long Size { get; internal set; }
public uint? Attrib { get; internal set; }
public uint? ExtendedAttrib { get; internal set; }
public uint? Crc { get; internal set; }
public string Name { get; internal set; }
public bool HasStream { get; internal set; }
public bool IsDir { get; internal set; }
public bool HasStream { get; internal set; }
public bool IsDir { get; internal set; }
public DateTime? CTime { get; internal set; }
public DateTime? ATime { get; internal set; }
public DateTime? MTime { get; internal set; }
public DateTime? CTime { get; internal set; }
public DateTime? ATime { get; internal set; }
public DateTime? MTime { get; internal set; }
public long? StartPos { get; internal set; }
public bool IsAnti { get; internal set; }
public long? StartPos { get; internal set; }
public bool IsAnti { get; internal set; }
internal CFileItem()
{
HasStream = true;
}
internal CFileItem()
{
HasStream = true;
}
}

View File

@@ -6,182 +6,182 @@ namespace SharpCompress.Common.SevenZip;
internal class CFolder
{
internal List<CCoderInfo> _coders = [];
internal List<CBindPair> _bindPairs = [];
internal List<int> _packStreams = [];
internal int _firstPackStreamId;
internal List<long> _unpackSizes = [];
internal uint? _unpackCrc;
internal List<CCoderInfo> _coders = [];
internal List<CBindPair> _bindPairs = [];
internal List<int> _packStreams = [];
internal int _firstPackStreamId;
internal List<long> _unpackSizes = [];
internal uint? _unpackCrc;
internal bool UnpackCrcDefined => _unpackCrc != null;
internal bool UnpackCrcDefined => _unpackCrc != null;
public long GetUnpackSize()
public long GetUnpackSize()
{
if (_unpackSizes.Count == 0)
{
if (_unpackSizes.Count == 0)
{
return 0;
}
for (var i = _unpackSizes.Count - 1; i >= 0; i--)
{
if (FindBindPairForOutStream(i) < 0)
{
return _unpackSizes[i];
}
}
throw new InvalidOperationException();
return 0;
}
public int GetNumOutStreams()
for (var i = _unpackSizes.Count - 1; i >= 0; i--)
{
var count = 0;
for (var i = 0; i < _coders.Count; i++)
{
count += _coders[i]._numOutStreams;
}
return count;
if (FindBindPairForOutStream(i) < 0)
{
return _unpackSizes[i];
}
}
public int FindBindPairForInStream(int inStreamIndex)
{
for (var i = 0; i < _bindPairs.Count; i++)
{
if (_bindPairs[i]._inIndex == inStreamIndex)
{
return i;
}
}
throw new InvalidOperationException();
}
return -1;
public int GetNumOutStreams()
{
var count = 0;
for (var i = 0; i < _coders.Count; i++)
{
count += _coders[i]._numOutStreams;
}
public int FindBindPairForOutStream(int outStreamIndex)
{
for (var i = 0; i < _bindPairs.Count; i++)
{
if (_bindPairs[i]._outIndex == outStreamIndex)
{
return i;
}
}
return count;
}
return -1;
public int FindBindPairForInStream(int inStreamIndex)
{
for (var i = 0; i < _bindPairs.Count; i++)
{
if (_bindPairs[i]._inIndex == inStreamIndex)
{
return i;
}
}
public int FindPackStreamArrayIndex(int inStreamIndex)
{
for (var i = 0; i < _packStreams.Count; i++)
{
if (_packStreams[i] == inStreamIndex)
{
return i;
}
}
return -1;
}
return -1;
public int FindBindPairForOutStream(int outStreamIndex)
{
for (var i = 0; i < _bindPairs.Count; i++)
{
if (_bindPairs[i]._outIndex == outStreamIndex)
{
return i;
}
}
public bool IsEncrypted()
{
for (var i = _coders.Count - 1; i >= 0; i--)
{
if (_coders[i]._methodId == CMethodId.K_AES)
{
return true;
}
}
return -1;
}
return false;
public int FindPackStreamArrayIndex(int inStreamIndex)
{
for (var i = 0; i < _packStreams.Count; i++)
{
if (_packStreams[i] == inStreamIndex)
{
return i;
}
}
public bool CheckStructure()
return -1;
}
public bool IsEncrypted()
{
for (var i = _coders.Count - 1; i >= 0; i--)
{
const int kNumCodersMax = 32; // don't change it
const int kMaskSize = 32; // it must be >= kNumCodersMax
const int kNumBindsMax = 32;
if (_coders.Count > kNumCodersMax || _bindPairs.Count > kNumBindsMax)
{
return false;
}
{
var v = new BitVector(_bindPairs.Count + _packStreams.Count);
for (var i = 0; i < _bindPairs.Count; i++)
{
if (v.GetAndSet(_bindPairs[i]._inIndex))
{
return false;
}
}
for (var i = 0; i < _packStreams.Count; i++)
{
if (v.GetAndSet(_packStreams[i]))
{
return false;
}
}
}
{
var v = new BitVector(_unpackSizes.Count);
for (var i = 0; i < _bindPairs.Count; i++)
{
if (v.GetAndSet(_bindPairs[i]._outIndex))
{
return false;
}
}
}
var mask = new uint[kMaskSize];
{
var inStreamToCoder = new List<int>();
var outStreamToCoder = new List<int>();
for (var i = 0; i < _coders.Count; i++)
{
var coder = _coders[i];
for (var j = 0; j < coder._numInStreams; j++)
{
inStreamToCoder.Add(i);
}
for (var j = 0; j < coder._numOutStreams; j++)
{
outStreamToCoder.Add(i);
}
}
for (var i = 0; i < _bindPairs.Count; i++)
{
var bp = _bindPairs[i];
mask[inStreamToCoder[bp._inIndex]] |= (1u << outStreamToCoder[bp._outIndex]);
}
}
for (var i = 0; i < kMaskSize; i++)
{
for (var j = 0; j < kMaskSize; j++)
{
if (((1u << j) & mask[i]) != 0)
{
mask[i] |= mask[j];
}
}
}
for (var i = 0; i < kMaskSize; i++)
{
if (((1u << i) & mask[i]) != 0)
{
return false;
}
}
if (_coders[i]._methodId == CMethodId.K_AES)
{
return true;
}
}
return false;
}
public bool CheckStructure()
{
const int kNumCodersMax = 32; // don't change it
const int kMaskSize = 32; // it must be >= kNumCodersMax
const int kNumBindsMax = 32;
if (_coders.Count > kNumCodersMax || _bindPairs.Count > kNumBindsMax)
{
return false;
}
{
var v = new BitVector(_bindPairs.Count + _packStreams.Count);
for (var i = 0; i < _bindPairs.Count; i++)
{
if (v.GetAndSet(_bindPairs[i]._inIndex))
{
return false;
}
}
for (var i = 0; i < _packStreams.Count; i++)
{
if (v.GetAndSet(_packStreams[i]))
{
return false;
}
}
}
{
var v = new BitVector(_unpackSizes.Count);
for (var i = 0; i < _bindPairs.Count; i++)
{
if (v.GetAndSet(_bindPairs[i]._outIndex))
{
return false;
}
}
}
var mask = new uint[kMaskSize];
{
var inStreamToCoder = new List<int>();
var outStreamToCoder = new List<int>();
for (var i = 0; i < _coders.Count; i++)
{
var coder = _coders[i];
for (var j = 0; j < coder._numInStreams; j++)
{
inStreamToCoder.Add(i);
}
for (var j = 0; j < coder._numOutStreams; j++)
{
outStreamToCoder.Add(i);
}
}
for (var i = 0; i < _bindPairs.Count; i++)
{
var bp = _bindPairs[i];
mask[inStreamToCoder[bp._inIndex]] |= (1u << outStreamToCoder[bp._outIndex]);
}
}
for (var i = 0; i < kMaskSize; i++)
{
for (var j = 0; j < kMaskSize; j++)
{
if (((1u << j) & mask[i]) != 0)
{
mask[i] |= mask[j];
}
}
}
for (var i = 0; i < kMaskSize; i++)
{
if (((1u << i) & mask[i]) != 0)
{
return false;
}
}
return true;
}
}

View File

@@ -2,43 +2,42 @@
namespace SharpCompress.Common.SevenZip;
internal readonly struct CMethodId : IEquatable<CMethodId>
{
public const ulong K_COPY_ID = 0;
public const ulong K_LZMA_ID = 0x030101;
public const ulong K_LZMA2_ID = 0x21;
public const ulong K_AES_ID = 0x06F10701;
public const ulong K_COPY_ID = 0;
public const ulong K_LZMA_ID = 0x030101;
public const ulong K_LZMA2_ID = 0x21;
public const ulong K_AES_ID = 0x06F10701;
public static readonly CMethodId K_COPY = new(K_COPY_ID);
public static readonly CMethodId K_LZMA = new(K_LZMA_ID);
public static readonly CMethodId K_LZMA2 = new(K_LZMA2_ID);
public static readonly CMethodId K_AES = new(K_AES_ID);
public static readonly CMethodId K_COPY = new(K_COPY_ID);
public static readonly CMethodId K_LZMA = new(K_LZMA_ID);
public static readonly CMethodId K_LZMA2 = new(K_LZMA2_ID);
public static readonly CMethodId K_AES = new(K_AES_ID);
public readonly ulong _id;
public readonly ulong _id;
public CMethodId(ulong id)
public CMethodId(ulong id)
{
_id = id;
}
public override int GetHashCode() => _id.GetHashCode();
public override bool Equals(object? obj) => obj is CMethodId other && Equals(other);
public bool Equals(CMethodId other) => _id == other._id;
public static bool operator ==(CMethodId left, CMethodId right) => left._id == right._id;
public static bool operator !=(CMethodId left, CMethodId right) => left._id != right._id;
public int GetLength()
{
var bytes = 0;
for (var value = _id; value != 0; value >>= 8)
{
_id = id;
}
public override int GetHashCode() => _id.GetHashCode();
public override bool Equals(object? obj) => obj is CMethodId other && Equals(other);
public bool Equals(CMethodId other) => _id == other._id;
public static bool operator ==(CMethodId left, CMethodId right) => left._id == right._id;
public static bool operator !=(CMethodId left, CMethodId right) => left._id != right._id;
public int GetLength()
{
var bytes = 0;
for (var value = _id; value != 0; value >>= 8)
{
bytes++;
}
return bytes;
bytes++;
}
return bytes;
}
}

View File

@@ -6,63 +6,63 @@ namespace SharpCompress.Common.SevenZip;
internal struct CStreamSwitch : IDisposable
{
private ArchiveReader _archive;
private bool _needRemove;
private bool _active;
private ArchiveReader _archive;
private bool _needRemove;
private bool _active;
public void Dispose()
public void Dispose()
{
if (_active)
{
if (_active)
{
_active = false;
_active = false;
#if DEBUG
Log.WriteLine("[end of switch]");
Log.WriteLine("[end of switch]");
#endif
}
if (_needRemove)
{
_needRemove = false;
_archive.DeleteByteStream();
}
}
public void Set(ArchiveReader archive, byte[] dataVector)
if (_needRemove)
{
Dispose();
_archive = archive;
_archive.AddByteStream(dataVector, 0, dataVector.Length);
_needRemove = true;
_active = true;
_needRemove = false;
_archive.DeleteByteStream();
}
}
public void Set(ArchiveReader archive, List<byte[]> dataVector)
public void Set(ArchiveReader archive, byte[] dataVector)
{
Dispose();
_archive = archive;
_archive.AddByteStream(dataVector, 0, dataVector.Length);
_needRemove = true;
_active = true;
}
public void Set(ArchiveReader archive, List<byte[]> dataVector)
{
Dispose();
_active = true;
var external = archive.ReadByte();
if (external != 0)
{
Dispose();
_active = true;
var external = archive.ReadByte();
if (external != 0)
{
var dataIndex = archive.ReadNum();
if (dataIndex < 0 || dataIndex >= dataVector.Count)
{
throw new InvalidOperationException();
}
var dataIndex = archive.ReadNum();
if (dataIndex < 0 || dataIndex >= dataVector.Count)
{
throw new InvalidOperationException();
}
#if DEBUG
Log.WriteLine("[switch to stream {0}]", dataIndex);
Log.WriteLine("[switch to stream {0}]", dataIndex);
#endif
_archive = archive;
_archive.AddByteStream(dataVector[dataIndex], 0, dataVector[dataIndex].Length);
_needRemove = true;
_active = true;
}
else
{
#if DEBUG
Log.WriteLine("[inline data]");
#endif
}
_archive = archive;
_archive.AddByteStream(dataVector[dataIndex], 0, dataVector[dataIndex].Length);
_needRemove = true;
_active = true;
}
else
{
#if DEBUG
Log.WriteLine("[inline data]");
#endif
}
}
}

View File

@@ -7,172 +7,172 @@ namespace SharpCompress.Common.SevenZip;
internal class DataReader
{
#region Static Methods
#region Static Methods
public static uint Get32(byte[] buffer, int offset) =>
buffer[offset]
+ ((uint)buffer[offset + 1] << 8)
+ ((uint)buffer[offset + 2] << 16)
+ ((uint)buffer[offset + 3] << 24);
public static uint Get32(byte[] buffer, int offset) =>
buffer[offset]
+ ((uint)buffer[offset + 1] << 8)
+ ((uint)buffer[offset + 2] << 16)
+ ((uint)buffer[offset + 3] << 24);
public static ulong Get64(byte[] buffer, int offset) =>
buffer[offset]
+ ((ulong)buffer[offset + 1] << 8)
+ ((ulong)buffer[offset + 2] << 16)
+ ((ulong)buffer[offset + 3] << 24)
+ ((ulong)buffer[offset + 4] << 32)
+ ((ulong)buffer[offset + 5] << 40)
+ ((ulong)buffer[offset + 6] << 48)
+ ((ulong)buffer[offset + 7] << 56);
public static ulong Get64(byte[] buffer, int offset) =>
buffer[offset]
+ ((ulong)buffer[offset + 1] << 8)
+ ((ulong)buffer[offset + 2] << 16)
+ ((ulong)buffer[offset + 3] << 24)
+ ((ulong)buffer[offset + 4] << 32)
+ ((ulong)buffer[offset + 5] << 40)
+ ((ulong)buffer[offset + 6] << 48)
+ ((ulong)buffer[offset + 7] << 56);
#endregion
#endregion
#region Variables
#region Variables
private readonly byte[] _buffer;
private readonly int _ending;
private readonly byte[] _buffer;
private readonly int _ending;
#endregion
#endregion
#region Public Methods
#region Public Methods
public DataReader(byte[] buffer, int offset, int length)
public DataReader(byte[] buffer, int offset, int length)
{
_buffer = buffer;
Offset = offset;
_ending = offset + length;
}
public int Offset { get; private set; }
public byte ReadByte()
{
if (Offset >= _ending)
{
_buffer = buffer;
Offset = offset;
_ending = offset + length;
throw new EndOfStreamException();
}
public int Offset { get; private set; }
return _buffer[Offset++];
}
public byte ReadByte()
public void ReadBytes(byte[] buffer, int offset, int length)
{
if (length > _ending - Offset)
{
if (Offset >= _ending)
{
throw new EndOfStreamException();
}
return _buffer[Offset++];
throw new EndOfStreamException();
}
public void ReadBytes(byte[] buffer, int offset, int length)
while (length-- > 0)
{
if (length > _ending - Offset)
{
throw new EndOfStreamException();
}
buffer[offset++] = _buffer[Offset++];
}
}
while (length-- > 0)
{
buffer[offset++] = _buffer[Offset++];
}
public void SkipData(long size)
{
if (size > _ending - Offset)
{
throw new EndOfStreamException();
}
public void SkipData(long size)
{
if (size > _ending - Offset)
{
throw new EndOfStreamException();
}
Offset += (int)size;
Offset += (int)size;
#if DEBUG
Log.WriteLine("SkipData {0}", size);
Log.WriteLine("SkipData {0}", size);
#endif
}
public void SkipData() => SkipData(checked((long)ReadNumber()));
public ulong ReadNumber()
{
if (Offset >= _ending)
{
throw new EndOfStreamException();
}
public void SkipData() => SkipData(checked((long)ReadNumber()));
var firstByte = _buffer[Offset++];
byte mask = 0x80;
ulong value = 0;
public ulong ReadNumber()
for (var i = 0; i < 8; i++)
{
if (Offset >= _ending)
{
throw new EndOfStreamException();
}
var firstByte = _buffer[Offset++];
byte mask = 0x80;
ulong value = 0;
for (var i = 0; i < 8; i++)
{
if ((firstByte & mask) == 0)
{
ulong highPart = firstByte & (mask - 1u);
value += highPart << (i * 8);
return value;
}
if (Offset >= _ending)
{
throw new EndOfStreamException();
}
value |= (ulong)_buffer[Offset++] << (8 * i);
mask >>= 1;
}
if ((firstByte & mask) == 0)
{
ulong highPart = firstByte & (mask - 1u);
value += highPart << (i * 8);
return value;
}
if (Offset >= _ending)
{
throw new EndOfStreamException();
}
value |= (ulong)_buffer[Offset++] << (8 * i);
mask >>= 1;
}
public int ReadNum()
return value;
}
public int ReadNum()
{
var value = ReadNumber();
if (value > int.MaxValue)
{
var value = ReadNumber();
if (value > int.MaxValue)
{
throw new NotSupportedException();
}
return (int)value;
throw new NotSupportedException();
}
public uint ReadUInt32()
return (int)value;
}
public uint ReadUInt32()
{
if (Offset + 4 > _ending)
{
if (Offset + 4 > _ending)
{
throw new EndOfStreamException();
}
var res = Get32(_buffer, Offset);
Offset += 4;
return res;
throw new EndOfStreamException();
}
public ulong ReadUInt64()
var res = Get32(_buffer, Offset);
Offset += 4;
return res;
}
public ulong ReadUInt64()
{
if (Offset + 8 > _ending)
{
if (Offset + 8 > _ending)
{
throw new EndOfStreamException();
}
var res = Get64(_buffer, Offset);
Offset += 8;
return res;
throw new EndOfStreamException();
}
public string ReadString()
var res = Get64(_buffer, Offset);
Offset += 8;
return res;
}
public string ReadString()
{
var ending = Offset;
for (; ; )
{
var ending = Offset;
if (ending + 2 > _ending)
{
throw new EndOfStreamException();
}
for (; ; )
{
if (ending + 2 > _ending)
{
throw new EndOfStreamException();
}
if (_buffer[ending] == 0 && _buffer[ending + 1] == 0)
{
break;
}
if (_buffer[ending] == 0 && _buffer[ending + 1] == 0)
{
break;
}
ending += 2;
}
var str = Encoding.Unicode.GetString(_buffer, Offset, ending - Offset);
Offset = ending + 2;
return str;
ending += 2;
}
#endregion
var str = Encoding.Unicode.GetString(_buffer, Offset, ending - Offset);
Offset = ending + 2;
return str;
}
#endregion
}

View File

@@ -5,44 +5,44 @@ namespace SharpCompress.Common.SevenZip;
public class SevenZipEntry : Entry
{
internal SevenZipEntry(SevenZipFilePart filePart)
{
FilePart = filePart;
}
internal SevenZipEntry(SevenZipFilePart filePart)
{
FilePart = filePart;
}
internal SevenZipFilePart FilePart { get; }
internal SevenZipFilePart FilePart { get; }
public override CompressionType CompressionType => FilePart.CompressionType;
public override CompressionType CompressionType => FilePart.CompressionType;
public override long Crc => FilePart.Header.Crc ?? 0;
public override long Crc => FilePart.Header.Crc ?? 0;
public override string? Key => FilePart.Header.Name;
public override string? Key => FilePart.Header.Name;
public override string? LinkTarget => null;
public override string? LinkTarget => null;
public override long CompressedSize => 0;
public override long CompressedSize => 0;
public override long Size => FilePart.Header.Size;
public override long Size => FilePart.Header.Size;
public override DateTime? LastModifiedTime => FilePart.Header.MTime;
public override DateTime? LastModifiedTime => FilePart.Header.MTime;
public override DateTime? CreatedTime => null;
public override DateTime? CreatedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? LastAccessedTime => null;
public override DateTime? ArchivedTime => null;
public override DateTime? ArchivedTime => null;
public override bool IsEncrypted => FilePart.IsEncrypted;
public override bool IsEncrypted => FilePart.IsEncrypted;
public override bool IsDirectory => FilePart.Header.IsDir;
public override bool IsDirectory => FilePart.Header.IsDir;
public override bool IsSplitAfter => false;
public override bool IsSplitAfter => false;
public override int? Attrib =>
FilePart.Header.Attrib.HasValue ? (int?)FilePart.Header.Attrib.Value : null;
public override int? Attrib =>
FilePart.Header.Attrib.HasValue ? (int?)FilePart.Header.Attrib.Value : null;
public int? ExtendedAttrib =>
FilePart.Header.ExtendedAttrib.HasValue ? (int?)FilePart.Header.ExtendedAttrib.Value : null;
public int? ExtendedAttrib =>
FilePart.Header.ExtendedAttrib.HasValue ? (int?)FilePart.Header.ExtendedAttrib.Value : null;
internal override IEnumerable<FilePart> Parts => FilePart.AsEnumerable<FilePart>();
internal override IEnumerable<FilePart> Parts => FilePart.AsEnumerable<FilePart>();
}

Some files were not shown because too many files have changed in this diff Show More