diff --git a/src/SharpCompress/Archives/Rar/RarArchive.cs b/src/SharpCompress/Archives/Rar/RarArchive.cs index eecd27b6..adb9117c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchive.cs +++ b/src/SharpCompress/Archives/Rar/RarArchive.cs @@ -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 { diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index 4bf39967..7270a4bd 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -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); diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index f5c61274..364202fc 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -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"); + } } } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 30753aee..55434c94 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -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; } /// /// Untyped flags. These should be typed when Promoting to another header diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index d05840ee..66d4c024 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -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 ReadHeaders(Stream stream) + internal IEnumerable 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(reader); - } case HeaderType.ProtectHeader: { diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index 6df617b5..aa8c73fd 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -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); diff --git a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs index 9635375b..4a14a375 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs @@ -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]; diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 89fee688..4bed1696 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -30,7 +30,7 @@ namespace SharpCompress.Common.Rar internal IEnumerable GetVolumeFileParts() { MarkHeader previousMarkHeader = null; - foreach (RarHeader header in headerFactory.ReadHeaders(Stream)) + foreach (var header in headerFactory.ReadHeaders(Stream)) { switch (header.HeaderType) { diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index aa7c820b..42163d53 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -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"); + } } } \ No newline at end of file diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index e227a74e..55d79994 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -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() { diff --git a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs index 29309cff..9d5b07d2 100644 --- a/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs +++ b/tests/SharpCompress.Test/Rar/RarHeaderFactoryTest.cs @@ -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)