mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-06 05:27:05 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f515ff36b6 | ||
|
|
ed57cfd2f9 | ||
|
|
d69559e9c7 | ||
|
|
396717efd1 | ||
|
|
284fa24464 | ||
|
|
0a20b9179a | ||
|
|
a0d5037885 | ||
|
|
4477833b1d | ||
|
|
e0a5ed4bdb | ||
|
|
46d4b26eba | ||
|
|
f7c6edf849 | ||
|
|
6c157def4b | ||
|
|
741712f89f | ||
|
|
4f749da628 | ||
|
|
8b02795d69 | ||
|
|
f8a0069a5d | ||
|
|
388bbe047e | ||
|
|
2d4ce30e58 | ||
|
|
d4fb17cf66 | ||
|
|
372a2c8375 | ||
|
|
8f27121f21 | ||
|
|
b986bf675f | ||
|
|
80718a461b | ||
|
|
2d14ecf58b | ||
|
|
32aa9877c0 | ||
|
|
cee3a9c11d | ||
|
|
b78643f2d8 | ||
|
|
30a31de45b | ||
|
|
e4c4db534c | ||
|
|
4f7a0d3ad0 | ||
|
|
ea3a96eead | ||
|
|
c0e01ac132 | ||
|
|
28ea50bca4 | ||
|
|
619e44b30f | ||
|
|
d678275dee | ||
|
|
08eed53595 | ||
|
|
ff40f7d262 | ||
|
|
3c1ae51dae | ||
|
|
8a59fc9aaf | ||
|
|
b7ea9dd841 | ||
|
|
0320db6b4a | ||
|
|
18c7f58093 | ||
|
|
7f6f7b1436 | ||
|
|
ca49176b97 |
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"csharpier": {
|
||||
"version": "0.26.1",
|
||||
"version": "0.26.7",
|
||||
"commands": [
|
||||
"dotnet-csharpier"
|
||||
]
|
||||
|
||||
13
.github/workflows/dotnetcore.yml
vendored
13
.github/workflows/dotnetcore.yml
vendored
@@ -15,18 +15,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-dotnet@v3
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: NuGet Caching
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('packages.lock.json', '*/packages.lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
dotnet-version: 8.0.x
|
||||
- run: dotnet run --project build/build.csproj
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-sharpcompress.nupkg
|
||||
path: artifacts/*
|
||||
|
||||
@@ -36,7 +36,7 @@ I'm always looking for help or ideas. Please submit code or email with ideas. Un
|
||||
|
||||
## TODOs (always lots)
|
||||
|
||||
* RAR 5 decryption support
|
||||
* RAR 5 decryption crc check support
|
||||
* 7Zip writing
|
||||
* Zip64 (Need writing and extend Reading)
|
||||
* Multi-volume Zip support.
|
||||
|
||||
@@ -61,7 +61,7 @@ Target(
|
||||
Target(
|
||||
Test,
|
||||
DependsOn(Build),
|
||||
ForEach("net7.0", "net462"),
|
||||
ForEach("net8.0", "net462"),
|
||||
framework =>
|
||||
{
|
||||
IEnumerable<string> GetFiles(string d)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.101",
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ public static class ArchiveFactory
|
||||
public static IWritableArchive Create(ArchiveType type)
|
||||
{
|
||||
var factory = Factory
|
||||
.Factories
|
||||
.OfType<IWriteableArchiveFactory>()
|
||||
.Factories.OfType<IWriteableArchiveFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == type);
|
||||
|
||||
if (factory != null)
|
||||
|
||||
@@ -40,9 +40,9 @@ public class RarArchive : AbstractArchive<RarArchiveEntry, RarVolume>
|
||||
streams[1].Position = 0;
|
||||
SrcStream.Position = 0;
|
||||
|
||||
return srcStream
|
||||
.Streams
|
||||
.Select(a => new StreamRarArchiveVolume(a, ReaderOptions, idx++));
|
||||
return srcStream.Streams.Select(
|
||||
a => new StreamRarArchiveVolume(a, ReaderOptions, idx++)
|
||||
);
|
||||
}
|
||||
else //split mode or single file
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -40,7 +41,10 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry
|
||||
get
|
||||
{
|
||||
CheckIncomplete();
|
||||
return parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc;
|
||||
return BitConverter.ToUInt32(
|
||||
parts.Select(fp => fp.FileHeader).Single(fh => !fh.IsSplitAfter).FileCrc,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,19 @@ internal class SeekableFilePart : RarFilePart
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
stream.Position = FileHeader.DataStartPosition;
|
||||
|
||||
if (FileHeader.R4Salt != null)
|
||||
{
|
||||
return new RarCryptoWrapper(stream, password!, FileHeader.R4Salt);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -236,13 +236,11 @@ public class SevenZipArchive : AbstractArchive<SevenZipArchiveEntry, SevenZipVol
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStream = archive
|
||||
.database
|
||||
.GetFolderStream(
|
||||
stream,
|
||||
currentFolder,
|
||||
new PasswordProvider(Options.Password)
|
||||
);
|
||||
currentStream = archive.database.GetFolderStream(
|
||||
stream,
|
||||
currentFolder,
|
||||
new PasswordProvider(Options.Password)
|
||||
);
|
||||
}
|
||||
foreach (var entry in group)
|
||||
{
|
||||
|
||||
87
src/SharpCompress/Common/Rar/CryptKey3.cs
Normal file
87
src/SharpCompress/Common/Rar/CryptKey3.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
#nullable disable
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class CryptKey3 : ICryptKey
|
||||
{
|
||||
const int AES_128 = 128;
|
||||
|
||||
private 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++)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
97
src/SharpCompress/Common/Rar/CryptKey5.cs
Normal file
97
src/SharpCompress/Common/Rar/CryptKey5.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class CryptKey5 : ICryptKey
|
||||
{
|
||||
const int AES_256 = 256;
|
||||
const int DERIVED_KEY_LENGTH = 0x10;
|
||||
const int SHA256_DIGEST_SIZE = 32;
|
||||
|
||||
private string _password;
|
||||
private Rar5CryptoInfo _cryptoInfo;
|
||||
private byte[] _pswCheck = { };
|
||||
private byte[] _hashKey = { };
|
||||
|
||||
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 (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password)))
|
||||
{
|
||||
byte[] block = hmac.ComputeHash(salt);
|
||||
byte[] finalHash = (byte[])block.Clone();
|
||||
|
||||
var loop = new int[] { iterations, 17, 17 };
|
||||
var res = new List<byte[]> { };
|
||||
|
||||
for (int x = 0; x < 3; x++)
|
||||
{
|
||||
for (int i = 1; i < loop[x]; i++)
|
||||
{
|
||||
block = hmac.ComputeHash(block);
|
||||
for (int j = 0; j < finalHash.Length; j++)
|
||||
{
|
||||
finalHash[j] ^= block[j];
|
||||
}
|
||||
}
|
||||
|
||||
res.Add((byte[])finalHash.Clone());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public ICryptoTransform Transformer(byte[] salt)
|
||||
{
|
||||
int 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 (int 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();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,21 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Rar.Headers;
|
||||
|
||||
internal class ArchiveCryptHeader : RarHeader
|
||||
{
|
||||
private const int CRYPT_VERSION = 0; // Supported encryption version.
|
||||
private const int SIZE_SALT50 = 16;
|
||||
private const int SIZE_PSWCHECK = 8;
|
||||
private const int SIZE_PSWCHECK_CSUM = 4;
|
||||
private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count.
|
||||
|
||||
private bool _usePswCheck;
|
||||
private uint _lg2Count; // Log2 of PBKDF2 repetition count.
|
||||
private byte[] _salt;
|
||||
private byte[] _pswCheck;
|
||||
private byte[] _pswCheckCsm;
|
||||
|
||||
public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader)
|
||||
: base(header, reader, HeaderType.Crypt) { }
|
||||
|
||||
public Rar5CryptoInfo CryptInfo = new Rar5CryptoInfo();
|
||||
|
||||
protected override void ReadFinish(MarkingBinaryReader reader)
|
||||
{
|
||||
var cryptVersion = reader.ReadRarVIntUInt32();
|
||||
if (cryptVersion > CRYPT_VERSION)
|
||||
{
|
||||
//error?
|
||||
return;
|
||||
}
|
||||
var encryptionFlags = reader.ReadRarVIntUInt32();
|
||||
_usePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
|
||||
_lg2Count = reader.ReadRarVIntByte(1);
|
||||
|
||||
//UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK);
|
||||
if (_lg2Count > CRYPT5_KDF_LG2_COUNT_MAX)
|
||||
{
|
||||
//error?
|
||||
return;
|
||||
}
|
||||
|
||||
_salt = reader.ReadBytes(SIZE_SALT50);
|
||||
if (_usePswCheck)
|
||||
{
|
||||
_pswCheck = reader.ReadBytes(SIZE_PSWCHECK);
|
||||
_pswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM);
|
||||
}
|
||||
CryptInfo = new Rar5CryptoInfo(reader, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.IO;
|
||||
#if !Rar2017_64bit
|
||||
@@ -16,7 +18,7 @@ namespace SharpCompress.Common.Rar.Headers;
|
||||
|
||||
internal class FileHeader : RarHeader
|
||||
{
|
||||
private uint _fileCrc;
|
||||
private byte[] _hash;
|
||||
|
||||
public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
|
||||
: base(header, reader, headerType) { }
|
||||
@@ -51,7 +53,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
if (HasFlag(FileFlagsV5.HAS_CRC32))
|
||||
{
|
||||
FileCrc = reader.ReadUInt32();
|
||||
FileCrc = reader.ReadBytes(4);
|
||||
}
|
||||
|
||||
var compressionInfo = reader.ReadRarVIntUInt16();
|
||||
@@ -103,7 +105,13 @@ internal class FileHeader : RarHeader
|
||||
throw new InvalidFormatException("rar5 header size / extra size inconsistency");
|
||||
}
|
||||
|
||||
isEncryptedRar5 = false;
|
||||
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)
|
||||
{
|
||||
@@ -113,21 +121,32 @@ internal class FileHeader : RarHeader
|
||||
switch (type)
|
||||
{
|
||||
//TODO
|
||||
case 1: // file encryption
|
||||
case FHEXTRA_CRYPT: // file encryption
|
||||
|
||||
{
|
||||
isEncryptedRar5 = true;
|
||||
Rar5CryptoInfo = new Rar5CryptoInfo(reader, true);
|
||||
|
||||
//var version = reader.ReadRarVIntByte();
|
||||
//if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version);
|
||||
if (Rar5CryptoInfo.PswCheck.All(singleByte => singleByte == 0))
|
||||
{
|
||||
Rar5CryptoInfo = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// case 2: // file hash
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
case 3: // file time
|
||||
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();
|
||||
@@ -147,22 +166,22 @@ internal class FileHeader : RarHeader
|
||||
}
|
||||
break;
|
||||
//TODO
|
||||
// case 4: // file version
|
||||
// case FHEXTRA_VERSION: // file version
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
// case 5: // file system redirection
|
||||
// case FHEXTRA_REDIR: // file system redirection
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
// case 6: // unix owner
|
||||
// case FHEXTRA_UOWNER: // unix owner
|
||||
// {
|
||||
//
|
||||
// }
|
||||
// break;
|
||||
// case 7: // service data
|
||||
// case FHEXTRA_SUBDATA: // service data
|
||||
// {
|
||||
//
|
||||
// }
|
||||
@@ -221,7 +240,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
HostOs = reader.ReadByte();
|
||||
|
||||
FileCrc = reader.ReadUInt32();
|
||||
FileCrc = reader.ReadBytes(4);
|
||||
|
||||
FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32());
|
||||
|
||||
@@ -254,7 +273,6 @@ internal class FileHeader : RarHeader
|
||||
|
||||
var fileNameBytes = reader.ReadBytes(nameSize);
|
||||
|
||||
const int saltSize = 8;
|
||||
const int newLhdSize = 32;
|
||||
|
||||
switch (HeaderCode)
|
||||
@@ -292,7 +310,7 @@ internal class FileHeader : RarHeader
|
||||
var datasize = HeaderSize - newLhdSize - nameSize;
|
||||
if (HasFlag(FileFlagsV4.SALT))
|
||||
{
|
||||
datasize -= saltSize;
|
||||
datasize -= EncryptionConstV5.SIZE_SALT30;
|
||||
}
|
||||
if (datasize > 0)
|
||||
{
|
||||
@@ -313,7 +331,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
if (HasFlag(FileFlagsV4.SALT))
|
||||
{
|
||||
R4Salt = reader.ReadBytes(saltSize);
|
||||
R4Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT30);
|
||||
}
|
||||
if (HasFlag(FileFlagsV4.EXT_TIME))
|
||||
{
|
||||
@@ -394,18 +412,10 @@ internal class FileHeader : RarHeader
|
||||
|
||||
private bool HasFlag(ushort flag) => (Flags & flag) == flag;
|
||||
|
||||
internal uint FileCrc
|
||||
internal byte[] FileCrc
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsRar5 && !HasFlag(FileFlagsV5.HAS_CRC32))
|
||||
{
|
||||
//!!! rar5:
|
||||
throw new InvalidOperationException("TODO rar5");
|
||||
}
|
||||
return _fileCrc;
|
||||
}
|
||||
private set => _fileCrc = value;
|
||||
get => _hash;
|
||||
private set => _hash = value;
|
||||
}
|
||||
|
||||
// 0 - storing
|
||||
@@ -431,7 +441,7 @@ internal class FileHeader : RarHeader
|
||||
internal size_t WindowSize { 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; }
|
||||
@@ -449,8 +459,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY);
|
||||
|
||||
private bool isEncryptedRar5 = false;
|
||||
public bool IsEncrypted => IsRar5 ? isEncryptedRar5 : HasFlag(FileFlagsV4.PASSWORD);
|
||||
public bool IsEncrypted => IsRar5 ? Rar5CryptoInfo != null : HasFlag(FileFlagsV4.PASSWORD);
|
||||
|
||||
internal DateTime? FileLastModifiedTime { get; private set; }
|
||||
|
||||
|
||||
@@ -50,6 +50,17 @@ internal static class EncryptionFlagsV5
|
||||
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;
|
||||
}
|
||||
|
||||
internal static class HeaderFlagsV5
|
||||
{
|
||||
public const ushort HAS_EXTRA = 0x0001;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Common.Rar;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
@@ -9,6 +11,8 @@ public class RarHeaderFactory
|
||||
{
|
||||
private bool _isRar5;
|
||||
|
||||
private Rar5CryptoInfo? _cryptInfo;
|
||||
|
||||
public RarHeaderFactory(StreamingMode mode, ReaderOptions options)
|
||||
{
|
||||
StreamingMode = mode;
|
||||
@@ -53,7 +57,19 @@ public class RarHeaderFactory
|
||||
"Encrypted Rar archive has no password specified."
|
||||
);
|
||||
}
|
||||
reader = new RarCryptoBinaryReader(stream, Options.Password);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding);
|
||||
@@ -105,7 +121,14 @@ public class RarHeaderFactory
|
||||
case HeaderCodeV.RAR5_SERVICE_HEADER:
|
||||
{
|
||||
var fh = new FileHeader(header, reader, HeaderType.Service);
|
||||
SkipData(fh, reader);
|
||||
if (fh.FileName == "CMT")
|
||||
{
|
||||
fh.PackedStream = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
SkipData(fh, reader);
|
||||
}
|
||||
return fh;
|
||||
}
|
||||
|
||||
@@ -133,7 +156,7 @@ public class RarHeaderFactory
|
||||
|
||||
{
|
||||
var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
|
||||
if (fh.R4Salt is null)
|
||||
if (fh.R4Salt is null && fh.Rar5CryptoInfo is null)
|
||||
{
|
||||
fh.PackedStream = ms;
|
||||
}
|
||||
@@ -141,8 +164,10 @@ public class RarHeaderFactory
|
||||
{
|
||||
fh.PackedStream = new RarCryptoWrapper(
|
||||
ms,
|
||||
Options.Password!,
|
||||
fh.R4Salt
|
||||
fh.R4Salt is null ? fh.Rar5CryptoInfo.Salt : fh.R4Salt,
|
||||
fh.R4Salt is null
|
||||
? new CryptKey5(Options.Password!, fh.Rar5CryptoInfo)
|
||||
: new CryptKey3(Options.Password!)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -161,9 +186,11 @@ public class RarHeaderFactory
|
||||
}
|
||||
case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER:
|
||||
{
|
||||
var ch = new ArchiveCryptHeader(header, reader);
|
||||
var cryptoHeader = new ArchiveCryptHeader(header, reader);
|
||||
IsEncrypted = true;
|
||||
return ch;
|
||||
_cryptInfo = cryptoHeader.CryptInfo;
|
||||
|
||||
return cryptoHeader;
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
||||
8
src/SharpCompress/Common/Rar/ICryptKey.cs
Normal file
8
src/SharpCompress/Common/Rar/ICryptKey.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal interface ICryptKey
|
||||
{
|
||||
ICryptoTransform Transformer(byte[] salt);
|
||||
}
|
||||
59
src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs
Normal file
59
src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class Rar5CryptoInfo
|
||||
{
|
||||
public Rar5CryptoInfo() { }
|
||||
|
||||
public Rar5CryptoInfo(MarkingBinaryReader reader, bool readInitV)
|
||||
{
|
||||
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);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadInitV(MarkingBinaryReader reader)
|
||||
{
|
||||
InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV);
|
||||
}
|
||||
|
||||
public bool UsePswCheck = false;
|
||||
|
||||
public int LG2Count = 0;
|
||||
|
||||
public byte[] InitV = { };
|
||||
|
||||
public byte[] Salt = { };
|
||||
|
||||
public byte[] PswCheck = { };
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader
|
||||
{
|
||||
private RarRijndael _rijndael;
|
||||
private byte[] _salt;
|
||||
private readonly string _password;
|
||||
private BlockTransformer _rijndael;
|
||||
private readonly Queue<byte> _data = new Queue<byte>();
|
||||
private long _readCount;
|
||||
|
||||
public RarCryptoBinaryReader(Stream stream, string password)
|
||||
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey)
|
||||
: base(stream)
|
||||
{
|
||||
_password = password;
|
||||
var salt = base.ReadBytes(EncryptionConstV5.SIZE_SALT30);
|
||||
_readCount += EncryptionConstV5.SIZE_SALT30;
|
||||
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
|
||||
}
|
||||
|
||||
// coderb: not sure why this was being done at this logical point
|
||||
//SkipQueue();
|
||||
var salt = ReadBytes(8);
|
||||
|
||||
_salt = salt;
|
||||
_rijndael = RarRijndael.InitializeFrom(_password, 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
|
||||
@@ -36,28 +39,14 @@ internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader
|
||||
|
||||
public override void Mark() => _readCount = 0;
|
||||
|
||||
private bool UseEncryption => _salt != null;
|
||||
|
||||
public override byte ReadByte()
|
||||
{
|
||||
if (UseEncryption)
|
||||
{
|
||||
return ReadAndDecryptBytes(1)[0];
|
||||
}
|
||||
|
||||
_readCount++;
|
||||
return base.ReadByte();
|
||||
return ReadAndDecryptBytes(1)[0];
|
||||
}
|
||||
|
||||
public override byte[] ReadBytes(int count)
|
||||
{
|
||||
if (UseEncryption)
|
||||
{
|
||||
return ReadAndDecryptBytes(count);
|
||||
}
|
||||
|
||||
_readCount += count;
|
||||
return base.ReadBytes(count);
|
||||
return ReadAndDecryptBytes(count);
|
||||
}
|
||||
|
||||
private byte[] ReadAndDecryptBytes(int count)
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal sealed class RarCryptoWrapper : Stream
|
||||
{
|
||||
private readonly Stream _actualStream;
|
||||
private readonly byte[] _salt;
|
||||
private RarRijndael _rijndael;
|
||||
private BlockTransformer _rijndael;
|
||||
private readonly Queue<byte> _data = new Queue<byte>();
|
||||
|
||||
public RarCryptoWrapper(Stream actualStream, string password, byte[] salt)
|
||||
public RarCryptoWrapper(Stream actualStream, byte[] salt, ICryptKey key)
|
||||
{
|
||||
_actualStream = actualStream;
|
||||
_salt = salt;
|
||||
_rijndael = RarRijndael.InitializeFrom(password ?? "", salt);
|
||||
_rijndael = new BlockTransformer(key.Transformer(salt));
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
@@ -26,10 +25,6 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_salt is null)
|
||||
{
|
||||
return _actualStream.Read(buffer, offset, count);
|
||||
}
|
||||
return ReadAndDecrypt(buffer, offset, count);
|
||||
}
|
||||
|
||||
@@ -41,7 +36,7 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
if (sizeToRead > 0)
|
||||
{
|
||||
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
|
||||
Span<byte> cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE];
|
||||
Span<byte> cipherText = stackalloc byte[16];
|
||||
for (var i = 0; i < alignedSize / 16; i++)
|
||||
{
|
||||
//long ax = System.currentTimeMillis();
|
||||
|
||||
@@ -20,7 +20,7 @@ public abstract class RarEntry : Entry
|
||||
/// <summary>
|
||||
/// The File's 32 bit CRC Hash
|
||||
/// </summary>
|
||||
public override long Crc => FileHeader.FileCrc;
|
||||
public override long Crc => BitConverter.ToUInt32(FileHeader.FileCrc, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The path of the file internal to the Rar Archive.
|
||||
@@ -55,7 +55,12 @@ public abstract class RarEntry : Entry
|
||||
public override bool IsEncrypted => FileHeader.IsEncrypted;
|
||||
|
||||
/// <summary>
|
||||
/// Entry is password protected and encrypted and cannot be extracted.
|
||||
/// Entry Windows file attributes
|
||||
/// </summary>
|
||||
public override int? Attrib => (int)FileHeader.FileAttributes;
|
||||
|
||||
/// <summary>
|
||||
/// Entry is a directory
|
||||
/// </summary>
|
||||
public override bool IsDirectory => FileHeader.IsDirectory;
|
||||
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SharpCompress.Crypto;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal class RarRijndael : IDisposable
|
||||
{
|
||||
internal const int CRYPTO_BLOCK_SIZE = 16;
|
||||
|
||||
private readonly string _password;
|
||||
private readonly byte[] _salt;
|
||||
private byte[] _aesInitializationVector;
|
||||
private RijndaelEngine _rijndael;
|
||||
|
||||
private RarRijndael(string password, byte[] salt)
|
||||
{
|
||||
_password = password;
|
||||
_salt = salt;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_rijndael = new RijndaelEngine();
|
||||
_aesInitializationVector = new byte[CRYPTO_BLOCK_SIZE];
|
||||
var rawLength = 2 * _password.Length;
|
||||
var rawPassword = new byte[rawLength + 8];
|
||||
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];
|
||||
}
|
||||
|
||||
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 >> CRYPTO_BLOCK_SIZE
|
||||
);
|
||||
|
||||
if (i % (noOfRounds / CRYPTO_BLOCK_SIZE) == 0)
|
||||
{
|
||||
digest = SHA1.Create()
|
||||
.ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock));
|
||||
_aesInitializationVector[i / (noOfRounds / CRYPTO_BLOCK_SIZE)] = digest[19];
|
||||
}
|
||||
}
|
||||
digest = SHA1.Create().ComputeHash(data);
|
||||
//slow code ends
|
||||
|
||||
var aesKey = new byte[CRYPTO_BLOCK_SIZE];
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_rijndael.Init(false, new KeyParameter(aesKey));
|
||||
}
|
||||
|
||||
public static RarRijndael InitializeFrom(string password, byte[] salt)
|
||||
{
|
||||
var rijndael = new RarRijndael(password, salt);
|
||||
rijndael.Initialize();
|
||||
return rijndael;
|
||||
}
|
||||
|
||||
public byte[] ProcessBlock(ReadOnlySpan<byte> cipherText)
|
||||
{
|
||||
Span<byte> plainText = stackalloc byte[CRYPTO_BLOCK_SIZE]; // 16 bytes
|
||||
var decryptedBytes = new byte[CRYPTO_BLOCK_SIZE];
|
||||
_rijndael.ProcessBlock(cipherText, plainText);
|
||||
|
||||
for (var j = 0; j < CRYPTO_BLOCK_SIZE; j++)
|
||||
{
|
||||
decryptedBytes[j] = (byte)(plainText[j] ^ _aesInitializationVector[j % 16]); //32:114, 33:101
|
||||
}
|
||||
|
||||
for (var j = 0; j < _aesInitializationVector.Length; j++)
|
||||
{
|
||||
_aesInitializationVector[j] = cipherText[j];
|
||||
}
|
||||
|
||||
return decryptedBytes;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
@@ -70,11 +71,7 @@ public abstract class RarVolume : Volume
|
||||
var part = CreateFilePart(lastMarkHeader!, fh);
|
||||
var buffer = new byte[fh.CompressedSize];
|
||||
part.GetCompressedStream().Read(buffer, 0, buffer.Length);
|
||||
Comment = System
|
||||
.Text
|
||||
.Encoding
|
||||
.UTF8
|
||||
.GetString(buffer, 0, buffer.Length - 1);
|
||||
Comment = Encoding.UTF8.GetString(buffer, 0, buffer.Length - 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1393,7 +1393,7 @@ internal class ArchiveReader
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable disable
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
@@ -132,8 +132,8 @@ internal sealed class TarHeader
|
||||
Mode |= 0b1_000_000_000;
|
||||
}
|
||||
|
||||
UserId = ReadAsciiInt64Base8(buffer, 108, 7);
|
||||
GroupId = ReadAsciiInt64Base8(buffer, 116, 7);
|
||||
UserId = ReadAsciiInt64Base8oldGnu(buffer, 108, 7);
|
||||
GroupId = ReadAsciiInt64Base8oldGnu(buffer, 116, 7);
|
||||
var unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
|
||||
LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
|
||||
@@ -249,6 +249,24 @@ internal sealed class TarHeader
|
||||
return Convert.ToInt64(s, 8);
|
||||
}
|
||||
|
||||
private static long ReadAsciiInt64Base8oldGnu(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer[offset] == 0x80 && buffer[offset + 1] == 0x00)
|
||||
{
|
||||
return buffer[offset + 4] << 24
|
||||
| buffer[offset + 5] << 16
|
||||
| buffer[offset + 6] << 8
|
||||
| buffer[offset + 7];
|
||||
}
|
||||
var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
|
||||
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return Convert.ToInt64(s, 8);
|
||||
}
|
||||
|
||||
private static long ReadAsciiInt64(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -47,7 +47,7 @@ internal class TarReadOnlySubStream : NonDisposingStream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -85,6 +85,36 @@ internal class DirectoryEntryHeader : ZipFileEntry
|
||||
RelativeOffsetOfEntryHeader = zip64ExtraData.RelativeOffsetOfEntryHeader;
|
||||
}
|
||||
}
|
||||
|
||||
var unixTimeExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnixTimeExtraField);
|
||||
|
||||
if (unixTimeExtra is not null)
|
||||
{
|
||||
// Tuple order is last modified time, last access time, and creation time.
|
||||
var unixTimeTuple = ((UnixTimeExtraField)unixTimeExtra).UnicodeTimes;
|
||||
|
||||
if (unixTimeTuple.Item1.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item1.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item2.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item2.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item3.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item3.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ushort Version { get; private set; }
|
||||
|
||||
@@ -64,6 +64,36 @@ internal class LocalEntryHeader : ZipFileEntry
|
||||
UncompressedSize = zip64ExtraData.UncompressedSize;
|
||||
}
|
||||
}
|
||||
|
||||
var unixTimeExtra = Extra.FirstOrDefault(u => u.Type == ExtraDataType.UnixTimeExtraField);
|
||||
|
||||
if (unixTimeExtra is not null)
|
||||
{
|
||||
// Tuple order is last modified time, last access time, and creation time.
|
||||
var unixTimeTuple = ((UnixTimeExtraField)unixTimeExtra).UnicodeTimes;
|
||||
|
||||
if (unixTimeTuple.Item1.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item1.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item2.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item2.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
else if (unixTimeTuple.Item3.HasValue)
|
||||
{
|
||||
var dosTime = Utility.DateTimeToDosTime(unixTimeTuple.Item3.Value);
|
||||
|
||||
LastModifiedDate = (ushort)(dosTime >> 16);
|
||||
LastModifiedTime = (ushort)(dosTime & 0x0FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ushort Version { get; private set; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
@@ -13,7 +13,8 @@ internal enum ExtraDataType : ushort
|
||||
// Third Party Mappings
|
||||
// -Info-ZIP Unicode Path Extra Field
|
||||
UnicodePathExtraField = 0x7075,
|
||||
Zip64ExtendedInformationExtraField = 0x0001
|
||||
Zip64ExtendedInformationExtraField = 0x0001,
|
||||
UnixTimeExtraField = 0x5455
|
||||
}
|
||||
|
||||
internal class ExtraData
|
||||
@@ -145,6 +146,84 @@ internal sealed class Zip64ExtendedInformationExtraField : ExtraData
|
||||
public uint VolumeNumber { get; private set; }
|
||||
}
|
||||
|
||||
internal sealed class UnixTimeExtraField : ExtraData
|
||||
{
|
||||
public UnixTimeExtraField(ExtraDataType type, ushort length, byte[] dataBytes)
|
||||
: base(type, length, dataBytes) { }
|
||||
|
||||
/// <summary>
|
||||
/// The unix modified time, last access time, and creation time, if set.
|
||||
/// </summary>
|
||||
/// <remarks>Must return Tuple explicitly due to net462 support.</remarks>
|
||||
internal Tuple<DateTime?, DateTime?, DateTime?> UnicodeTimes
|
||||
{
|
||||
get
|
||||
{
|
||||
// There has to be at least 5 byte for there to be a timestamp.
|
||||
// 1 byte for flags and 4 bytes for a timestamp.
|
||||
if (DataBytes is null || DataBytes.Length < 5)
|
||||
{
|
||||
return Tuple.Create<DateTime?, DateTime?, DateTime?>(null, null, null);
|
||||
}
|
||||
|
||||
var flags = DataBytes[0];
|
||||
var isModifiedTimeSpecified = (flags & 0x01) == 1;
|
||||
var isLastAccessTimeSpecified = (flags & 0x02) == 1;
|
||||
var isCreationTimeSpecified = (flags & 0x04) == 1;
|
||||
var currentIndex = 1;
|
||||
DateTime? modifiedTime = null;
|
||||
DateTime? lastAccessTime = null;
|
||||
DateTime? creationTime = null;
|
||||
|
||||
if (isModifiedTimeSpecified)
|
||||
{
|
||||
var modifiedEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
|
||||
DataBytes.AsSpan(currentIndex, 4)
|
||||
);
|
||||
|
||||
currentIndex += 4;
|
||||
modifiedTime = DateTimeOffset.FromUnixTimeSeconds(modifiedEpochTime).UtcDateTime;
|
||||
}
|
||||
|
||||
if (isLastAccessTimeSpecified)
|
||||
{
|
||||
if (currentIndex + 4 > DataBytes.Length)
|
||||
{
|
||||
throw new ArchiveException("Invalid UnicodeExtraTime field");
|
||||
}
|
||||
|
||||
var lastAccessEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
|
||||
DataBytes.AsSpan(currentIndex, 4)
|
||||
);
|
||||
|
||||
currentIndex += 4;
|
||||
lastAccessTime = DateTimeOffset
|
||||
.FromUnixTimeSeconds(lastAccessEpochTime)
|
||||
.UtcDateTime;
|
||||
}
|
||||
|
||||
if (isCreationTimeSpecified)
|
||||
{
|
||||
if (currentIndex + 4 > DataBytes.Length)
|
||||
{
|
||||
throw new ArchiveException("Invalid UnicodeExtraTime field");
|
||||
}
|
||||
|
||||
var creationTimeEpochTime = BinaryPrimitives.ReadInt32LittleEndian(
|
||||
DataBytes.AsSpan(currentIndex, 4)
|
||||
);
|
||||
|
||||
currentIndex += 4;
|
||||
creationTime = DateTimeOffset
|
||||
.FromUnixTimeSeconds(creationTimeEpochTime)
|
||||
.UtcDateTime;
|
||||
}
|
||||
|
||||
return Tuple.Create(modifiedTime, lastAccessTime, creationTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LocalEntryHeaderExtraFactory
|
||||
{
|
||||
internal static ExtraData Create(ExtraDataType type, ushort length, byte[] extraData) =>
|
||||
@@ -154,6 +233,7 @@ internal static class LocalEntryHeaderExtraFactory
|
||||
=> new ExtraUnicodePathExtraField(type, length, extraData),
|
||||
ExtraDataType.Zip64ExtendedInformationExtraField
|
||||
=> new Zip64ExtendedInformationExtraField(type, length, extraData),
|
||||
ExtraDataType.UnixTimeExtraField => new UnixTimeExtraField(type, length, extraData),
|
||||
_ => new ExtraData(type, length, extraData)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Zip;
|
||||
@@ -87,10 +87,7 @@ internal class PkwareTraditionalCryptoStream : Stream
|
||||
_stream.Write(encrypted, 0, encrypted.Length);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
//throw new NotSupportedException();
|
||||
}
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ internal class WinzipAesCryptoStream : Stream
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
|
||||
@@ -48,15 +48,15 @@ internal class WinzipAesEncryptionData
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
#if NET7_0
|
||||
#if NETFRAMEWORK || NETSTANDARD2_0
|
||||
var rfc2898 = new Rfc2898DeriveBytes(_password, _salt, RFC2898_ITERATIONS);
|
||||
#else
|
||||
var rfc2898 = new Rfc2898DeriveBytes(
|
||||
_password,
|
||||
_salt,
|
||||
RFC2898_ITERATIONS,
|
||||
HashAlgorithmName.SHA1
|
||||
);
|
||||
#else
|
||||
var rfc2898 = new Rfc2898DeriveBytes(_password, _salt, RFC2898_ITERATIONS);
|
||||
#endif
|
||||
|
||||
KeyBytes = rfc2898.GetBytes(KeySizeInBytes); // 16 or 24 or 32 ???
|
||||
|
||||
@@ -142,9 +142,9 @@ internal class ZipHeaderFactory
|
||||
|
||||
if (entryHeader.CompressionMethod == ZipCompressionMethod.WinzipAes)
|
||||
{
|
||||
var data = entryHeader
|
||||
.Extra
|
||||
.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
|
||||
var data = entryHeader.Extra.SingleOrDefault(
|
||||
x => x.Type == ExtraDataType.WinZipAes
|
||||
);
|
||||
if (data != null)
|
||||
{
|
||||
var keySize = (WinzipAesKeySize)data.DataBytes[4];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Filters;
|
||||
@@ -79,7 +79,7 @@ internal class BCJ2Filter : Stream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => _baseStream.Length + _data1.Length + _data2.Length;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Filters;
|
||||
@@ -40,7 +40,7 @@ internal abstract class Filter : Stream
|
||||
|
||||
public override bool CanWrite => _isEncoder;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => _baseStream.Length;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common.SevenZip;
|
||||
using SharpCompress.Compressors.LZMA.Utilites;
|
||||
@@ -14,7 +14,7 @@ internal abstract class DecoderStream2 : Stream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -207,14 +207,23 @@ public class LzmaStream : Stream
|
||||
|
||||
if (_availableBytes == 0 && !_uncompressedChunk)
|
||||
{
|
||||
_rangeDecoder.ReleaseStream();
|
||||
// Check range corruption scenario
|
||||
if (
|
||||
!_rangeDecoder.IsFinished
|
||||
|| (_rangeDecoderLimit >= 0 && _rangeDecoder._total != _rangeDecoderLimit)
|
||||
)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
// Stream might have End Of Stream marker
|
||||
_outWindow.SetLimit(toProcess + 1);
|
||||
if (!_decoder.Code(_dictionarySize, _outWindow, _rangeDecoder))
|
||||
{
|
||||
_rangeDecoder.ReleaseStream();
|
||||
throw new DataErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
_rangeDecoder.ReleaseStream();
|
||||
|
||||
_inputPosition += _rangeDecoder._total;
|
||||
if (_outWindow.HasPending)
|
||||
{
|
||||
|
||||
@@ -128,9 +128,9 @@ internal sealed class MultiVolumeReadOnlyStream : Stream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public uint CurrentCrc { get; private set; }
|
||||
public byte[] CurrentCrc { get; private set; }
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
|
||||
305
src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs
Normal file
305
src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
namespace SharpCompress.Compressors.Rar;
|
||||
|
||||
internal class RarBLAKE2spStream : RarStream
|
||||
{
|
||||
private readonly MultiVolumeReadOnlyStream readStream;
|
||||
private readonly bool disableCRCCheck;
|
||||
|
||||
const uint BLAKE2S_NUM_ROUNDS = 10;
|
||||
const uint BLAKE2S_FINAL_FLAG = (~(uint)0);
|
||||
const int BLAKE2S_BLOCK_SIZE = 64;
|
||||
const int BLAKE2S_DIGEST_SIZE = 32;
|
||||
const int BLAKE2SP_PARALLEL_DEGREE = 8;
|
||||
const uint BLAKE2S_INIT_IV_SIZE = 8;
|
||||
|
||||
static readonly UInt32[] k_BLAKE2S_IV =
|
||||
{
|
||||
0x6A09E667U,
|
||||
0xBB67AE85U,
|
||||
0x3C6EF372U,
|
||||
0xA54FF53AU,
|
||||
0x510E527FU,
|
||||
0x9B05688CU,
|
||||
0x1F83D9ABU,
|
||||
0x5BE0CD19U
|
||||
};
|
||||
|
||||
static readonly byte[][] k_BLAKE2S_Sigma =
|
||||
{
|
||||
new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
|
||||
new byte[] { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
|
||||
new byte[] { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
|
||||
new byte[] { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
|
||||
new byte[] { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
|
||||
new byte[] { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
|
||||
new byte[] { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
|
||||
new byte[] { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
|
||||
new byte[] { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
|
||||
new byte[] { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
|
||||
};
|
||||
|
||||
internal class BLAKE2S
|
||||
{
|
||||
internal UInt32[] h;
|
||||
internal UInt32[] t;
|
||||
internal UInt32[] f;
|
||||
internal byte[] b;
|
||||
internal int bufferPosition;
|
||||
internal UInt32 lastNodeFlag;
|
||||
UInt32[] dummy;
|
||||
|
||||
public BLAKE2S()
|
||||
{
|
||||
h = new uint[BLAKE2S_INIT_IV_SIZE];
|
||||
t = new uint[2];
|
||||
f = new uint[2];
|
||||
b = new byte[BLAKE2S_BLOCK_SIZE];
|
||||
dummy = new uint[2];
|
||||
}
|
||||
};
|
||||
|
||||
internal class BLAKE2SP
|
||||
{
|
||||
internal BLAKE2S[] S;
|
||||
internal int bufferPosition;
|
||||
|
||||
public BLAKE2SP()
|
||||
{
|
||||
S = new BLAKE2S[BLAKE2SP_PARALLEL_DEGREE];
|
||||
for (int i = 0; i < S.Length; i++)
|
||||
{
|
||||
S[i] = new BLAKE2S();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BLAKE2SP _blake2sp;
|
||||
|
||||
byte[] _hash = { };
|
||||
|
||||
public RarBLAKE2spStream(
|
||||
IRarUnpack unpack,
|
||||
FileHeader fileHeader,
|
||||
MultiVolumeReadOnlyStream readStream
|
||||
)
|
||||
: base(unpack, fileHeader, readStream)
|
||||
{
|
||||
this.readStream = readStream;
|
||||
disableCRCCheck = fileHeader.IsEncrypted;
|
||||
_hash = fileHeader.FileCrc;
|
||||
_blake2sp = new BLAKE2SP();
|
||||
ResetCrc();
|
||||
}
|
||||
|
||||
public byte[] GetCrc() => _hash;
|
||||
|
||||
internal void ResetCrc(BLAKE2S hash)
|
||||
{
|
||||
for (UInt32 j = 0; j < BLAKE2S_INIT_IV_SIZE; j++)
|
||||
{
|
||||
hash.h[j] = k_BLAKE2S_IV[j];
|
||||
}
|
||||
hash.t[0] = 0;
|
||||
hash.t[1] = 0;
|
||||
hash.f[0] = 0;
|
||||
hash.f[1] = 0;
|
||||
hash.bufferPosition = 0;
|
||||
hash.lastNodeFlag = 0;
|
||||
}
|
||||
|
||||
internal void G(
|
||||
ref UInt32[] m,
|
||||
ref byte[] sigma,
|
||||
int i,
|
||||
ref UInt32 a,
|
||||
ref UInt32 b,
|
||||
ref UInt32 c,
|
||||
ref UInt32 d
|
||||
)
|
||||
{
|
||||
a += b + m[sigma[2 * i]];
|
||||
d ^= a;
|
||||
d = (d >> 16) | (d << 16);
|
||||
c += d;
|
||||
b ^= c;
|
||||
b = (b >> 12) | (b << 20);
|
||||
|
||||
a += b + m[sigma[2 * i + 1]];
|
||||
d ^= a;
|
||||
d = (d >> 8) | (d << 24);
|
||||
c += d;
|
||||
b ^= c;
|
||||
b = (b >> 7) | (b << 25);
|
||||
}
|
||||
|
||||
internal void Compress(BLAKE2S hash)
|
||||
{
|
||||
UInt32[] m = new UInt32[16];
|
||||
UInt32[] v = new UInt32[16];
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
m[i] = BitConverter.ToUInt32(hash.b, i * 4);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
v[i] = hash.h[i];
|
||||
}
|
||||
|
||||
v[8] = k_BLAKE2S_IV[0];
|
||||
v[9] = k_BLAKE2S_IV[1];
|
||||
v[10] = k_BLAKE2S_IV[2];
|
||||
v[11] = k_BLAKE2S_IV[3];
|
||||
|
||||
v[12] = hash.t[0] ^ k_BLAKE2S_IV[4];
|
||||
v[13] = hash.t[1] ^ k_BLAKE2S_IV[5];
|
||||
v[14] = hash.f[0] ^ k_BLAKE2S_IV[6];
|
||||
v[15] = hash.f[1] ^ k_BLAKE2S_IV[7];
|
||||
|
||||
for (int r = 0; r < BLAKE2S_NUM_ROUNDS; r++)
|
||||
{
|
||||
ref byte[] sigma = ref k_BLAKE2S_Sigma[r];
|
||||
|
||||
G(ref m, ref sigma, 0, ref v[0], ref v[4], ref v[8], ref v[12]);
|
||||
G(ref m, ref sigma, 1, ref v[1], ref v[5], ref v[9], ref v[13]);
|
||||
G(ref m, ref sigma, 2, ref v[2], ref v[6], ref v[10], ref v[14]);
|
||||
G(ref m, ref sigma, 3, ref v[3], ref v[7], ref v[11], ref v[15]);
|
||||
G(ref m, ref sigma, 4, ref v[0], ref v[5], ref v[10], ref v[15]);
|
||||
G(ref m, ref sigma, 5, ref v[1], ref v[6], ref v[11], ref v[12]);
|
||||
G(ref m, ref sigma, 6, ref v[2], ref v[7], ref v[8], ref v[13]);
|
||||
G(ref m, ref sigma, 7, ref v[3], ref v[4], ref v[9], ref v[14]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
hash.h[i] ^= v[i] ^ v[i + 8];
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update(BLAKE2S hash, ReadOnlySpan<byte> data, int size)
|
||||
{
|
||||
int i = 0;
|
||||
while (size != 0)
|
||||
{
|
||||
var pos = hash.bufferPosition;
|
||||
var reminder = BLAKE2S_BLOCK_SIZE - pos;
|
||||
|
||||
if (size <= reminder)
|
||||
{
|
||||
data.Slice(i, size).CopyTo(new Span<byte>(hash.b, pos, size));
|
||||
hash.bufferPosition += size;
|
||||
return;
|
||||
}
|
||||
data.Slice(i, reminder).CopyTo(new Span<byte>(hash.b, pos, reminder));
|
||||
hash.t[0] += BLAKE2S_BLOCK_SIZE;
|
||||
hash.t[1] += hash.t[0] < BLAKE2S_BLOCK_SIZE ? 1U : 0U;
|
||||
Compress(hash);
|
||||
hash.bufferPosition = 0;
|
||||
i += reminder;
|
||||
size -= reminder;
|
||||
}
|
||||
}
|
||||
|
||||
internal byte[] Final(BLAKE2S hash)
|
||||
{
|
||||
hash.t[0] += (uint)hash.bufferPosition;
|
||||
hash.t[1] += hash.t[0] < hash.bufferPosition ? 1U : 0U;
|
||||
hash.f[0] = BLAKE2S_FINAL_FLAG;
|
||||
hash.f[1] = hash.lastNodeFlag;
|
||||
Array.Clear(hash.b, hash.bufferPosition, BLAKE2S_BLOCK_SIZE - hash.bufferPosition);
|
||||
Compress(hash);
|
||||
|
||||
var mem = new MemoryStream();
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
mem.Write(BitConverter.GetBytes(hash.h[i]), 0, 4);
|
||||
}
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
|
||||
public void ResetCrc()
|
||||
{
|
||||
_blake2sp.bufferPosition = 0;
|
||||
|
||||
for (UInt32 i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
{
|
||||
_blake2sp.S[i].bufferPosition = 0;
|
||||
ResetCrc(_blake2sp.S[i]);
|
||||
_blake2sp.S[i].h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
|
||||
_blake2sp.S[i].h[2] ^= i;
|
||||
_blake2sp.S[i].h[3] ^= (BLAKE2S_DIGEST_SIZE << 24);
|
||||
}
|
||||
|
||||
_blake2sp.S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
}
|
||||
|
||||
internal void Update(BLAKE2SP hash, ReadOnlySpan<byte> data, int size)
|
||||
{
|
||||
int i = 0;
|
||||
var pos = hash.bufferPosition;
|
||||
while (size != 0)
|
||||
{
|
||||
var index = pos / BLAKE2S_BLOCK_SIZE;
|
||||
var reminder = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1));
|
||||
if (reminder > size)
|
||||
{
|
||||
reminder = size;
|
||||
}
|
||||
// Update(hash.S[index], data, size);
|
||||
Update(hash.S[index], data.Slice(i, reminder), reminder);
|
||||
size -= reminder;
|
||||
i += reminder;
|
||||
pos += reminder;
|
||||
pos &= (BLAKE2S_BLOCK_SIZE * (BLAKE2SP_PARALLEL_DEGREE - 1));
|
||||
}
|
||||
hash.bufferPosition = pos;
|
||||
}
|
||||
|
||||
internal byte[] Final(BLAKE2SP hash)
|
||||
{
|
||||
var h = new BLAKE2S();
|
||||
|
||||
ResetCrc(h);
|
||||
h.h[0] ^= (BLAKE2S_DIGEST_SIZE | BLAKE2SP_PARALLEL_DEGREE << 16 | 2 << 24);
|
||||
h.h[3] ^= (1 << 16 | BLAKE2S_DIGEST_SIZE << 24);
|
||||
h.lastNodeFlag = BLAKE2S_FINAL_FLAG;
|
||||
|
||||
for (int i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++)
|
||||
{
|
||||
var digest = Final(_blake2sp.S[i]);
|
||||
Update(h, digest, BLAKE2S_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
return Final(h);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var result = base.Read(buffer, offset, count);
|
||||
if (result != 0)
|
||||
{
|
||||
Update(_blake2sp, new ReadOnlySpan<byte>(buffer, offset, result), result);
|
||||
}
|
||||
else
|
||||
{
|
||||
_hash = Final(_blake2sp);
|
||||
if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
|
||||
@@ -7,6 +8,7 @@ internal class RarCrcStream : RarStream
|
||||
{
|
||||
private readonly MultiVolumeReadOnlyStream readStream;
|
||||
private uint currentCrc;
|
||||
private readonly bool disableCRC;
|
||||
|
||||
public RarCrcStream(
|
||||
IRarUnpack unpack,
|
||||
@@ -16,6 +18,7 @@ internal class RarCrcStream : RarStream
|
||||
: base(unpack, fileHeader, readStream)
|
||||
{
|
||||
this.readStream = readStream;
|
||||
disableCRC = fileHeader.IsEncrypted;
|
||||
ResetCrc();
|
||||
}
|
||||
|
||||
@@ -30,7 +33,11 @@ internal class RarCrcStream : RarStream
|
||||
{
|
||||
currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result);
|
||||
}
|
||||
else if (GetCrc() != readStream.CurrentCrc && count != 0)
|
||||
else if (
|
||||
!disableCRC
|
||||
&& GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0)
|
||||
&& count != 0
|
||||
)
|
||||
{
|
||||
// NOTE: we use the last FileHeader in a multipart volume to check CRC
|
||||
throw new InvalidFormatException("file crc mismatch");
|
||||
|
||||
@@ -1269,10 +1269,9 @@ internal sealed partial class Unpack : BitInput, IRarUnpack
|
||||
if (CurSize < DataSize + RarVM.VM_FIXEDGLOBALSIZE)
|
||||
{
|
||||
// StackFilter->Prg.GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE-CurSize);
|
||||
StackFilter
|
||||
.Program
|
||||
.GlobalData
|
||||
.SetSize(DataSize + RarVM.VM_FIXEDGLOBALSIZE - CurSize);
|
||||
StackFilter.Program.GlobalData.SetSize(
|
||||
DataSize + RarVM.VM_FIXEDGLOBALSIZE - CurSize
|
||||
);
|
||||
}
|
||||
var offset = RarVM.VM_FIXEDGLOBALSIZE;
|
||||
globalData = StackFilter.Program.GlobalData;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable disable
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
@@ -23,7 +23,7 @@ public abstract class ReadOnlyStream : Stream
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
|
||||
32
src/SharpCompress/Crypto/BlockTransformer.cs
Normal file
32
src/SharpCompress/Crypto/BlockTransformer.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SharpCompress.Crypto;
|
||||
|
||||
internal class BlockTransformer : IDisposable
|
||||
{
|
||||
private ICryptoTransform _transform;
|
||||
|
||||
public BlockTransformer(ICryptoTransform transformer)
|
||||
{
|
||||
_transform = transformer;
|
||||
}
|
||||
|
||||
public byte[] ProcessBlock(ReadOnlySpan<byte> cipherText)
|
||||
{
|
||||
var decryptedBytes = new byte[cipherText.Length];
|
||||
var bytes = _transform.TransformBlock(
|
||||
cipherText.ToArray(),
|
||||
0,
|
||||
cipherText.Length,
|
||||
decryptedBytes,
|
||||
0
|
||||
);
|
||||
|
||||
return decryptedBytes;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.IO;
|
||||
@@ -26,7 +26,7 @@ internal class BufferedSubStream : NonDisposingStream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => BytesLeftToRead;
|
||||
|
||||
|
||||
@@ -45,13 +45,13 @@ public class DataDescriptorStream : Stream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => _stream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _stream.Position;
|
||||
get => _stream.Position - _start;
|
||||
set => _stream.Position = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ internal class ReadOnlySubStream : NonDisposingStream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public class RewindableStream : Stream
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => stream.Length;
|
||||
|
||||
|
||||
@@ -72,6 +72,14 @@ public abstract class RarReader : AbstractReader<RarReaderEntry, RarVolume>
|
||||
{
|
||||
return CreateEntryStream(new RarCrcStream(UnpackV1.Value, Entry.FileHeader, stream));
|
||||
}
|
||||
|
||||
if (Entry.FileHeader.FileCrc.Length > 5)
|
||||
{
|
||||
return CreateEntryStream(
|
||||
new RarBLAKE2spStream(UnpackV2017.Value, Entry.FileHeader, stream)
|
||||
);
|
||||
}
|
||||
|
||||
return CreateEntryStream(new RarCrcStream(UnpackV2017.Value, Entry.FileHeader, stream));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>SharpCompress - Pure C# Decompression/Compression</AssemblyTitle>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.34.2</VersionPrefix>
|
||||
<AssemblyVersion>0.34.2</AssemblyVersion>
|
||||
<FileVersion>0.34.2</FileVersion>
|
||||
<VersionPrefix>0.36.0</VersionPrefix>
|
||||
<AssemblyVersion>0.36.0</AssemblyVersion>
|
||||
<FileVersion>0.36.0</FileVersion>
|
||||
<Authors>Adam Hathcock</Authors>
|
||||
<TargetFrameworks>net462;netstandard2.0;netstandard2.1;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks>net462;netstandard2.0;netstandard2.1;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<AssemblyName>SharpCompress</AssemblyName>
|
||||
<AssemblyOriginatorKeyFile>../../SharpCompress.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
@@ -27,18 +27,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="ZstdSharp.Port" Version="0.7.2" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="ZstdSharp.Port" Version="0.7.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(VersionlessImplicitFrameworkDefine)' == 'NETFRAMEWORK' ">
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -10,9 +10,7 @@ public static class WriterFactory
|
||||
public static IWriter Open(Stream stream, ArchiveType archiveType, WriterOptions writerOptions)
|
||||
{
|
||||
var factory = Factories
|
||||
.Factory
|
||||
.Factories
|
||||
.OfType<IWriterFactory>()
|
||||
.Factory.Factories.OfType<IWriterFactory>()
|
||||
.FirstOrDefault(item => item.KnownArchiveType == archiveType);
|
||||
|
||||
if (factory != null)
|
||||
|
||||
@@ -304,7 +304,10 @@ public class ZipWriter : AbstractWriter
|
||||
|
||||
// Write normal end of central directory record
|
||||
OutputStream.Write(stackalloc byte[] { 80, 75, 5, 6, 0, 0, 0, 0 });
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(intBuf, (ushort)entries.Count);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(
|
||||
intBuf,
|
||||
(ushort)(entries.Count < 0xFFFF ? entries.Count : 0xFFFF)
|
||||
);
|
||||
OutputStream.Write(intBuf.Slice(0, 2));
|
||||
OutputStream.Write(intBuf.Slice(0, 2));
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(intBuf, sizevalue);
|
||||
|
||||
@@ -258,6 +258,29 @@ public class ArchiveTests : ReaderTests
|
||||
VerifyFiles();
|
||||
}
|
||||
|
||||
protected void ArchiveFileSkip(
|
||||
string testArchive,
|
||||
string fileOrder,
|
||||
ReaderOptions? readerOptions = null
|
||||
)
|
||||
{
|
||||
#if !NETFRAMEWORK
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
fileOrder = fileOrder.Replace('\\', '/');
|
||||
}
|
||||
#endif
|
||||
var expected = new Stack<string>(fileOrder.Split(' '));
|
||||
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
|
||||
using (var archive = ArchiveFactory.Open(testArchive, readerOptions))
|
||||
{
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
Assert.Equal(expected.Pop(), entry.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate the ExtractionOptions.PreserveFileTime and ExtractionOptions.PreserveAttributes extract options
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Test.Mocks;
|
||||
@@ -27,7 +27,7 @@ public class FlushOnDisposeStream : Stream, IDisposable
|
||||
set => inner.Position = value;
|
||||
}
|
||||
|
||||
public override void Flush() => throw new NotImplementedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
inner.Read(buffer, offset, count);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Test.Mocks;
|
||||
@@ -29,7 +29,7 @@ public class ForwardOnlyStream : Stream
|
||||
public override bool CanSeek => false;
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush() => throw new NotSupportedException();
|
||||
public override void Flush() { }
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
|
||||
@@ -22,11 +22,16 @@ public class RarArchiveTests : ArchiveTests
|
||||
() => ReadRarPassword("Rar.encrypted_filesAndHeader.rar", null)
|
||||
);
|
||||
|
||||
/*[Fact]
|
||||
public void Rar5_EncryptedFileAndHeader_Archive()
|
||||
{
|
||||
[Fact]
|
||||
public void Rar5_EncryptedFileAndHeader_Archive() =>
|
||||
ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test");
|
||||
}*/
|
||||
|
||||
[Fact]
|
||||
public void Rar5_EncryptedFileAndHeader_Archive_Err() =>
|
||||
Assert.Throws(
|
||||
typeof(CryptographicException),
|
||||
() => ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "failed")
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest() =>
|
||||
@@ -39,20 +44,23 @@ public class RarArchiveTests : ArchiveTests
|
||||
public void Rar_EncryptedFileOnly_Archive() =>
|
||||
ReadRarPassword("Rar.encrypted_filesOnly.rar", "test");
|
||||
|
||||
/*[Fact]
|
||||
public void Rar5_EncryptedFileOnly_Archive()
|
||||
{
|
||||
[Fact]
|
||||
public void Rar_EncryptedFileOnly_Archive_Err() =>
|
||||
Assert.Throws(
|
||||
typeof(CryptographicException),
|
||||
() => ReadRarPassword("Rar5.encrypted_filesOnly.rar", "failed")
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_EncryptedFileOnly_Archive() =>
|
||||
ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test");
|
||||
}*/
|
||||
|
||||
[Fact]
|
||||
public void Rar_Encrypted_Archive() => ReadRarPassword("Rar.Encrypted.rar", "test");
|
||||
|
||||
/*[Fact]
|
||||
public void Rar5_Encrypted_Archive()
|
||||
{
|
||||
[Fact]
|
||||
public void Rar5_Encrypted_Archive() =>
|
||||
ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test");
|
||||
}*/
|
||||
|
||||
private void ReadRarPassword(string testArchive, string? password)
|
||||
{
|
||||
@@ -600,4 +608,37 @@ public class RarArchiveTests : ArchiveTests
|
||||
Assert.True(archive.IsMultipartVolume());
|
||||
Assert.False(archive.IsFirstVolume());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rar5_CRC_Blake2_Archive() => ArchiveFileRead("Rar5.crc_blake2.rar");
|
||||
|
||||
[Fact]
|
||||
void Rar_Iterate_Archive() =>
|
||||
ArchiveFileSkip("Rar.rar", "Failure jpg exe Empty jpg\\test.jpg exe\\test.exe тест.txt");
|
||||
|
||||
[Fact]
|
||||
public void Rar2_Iterate_Archive() =>
|
||||
ArchiveFileSkip("Rar2.rar", "Failure Empty тест.txt jpg\\test.jpg exe\\test.exe jpg exe");
|
||||
|
||||
[Fact]
|
||||
public void Rar4_Iterate_Archive() =>
|
||||
ArchiveFileSkip("Rar4.rar", "Failure Empty jpg exe тест.txt jpg\\test.jpg exe\\test.exe");
|
||||
|
||||
[Fact]
|
||||
public void Rar5_Iterate_Archive() =>
|
||||
ArchiveFileSkip("Rar5.rar", "Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe");
|
||||
|
||||
[Fact]
|
||||
public void Rar_Encrypted_Iterate_Archive() =>
|
||||
ArchiveFileSkip(
|
||||
"Rar.encrypted_filesOnly.rar",
|
||||
"Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe"
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_Encrypted_Iterate_Archive() =>
|
||||
ArchiveFileSkip(
|
||||
"Rar5.encrypted_filesOnly.rar",
|
||||
"Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -171,33 +171,28 @@ public class RarReaderTests : ReaderTests
|
||||
[Fact]
|
||||
public void Rar5_Reader() => Read("Rar5.rar", CompressionType.Rar);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_CRC_Blake2_Reader() => Read("Rar5.crc_blake2.rar", CompressionType.Rar);
|
||||
|
||||
[Fact]
|
||||
public void Rar_EncryptedFileAndHeader_Reader() =>
|
||||
ReadRar("Rar.encrypted_filesAndHeader.rar", "test");
|
||||
|
||||
/*[Fact]
|
||||
public void Rar5_EncryptedFileAndHeader_Reader()
|
||||
{
|
||||
[Fact]
|
||||
public void Rar5_EncryptedFileAndHeader_Reader() =>
|
||||
ReadRar("Rar5.encrypted_filesAndHeader.rar", "test");
|
||||
}*/
|
||||
|
||||
[Fact]
|
||||
public void Rar_EncryptedFileOnly_Reader() => ReadRar("Rar.encrypted_filesOnly.rar", "test");
|
||||
|
||||
/*[Fact]
|
||||
public void Rar5_EncryptedFileOnly_Reader()
|
||||
{
|
||||
ReadRar("Rar5.encrypted_filesOnly.rar", "test");
|
||||
}*/
|
||||
[Fact]
|
||||
public void Rar5_EncryptedFileOnly_Reader() => ReadRar("Rar5.encrypted_filesOnly.rar", "test");
|
||||
|
||||
[Fact]
|
||||
public void Rar_Encrypted_Reader() => ReadRar("Rar.Encrypted.rar", "test");
|
||||
|
||||
/*[Fact]
|
||||
public void Rar5_Encrypted_Reader()
|
||||
{
|
||||
ReadRar("Rar5.encrypted_filesOnly.rar", "test");
|
||||
}*/
|
||||
[Fact]
|
||||
public void Rar5_Encrypted_Reader() => ReadRar("Rar5.encrypted_filesOnly.rar", "test");
|
||||
|
||||
private void ReadRar(string testArchive, string password) =>
|
||||
Read(testArchive, CompressionType.Rar, new ReaderOptions { Password = password });
|
||||
@@ -284,6 +279,12 @@ public class RarReaderTests : ReaderTests
|
||||
[Fact]
|
||||
public void Rar_Solid_Reader() => Read("Rar.solid.rar", CompressionType.Rar);
|
||||
|
||||
[Fact]
|
||||
public void Rar_Comment_Reader() => Read("Rar.comment.rar", CompressionType.Rar);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_Comment_Reader() => Read("Rar5.comment.rar", CompressionType.Rar);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_Solid_Reader() => Read("Rar5.solid.rar", CompressionType.Rar);
|
||||
|
||||
@@ -377,4 +378,55 @@ public class RarReaderTests : ReaderTests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rar_Iterate_Reader() =>
|
||||
Iterate(
|
||||
"Rar.rar",
|
||||
"Failure jpg exe Empty jpg\\test.jpg exe\\test.exe тест.txt",
|
||||
CompressionType.Rar
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar2_Iterate_Archive() =>
|
||||
Iterate(
|
||||
"Rar2.rar",
|
||||
"Failure Empty тест.txt jpg\\test.jpg exe\\test.exe jpg exe",
|
||||
CompressionType.Rar
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar4_Iterate_Archive() =>
|
||||
Iterate(
|
||||
"Rar4.rar",
|
||||
"Failure Empty jpg exe тест.txt jpg\\test.jpg exe\\test.exe",
|
||||
CompressionType.Rar
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_Iterate_Archive() =>
|
||||
Iterate(
|
||||
"Rar5.rar",
|
||||
"Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe",
|
||||
CompressionType.Rar
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar_Encrypted_Iterate_Archive() =>
|
||||
Iterate(
|
||||
"Rar.encrypted_filesOnly.rar",
|
||||
"Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe",
|
||||
CompressionType.Rar
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void Rar5_Encrypted_Iterate_Archive() =>
|
||||
Assert.Throws<CryptographicException>(
|
||||
() =>
|
||||
Iterate(
|
||||
"Rar5.encrypted_filesOnly.rar",
|
||||
"Failure jpg exe Empty тест.txt jpg\\test.jpg exe\\test.exe",
|
||||
CompressionType.Rar
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
@@ -67,4 +69,32 @@ public abstract class ReaderTests : TestBase
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void Iterate(
|
||||
string testArchive,
|
||||
string fileOrder,
|
||||
CompressionType expectedCompression,
|
||||
ReaderOptions? options = null
|
||||
)
|
||||
{
|
||||
#if !NETFRAMEWORK
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
fileOrder = fileOrder.Replace('\\', '/');
|
||||
}
|
||||
#endif
|
||||
var expected = new Stack<string>(fileOrder.Split(' '));
|
||||
|
||||
testArchive = Path.Combine(TEST_ARCHIVES_PATH, testArchive);
|
||||
using var file = File.OpenRead(testArchive);
|
||||
using var forward = new ForwardOnlyStream(file);
|
||||
using (var reader = ReaderFactory.Open(forward, options))
|
||||
{
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
Assert.Equal(expectedCompression, reader.Entry.CompressionType);
|
||||
Assert.Equal(expected.Pop(), reader.Entry.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,9 @@ public class SevenZipArchiveTests : ArchiveTests
|
||||
)
|
||||
);
|
||||
|
||||
[Fact]
|
||||
public void SevenZipArchive_EOS_FileRead() => ArchiveFileRead("7Zip.eos.7z");
|
||||
|
||||
[Fact]
|
||||
public void SevenZipArchive_Delta_FileRead() => ArchiveFileRead("7Zip.delta.7z");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net462</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net462</TargetFrameworks>
|
||||
<AssemblyName>SharpCompress.Test</AssemblyName>
|
||||
<PackageId>SharpCompress.Test</PackageId>
|
||||
</PropertyGroup>
|
||||
@@ -8,13 +8,13 @@
|
||||
<ProjectReference Include="..\..\src\SharpCompress\SharpCompress.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.10.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit" Version="2.6.3" />
|
||||
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(VersionlessImplicitFrameworkDefine)' != 'NETFRAMEWORK' ">
|
||||
|
||||
@@ -198,9 +198,9 @@ public class TarArchiveTests : ArchiveTests
|
||||
|
||||
using (var archive = TarArchive.Open(unmodified))
|
||||
{
|
||||
var entry = archive
|
||||
.Entries
|
||||
.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase));
|
||||
var entry = archive.Entries.Single(
|
||||
x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
archive.RemoveEntry(entry);
|
||||
archive.SaveTo(scratchPath, CompressionType.None);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ public class TarReaderTests : ReaderTests
|
||||
[Fact]
|
||||
public void Tar_Xz_Reader() => Read("Tar.tar.xz", CompressionType.Xz);
|
||||
|
||||
[Fact]
|
||||
public void Tar_GZip_OldGnu_Reader() => Read("Tar.oldgnu.tar.gz", CompressionType.GZip);
|
||||
|
||||
[Fact]
|
||||
public void Tar_BZip2_Entry_Stream()
|
||||
{
|
||||
@@ -206,11 +209,9 @@ public class TarReaderTests : ReaderTests
|
||||
[Fact]
|
||||
public void Tar_GZip_With_Symlink_Entries()
|
||||
{
|
||||
var isWindows = System
|
||||
.Runtime
|
||||
.InteropServices
|
||||
.RuntimeInformation
|
||||
.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
|
||||
var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
|
||||
System.Runtime.InteropServices.OSPlatform.Windows
|
||||
);
|
||||
using (
|
||||
Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "TarWithSymlink.tar.gz"))
|
||||
)
|
||||
@@ -253,15 +254,12 @@ public class TarReaderTests : ReaderTests
|
||||
{
|
||||
// need to convert the link to an absolute path for comparison
|
||||
var target = reader.Entry.LinkTarget;
|
||||
var realTarget = System
|
||||
.IO
|
||||
.Path
|
||||
.GetFullPath(
|
||||
System
|
||||
.IO
|
||||
.Path
|
||||
.Combine($"{System.IO.Path.GetDirectoryName(path)}", target)
|
||||
);
|
||||
var realTarget = System.IO.Path.GetFullPath(
|
||||
System.IO.Path.Combine(
|
||||
$"{System.IO.Path.GetDirectoryName(path)}",
|
||||
target
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Equal(realTarget, link.GetContents().ToString());
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ public class TestBase : IDisposable
|
||||
|
||||
public TestBase()
|
||||
{
|
||||
var index = AppDomain
|
||||
.CurrentDomain
|
||||
.BaseDirectory
|
||||
.IndexOf("SharpCompress.Test", StringComparison.OrdinalIgnoreCase);
|
||||
var index = AppDomain.CurrentDomain.BaseDirectory.IndexOf(
|
||||
"SharpCompress.Test",
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
var path = AppDomain.CurrentDomain.BaseDirectory.Substring(0, index);
|
||||
SOLUTION_BASE_PATH = Path.GetDirectoryName(path) ?? throw new ArgumentNullException();
|
||||
|
||||
|
||||
@@ -184,9 +184,9 @@ public class ZipArchiveTests : ArchiveTests
|
||||
|
||||
using (var archive = ZipArchive.Open(unmodified))
|
||||
{
|
||||
var entry = archive
|
||||
.Entries
|
||||
.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase));
|
||||
var entry = archive.Entries.Single(
|
||||
x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
archive.RemoveEntry(entry);
|
||||
|
||||
WriterOptions writerOptions = new ZipWriterOptions(CompressionType.Deflate);
|
||||
@@ -252,9 +252,9 @@ public class ZipArchiveTests : ArchiveTests
|
||||
)
|
||||
);
|
||||
Assert.Null(
|
||||
((IArchive)vfs)
|
||||
.Entries
|
||||
.FirstOrDefault(v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))
|
||||
((IArchive)vfs).Entries.FirstOrDefault(
|
||||
v => v.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -391,14 +391,14 @@ public class ZipArchiveTests : ArchiveTests
|
||||
{
|
||||
archive.AddAllFromDirectory(SCRATCH_FILES_PATH);
|
||||
archive.RemoveEntry(
|
||||
archive
|
||||
.Entries
|
||||
.Single(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))
|
||||
archive.Entries.Single(
|
||||
x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
Assert.Null(
|
||||
archive
|
||||
.Entries
|
||||
.FirstOrDefault(x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))
|
||||
archive.Entries.FirstOrDefault(
|
||||
x => x.Key.EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
);
|
||||
}
|
||||
Directory.Delete(SCRATCH_FILES_PATH, true);
|
||||
|
||||
BIN
tests/TestArchives/Archives/7Zip.eos.7z
Normal file
BIN
tests/TestArchives/Archives/7Zip.eos.7z
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/Rar.comment.rar
Normal file
BIN
tests/TestArchives/Archives/Rar.comment.rar
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/Rar5.comment.rar
Normal file
BIN
tests/TestArchives/Archives/Rar5.comment.rar
Normal file
Binary file not shown.
BIN
tests/TestArchives/Archives/Tar.oldgnu.tar.gz
Normal file
BIN
tests/TestArchives/Archives/Tar.oldgnu.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user