rar5: change rar MarkHeader detection logic to support rar5

This commit is contained in:
coderb
2017-12-16 17:35:07 -05:00
parent d334b54846
commit c346a4ca94
11 changed files with 189 additions and 138 deletions

View File

@@ -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
{

View File

@@ -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);

View File

@@ -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");
}
}
}

View File

@@ -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

View File

@@ -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:
{

View File

@@ -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);

View File

@@ -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];

View File

@@ -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)
{

View File

@@ -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");
}
}
}

View File

@@ -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()
{

View File

@@ -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)