mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-25 00:15:15 +00:00
rar5: change rar MarkHeader detection logic to support rar5
This commit is contained in:
@@ -134,9 +134,8 @@ namespace SharpCompress.Archives.Rar
|
||||
{
|
||||
try
|
||||
{
|
||||
var headerFactory = new RarHeaderFactory(StreamingMode.Seekable, options ?? new ReaderOptions());
|
||||
var markHeader = headerFactory.ReadHeaders(stream).FirstOrDefault() as MarkHeader;
|
||||
return markHeader != null && markHeader.IsValid();
|
||||
MarkHeader.Read(stream, true, false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace SharpCompress.Archives.Rar
|
||||
return null;
|
||||
}
|
||||
bool oldNumbering = !ah.ArchiveHeaderFlags.HasFlag(ArchiveFlags.NEWNUMBERING)
|
||||
|| currentFilePart.MarkHeader.OldFormat;
|
||||
|| currentFilePart.MarkHeader.OldNumberingFormat;
|
||||
if (oldNumbering)
|
||||
{
|
||||
return FindNextFileWithOldNumbering(currentFilePart.FileInfo);
|
||||
|
||||
@@ -1,26 +1,92 @@
|
||||
using SharpCompress.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Rar.Headers
|
||||
{
|
||||
internal class MarkHeader : RarHeader
|
||||
internal class MarkHeader : IRarHeader
|
||||
{
|
||||
protected override void ReadFromReader(MarkingBinaryReader reader)
|
||||
{
|
||||
private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136
|
||||
|
||||
internal bool OldNumberingFormat { get; private set; }
|
||||
public bool IsRar5 { get; private set; }
|
||||
|
||||
private MarkHeader(bool isRar5) {
|
||||
IsRar5 = isRar5;
|
||||
}
|
||||
|
||||
internal bool IsValid()
|
||||
{
|
||||
// Rar old signature: 52 45 7E 5E (not supported)
|
||||
|
||||
// Rar4 signature: 52 61 72 21 1A 07 00
|
||||
return HeadCRC == 0x6152 &&
|
||||
HeaderType == HeaderType.MarkHeader &&
|
||||
Flags == 0x1A21 &&
|
||||
HeaderSize == 0x07;
|
||||
|
||||
// Rar5 signature: 52 61 72 21 1A 07 01 00 (not supported yet)
|
||||
public HeaderType HeaderType {
|
||||
get { return HeaderType.MarkHeader; }
|
||||
}
|
||||
|
||||
internal bool OldFormat { get; private set; }
|
||||
private static byte GetByte(Stream stream) {
|
||||
var b = stream.ReadByte();
|
||||
if (b != -1) {
|
||||
return (byte)b;
|
||||
}
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
public static MarkHeader Read(Stream stream, bool leaveStreamOpen, bool lookForHeader) {
|
||||
int maxScanIndex = lookForHeader ? MAX_SFX_SIZE : 0;
|
||||
try
|
||||
{
|
||||
int 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) {
|
||||
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 {
|
||||
continue;
|
||||
}
|
||||
} 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.");
|
||||
//return new MarkHeader();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
b = GetByte(stream); start++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// unreachable
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!leaveStreamOpen)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
throw new InvalidFormatException("Error trying to read rar signature.", e);
|
||||
}
|
||||
|
||||
throw new InvalidFormatException("Rar signature not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Rar.Headers
|
||||
{
|
||||
internal class RarHeader
|
||||
internal class RarHeader : IRarHeader
|
||||
{
|
||||
internal const short BaseBlockSize = 7;
|
||||
internal const short LONG_BLOCK = -0x8000;
|
||||
@@ -25,13 +24,11 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
{
|
||||
try
|
||||
{
|
||||
RarHeader header = new RarHeader();
|
||||
|
||||
var header = new RarHeader();
|
||||
header.ArchiveEncoding = archiveEncoding;
|
||||
reader.Mark();
|
||||
header.ReadStartFromReader(reader);
|
||||
header.ReadBytes += reader.CurrentReadByteCount;
|
||||
|
||||
return header;
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
@@ -44,7 +41,7 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
{
|
||||
HeadCRC = reader.ReadUInt16();
|
||||
reader.ResetCrc();
|
||||
HeaderType = (HeaderType)(reader.ReadByte() & 0xff);
|
||||
HeaderType = (HeaderType)reader.ReadByte();
|
||||
Flags = reader.ReadInt16();
|
||||
HeaderSize = reader.ReadInt16();
|
||||
if (FlagUtility.HasFlag(Flags, LONG_BLOCK))
|
||||
@@ -69,7 +66,6 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
header.ReadBytes += reader.CurrentReadByteCount;
|
||||
|
||||
int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes;
|
||||
|
||||
if (headerSizeDiff > 0)
|
||||
{
|
||||
reader.ReadBytes(headerSizeDiff);
|
||||
@@ -102,7 +98,7 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
|
||||
protected ushort HeadCRC { get; private set; }
|
||||
|
||||
internal HeaderType HeaderType { get; private set; }
|
||||
public HeaderType HeaderType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Untyped flags. These should be typed when Promoting to another header
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
{
|
||||
internal class RarHeaderFactory
|
||||
{
|
||||
private const int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136
|
||||
private bool isRar5;
|
||||
|
||||
internal RarHeaderFactory(StreamingMode mode, ReaderOptions options)
|
||||
{
|
||||
@@ -20,12 +20,11 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
internal StreamingMode StreamingMode { get; }
|
||||
internal bool IsEncrypted { get; private set; }
|
||||
|
||||
internal IEnumerable<RarHeader> ReadHeaders(Stream stream)
|
||||
internal IEnumerable<IRarHeader> ReadHeaders(Stream stream)
|
||||
{
|
||||
if (Options.LookForHeader)
|
||||
{
|
||||
stream = CheckSFX(stream);
|
||||
}
|
||||
var markHeader = MarkHeader.Read(stream, Options.LeaveStreamOpen, Options.LookForHeader);
|
||||
this.isRar5 = markHeader.IsRar5;
|
||||
yield return markHeader;
|
||||
|
||||
RarHeader header;
|
||||
while ((header = ReadNextHeader(stream)) != null)
|
||||
@@ -38,102 +37,24 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
}
|
||||
}
|
||||
|
||||
private Stream CheckSFX(Stream stream)
|
||||
{
|
||||
RewindableStream rewindableStream = GetRewindableStream(stream);
|
||||
stream = rewindableStream;
|
||||
BinaryReader reader = new BinaryReader(rewindableStream);
|
||||
try
|
||||
{
|
||||
int count = 0;
|
||||
while (true)
|
||||
{
|
||||
byte firstByte = reader.ReadByte();
|
||||
if (firstByte == 0x52)
|
||||
{
|
||||
MemoryStream buffer = new MemoryStream();
|
||||
byte[] nextThreeBytes = reader.ReadBytes(3);
|
||||
if ((nextThreeBytes[0] == 0x45)
|
||||
&& (nextThreeBytes[1] == 0x7E)
|
||||
&& (nextThreeBytes[2] == 0x5E))
|
||||
{
|
||||
//old format and isvalid
|
||||
buffer.WriteByte(0x52);
|
||||
buffer.Write(nextThreeBytes, 0, 3);
|
||||
rewindableStream.Rewind(buffer);
|
||||
break;
|
||||
}
|
||||
byte[] secondThreeBytes = reader.ReadBytes(3);
|
||||
if ((nextThreeBytes[0] == 0x61)
|
||||
&& (nextThreeBytes[1] == 0x72)
|
||||
&& (nextThreeBytes[2] == 0x21)
|
||||
&& (secondThreeBytes[0] == 0x1A)
|
||||
&& (secondThreeBytes[1] == 0x07)
|
||||
&& (secondThreeBytes[2] == 0x00))
|
||||
{
|
||||
//new format and isvalid
|
||||
buffer.WriteByte(0x52);
|
||||
buffer.Write(nextThreeBytes, 0, 3);
|
||||
buffer.Write(secondThreeBytes, 0, 3);
|
||||
rewindableStream.Rewind(buffer);
|
||||
break;
|
||||
}
|
||||
buffer.Write(nextThreeBytes, 0, 3);
|
||||
buffer.Write(secondThreeBytes, 0, 3);
|
||||
rewindableStream.Rewind(buffer);
|
||||
}
|
||||
if (count > MAX_SFX_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!Options.LeaveStreamOpen)
|
||||
{
|
||||
#if NET35
|
||||
reader.Close();
|
||||
#else
|
||||
reader.Dispose();
|
||||
#endif
|
||||
}
|
||||
throw new InvalidFormatException("Error trying to read rar signature.", e);
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
private RewindableStream GetRewindableStream(Stream stream)
|
||||
{
|
||||
RewindableStream rewindableStream = stream as RewindableStream;
|
||||
if (rewindableStream == null)
|
||||
{
|
||||
rewindableStream = new RewindableStream(stream);
|
||||
}
|
||||
return rewindableStream;
|
||||
}
|
||||
|
||||
private RarHeader ReadNextHeader(Stream stream)
|
||||
{
|
||||
RarCrcBinaryReader reader;
|
||||
if (!IsEncrypted) {
|
||||
reader = new RarCrcBinaryReader(stream);
|
||||
} else {
|
||||
#if !NO_CRYPTO
|
||||
var reader = new RarCryptoBinaryReader(stream, Options.Password);
|
||||
|
||||
if (IsEncrypted)
|
||||
{
|
||||
if (Options.Password == null)
|
||||
{
|
||||
throw new CryptographicException("Encrypted Rar archive has no password specified.");
|
||||
}
|
||||
reader.SkipQueue();
|
||||
byte[] salt = reader.ReadBytes(8);
|
||||
reader.InitializeAes(salt);
|
||||
}
|
||||
reader = new RarCryptoBinaryReader(stream, Options.Password);
|
||||
#else
|
||||
var reader = new RarCrcBinaryReader(stream);
|
||||
|
||||
throw new CryptographicException("Rar encryption unsupported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
RarHeader header = RarHeader.Create(reader, Options.ArchiveEncoding);
|
||||
var header = RarHeader.Create(reader, Options.ArchiveEncoding);
|
||||
if (header == null)
|
||||
{
|
||||
return null;
|
||||
@@ -146,10 +67,6 @@ namespace SharpCompress.Common.Rar.Headers
|
||||
IsEncrypted = ah.HasPassword;
|
||||
return ah;
|
||||
}
|
||||
case HeaderType.MarkHeader:
|
||||
{
|
||||
return header.PromoteHeader<MarkHeader>(reader);
|
||||
}
|
||||
|
||||
case HeaderType.ProtectHeader:
|
||||
{
|
||||
|
||||
@@ -30,6 +30,13 @@ namespace SharpCompress.Common.Rar {
|
||||
return base.ReadBytes(count);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#if !NO_CRYPTO
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Rar
|
||||
{
|
||||
@@ -18,6 +17,10 @@ namespace SharpCompress.Common.Rar
|
||||
: base(stream)
|
||||
{
|
||||
this.password = password;
|
||||
// coderb: not sure why this was being done at this logical point
|
||||
//SkipQueue();
|
||||
byte[] salt = ReadBytes(8);
|
||||
InitializeAes(salt);
|
||||
}
|
||||
|
||||
// track read count ourselves rather than using the underlying stream since we buffer
|
||||
@@ -47,6 +50,36 @@ namespace SharpCompress.Common.Rar
|
||||
rijndael = RarRijndael.InitializeFrom(password, salt);
|
||||
}
|
||||
|
||||
public override byte ReadByte() {
|
||||
if (UseEncryption)
|
||||
{
|
||||
return ReadAndDecryptBytes(1)[0];
|
||||
}
|
||||
this.readCount++;
|
||||
return base.ReadByte();
|
||||
}
|
||||
//x
|
||||
// public override byte ReadByte() {
|
||||
// if (UseEncryption)
|
||||
// {
|
||||
// int queueSize = this.data.Count;
|
||||
// if (queueSize < 1)
|
||||
// {
|
||||
// byte[] cipherText = ReadBytesNoCrc(16);
|
||||
// var readBytes = this.rijndael.ProcessBlock(cipherText);
|
||||
// foreach (var readByte in readBytes)
|
||||
// this.data.Enqueue(readByte);
|
||||
// }
|
||||
//
|
||||
// var b = this.data.Dequeue();
|
||||
// UpdateCrc(b);
|
||||
// this.readCount++;
|
||||
// return b;
|
||||
// }
|
||||
// this.readCount++;
|
||||
// return base.ReadByte();
|
||||
// }
|
||||
|
||||
public override byte[] ReadBytes(int count)
|
||||
{
|
||||
if (UseEncryption)
|
||||
@@ -68,13 +101,11 @@ namespace SharpCompress.Common.Rar
|
||||
for (int i = 0; i < alignedSize / 16; i++)
|
||||
{
|
||||
//long ax = System.currentTimeMillis();
|
||||
byte[] cipherText = base.ReadBytesNoCrc(16);
|
||||
byte[] cipherText = ReadBytesNoCrc(16);
|
||||
var readBytes = rijndael.ProcessBlock(cipherText);
|
||||
foreach (var readByte in readBytes)
|
||||
data.Enqueue(readByte);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var decryptedBytes = new byte[count];
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace SharpCompress.Common.Rar
|
||||
internal IEnumerable<RarFilePart> GetVolumeFileParts()
|
||||
{
|
||||
MarkHeader previousMarkHeader = null;
|
||||
foreach (RarHeader header in headerFactory.ReadHeaders(Stream))
|
||||
foreach (var header in headerFactory.ReadHeaders(Stream))
|
||||
{
|
||||
switch (header.HeaderType)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Converters;
|
||||
|
||||
namespace SharpCompress.IO
|
||||
@@ -36,12 +35,22 @@ namespace SharpCompress.IO
|
||||
|
||||
public override bool ReadBoolean()
|
||||
{
|
||||
return ReadBytes(1).Single() != 0;
|
||||
return ReadByte() != 0;
|
||||
}
|
||||
|
||||
// NOTE: there is a somewhat fragile dependency on the internals of this class
|
||||
// with RarCrcBinaryReader and RarCryptoBinaryReader.
|
||||
//
|
||||
// RarCrcBinaryReader/RarCryptoBinaryReader need to override any specific methods
|
||||
// that call directly to the base BinaryReader and do not delegate to other methods
|
||||
// in this class so that it can track the each byte being read.
|
||||
//
|
||||
// if altering this class in a way that changes the implementation be sure to
|
||||
// update RarCrcBinaryReader/RarCryptoBinaryReader.
|
||||
public override byte ReadByte()
|
||||
{
|
||||
return ReadBytes(1).Single();
|
||||
CurrentReadByteCount++;
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override byte[] ReadBytes(int count)
|
||||
@@ -121,5 +130,31 @@ namespace SharpCompress.IO
|
||||
{
|
||||
return DataConverter.LittleEndian.GetUInt64(ReadBytes(8), 0);
|
||||
}
|
||||
|
||||
// RAR5 style variable length encoded value
|
||||
// maximum value of 0xffffffffffffffff (64 bits)
|
||||
// implies max 10 bytes consumed
|
||||
//
|
||||
// Variable length integer. Can include one or more bytes, where lower 7 bits of every byte contain integer data
|
||||
// and highest bit in every byte is the continuation flag. If highest bit is 0, this is the last byte in sequence.
|
||||
// So first byte contains 7 least significant bits of integer and continuation flag. Second byte, if present,
|
||||
// contains next 7 bits and so on.
|
||||
public ulong ReadRarVInt() {
|
||||
int shift = 0;
|
||||
ulong result = 0;
|
||||
do {
|
||||
ulong n = ReadByte();
|
||||
result |= n << shift;
|
||||
shift += 7;
|
||||
if ((n & 0x80) == 0) {
|
||||
return result;
|
||||
}
|
||||
// note: we're actually only allowing a max high bit of 2^56
|
||||
// to avoid an extra complex check for shift overflow due to the
|
||||
// 10th byte having high bits set
|
||||
} while (shift < 63);
|
||||
|
||||
throw new FormatException("malformed vint");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,12 @@ namespace SharpCompress.Test.Rar
|
||||
public void Rar_EncryptedFileAndHeader_Archive()
|
||||
{
|
||||
ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test");
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rar_EncryptedFileOnly_Archive()
|
||||
{
|
||||
ReadRarPassword("Rar.encrypted_filesOnly.rar", "test");
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -209,6 +207,12 @@ namespace SharpCompress.Test.Rar
|
||||
ArchiveFileRead("Rar.none.rar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rar5NoneArchiveFileRead()
|
||||
{
|
||||
ArchiveFileRead("Rar5.none.rar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rar_ArchiveFileRead()
|
||||
{
|
||||
|
||||
@@ -26,11 +26,7 @@ namespace SharpCompress.Test.Rar
|
||||
[Fact]
|
||||
public void ReadHeaders_RecognizeEncryptedFlag()
|
||||
{
|
||||
|
||||
ReadEncryptedFlag("Rar.encrypted_filesAndHeader.rar", true);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void ReadEncryptedFlag(string testArchive, bool isEncrypted)
|
||||
|
||||
Reference in New Issue
Block a user