From 9f549b98da822151cde78d53e2d09ff585ec4ddc Mon Sep 17 00:00:00 2001 From: coderb Date: Sat, 16 Dec 2017 20:39:53 -0500 Subject: [PATCH] rar5: support ArchiveHeader, additional rar5 header implementation --- .../Archives/Rar/RarArchiveVolumeFactory.cs | 6 +- .../Common/Rar/Headers/ArchiveHeader.cs | 82 ++++++++++++++-- .../Common/Rar/Headers/FileHeader.cs | 2 +- src/SharpCompress/Common/Rar/Headers/Flags.cs | 50 +++++++--- .../Common/Rar/Headers/MarkHeader.cs | 1 + .../Common/Rar/Headers/ProtectHeader.cs | 2 +- .../Common/Rar/Headers/RarHeader.cs | 97 ++++++++++++++----- .../Common/Rar/Headers/RarHeaderFactory.cs | 6 +- .../Common/Rar/RarCrcBinaryReader.cs | 4 +- src/SharpCompress/Common/Rar/RarVolume.cs | 7 +- src/SharpCompress/IO/MarkingBinaryReader.cs | 44 ++++++++- 11 files changed, 239 insertions(+), 62 deletions(-) diff --git a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs index 7270a4bd..091f24ae 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveVolumeFactory.cs @@ -33,11 +33,11 @@ namespace SharpCompress.Archives.Rar FileInfoRarArchiveVolume part = new FileInfoRarArchiveVolume(fileInfo, options); yield return part; - if (!part.ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME)) + ArchiveHeader ah = part.ArchiveHeader; + if (!ah.IsVolume) { yield break; //if file isn't volume then there is no reason to look } - ArchiveHeader ah = part.ArchiveHeader; fileInfo = GetNextFileInfo(ah, part.FileParts.FirstOrDefault() as FileInfoRarFilePart); //we use fileinfo because rar is dumb and looks at file names rather than archive info for another volume while (fileInfo != null && fileInfo.Exists) @@ -55,7 +55,7 @@ namespace SharpCompress.Archives.Rar { return null; } - bool oldNumbering = !ah.ArchiveHeaderFlags.HasFlag(ArchiveFlags.NEWNUMBERING) + bool oldNumbering = ah.OldNumberingFormat || currentFilePart.MarkHeader.OldNumberingFormat; if (oldNumbering) { diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs index 214a25c0..82a32910 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveHeader.cs @@ -1,3 +1,4 @@ +using System; using SharpCompress.IO; namespace SharpCompress.Common.Rar.Headers @@ -7,24 +8,91 @@ namespace SharpCompress.Common.Rar.Headers internal const short mainHeaderSizeWithEnc = 7; internal const short mainHeaderSize = 6; + public ArchiveHeader(RarHeader header, RarCrcBinaryReader reader) + : base(header, reader) { + } + protected override void ReadFromReader(MarkingBinaryReader reader) { - HighPosAv = reader.ReadInt16(); - PosAv = reader.ReadInt32(); - if (ArchiveHeaderFlags.HasFlag(ArchiveFlags.ENCRYPTVER)) + if (this.isRar5) { - EncryptionVersion = reader.ReadByte(); + ArchiveHeaderFlags = reader.ReadRarVIntUInt16(); + if ((ArchiveHeaderFlags & ArchiveFlagsV5.HasVolumeNumber) == ArchiveFlagsV5.HasVolumeNumber) + { + VolumeNumber = (int)reader.ReadRarVIntUInt32(); + } + if (ExtraSize != 0) { + ReadLocator(reader); + } + } else { + ArchiveHeaderFlags = Flags; + HighPosAv = reader.ReadInt16(); + PosAv = reader.ReadInt32(); + if ((ArchiveHeaderFlags & ArchiveFlagsV4.EncryptVer) == ArchiveFlagsV4.EncryptVer) + { + EncryptionVersion = reader.ReadByte(); + } } } - internal ArchiveFlags ArchiveHeaderFlags => (ArchiveFlags)Flags; + private void ReadLocator(MarkingBinaryReader reader) { + var size = reader.ReadRarVIntUInt16(); + var type = reader.ReadRarVIntUInt16(); + if (type != 1) throw new InvalidFormatException("expected locator record"); + var flags = reader.ReadRarVIntUInt16(); + const ushort HasQuickOpenOffset = 0x01; + const ushort HasRecoveryOffset = 0x02; + ulong quickOpenOffset = 0; + if ((flags & HasQuickOpenOffset) == HasQuickOpenOffset) { + quickOpenOffset = reader.ReadRarVInt(); + } + ulong recoveryOffset = 0; + if ((flags & HasRecoveryOffset) == HasRecoveryOffset) { + recoveryOffset = reader.ReadRarVInt(); + } + } + + internal ushort ArchiveHeaderFlags { get; private set; } + + internal int VolumeNumber { get; private set; } internal short HighPosAv { get; private set; } internal int PosAv { get; private set; } - internal byte EncryptionVersion { get; private set; } + private byte EncryptionVersion { get; set; } - public bool HasPassword => ArchiveHeaderFlags.HasFlag(ArchiveFlags.PASSWORD); + public bool HasPassword => (ArchiveHeaderFlags & ArchiveFlagsV4.Password) == ArchiveFlagsV4.Password; + + public bool OldNumberingFormat { + get { + return !this.isRar5 && + ((ArchiveHeaderFlags & ArchiveFlagsV4.NewNumbering) != ArchiveFlagsV4.NewNumbering); + } + } + + public bool IsVolume { + get { + return this.isRar5 + ? ((ArchiveHeaderFlags & ArchiveFlagsV5.Volume) == ArchiveFlagsV5.Volume) + : ((ArchiveHeaderFlags & ArchiveFlagsV4.Volume) == ArchiveFlagsV4.Volume); + } + } + + public bool IsFirstVolume { + get { + return this.isRar5 + ? VolumeNumber == 1 + : ((ArchiveHeaderFlags & ArchiveFlagsV4.FirstVolume) == ArchiveFlagsV4.FirstVolume); + } + } + + public bool IsSolid { + get { + return this.isRar5 + ? ((ArchiveHeaderFlags & ArchiveFlagsV5.Solid) == ArchiveFlagsV5.Solid) + : ((ArchiveHeaderFlags & ArchiveFlagsV4.Solid) == ArchiveFlagsV4.Solid); + } + } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 1b539eb7..335f343c 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -42,7 +42,7 @@ namespace SharpCompress.Common.Rar.Headers highUncompressedkSize = int.MaxValue; } } - CompressedSize = UInt32To64(highCompressedSize, AdditionalSize); + CompressedSize = UInt32To64(highCompressedSize, AdditionalDataSize); UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize); nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize; diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index a2dbd550..09c58083 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -2,8 +2,14 @@ using System; namespace SharpCompress.Common.Rar.Headers { - internal enum HeaderType + internal enum HeaderType : byte { + Rar5ArchiveHeader = 0x01, + Rar5FileHeader = 0x02, + Rar5ServiceHeader = 0x03, + Rar5EncryptionHeader = 0x04, + Rar5EndArchiveHeader = 0x05, + MarkHeader = 0x72, ArchiveHeader = 0x73, FileHeader = 0x74, @@ -16,24 +22,38 @@ namespace SharpCompress.Common.Rar.Headers EndArchiveHeader = 0x7b } - internal enum HeaderFlags : short + internal static class HeaderFlagsV4 { - LONG_BLOCK = -0x8000 + public const ushort HasData = 0x8000; } - [Flags] - internal enum ArchiveFlags + internal static class HeaderFlagsV5 { - VOLUME = 0x0001, - COMMENT = 0x0002, - LOCK = 0x0004, - SOLID = 0x0008, - NEWNUMBERING = 0x0010, - AV = 0x0020, - PROTECT = 0x0040, - PASSWORD = 0x0080, - FIRSTVOLUME = 0x0100, - ENCRYPTVER = 0x0200 + public const ushort HasExtra = 0x01; + public const ushort HasData = 0x02; + } + + internal static class ArchiveFlagsV4 + { + public const ushort Volume = 0x0001; + public const ushort Comment = 0x0002; + public const ushort Lock = 0x0004; + public const ushort Solid = 0x0008; + public const ushort NewNumbering = 0x0010; + public const ushort AV = 0x0020; + public const ushort Protect = 0x0040; + public const ushort Password = 0x0080; + public const ushort FirstVolume = 0x0100; + public const ushort EncryptVer = 0x0200; + } + + internal static class ArchiveFlagsV5 + { + public const ushort Volume = 0x0001; + public const ushort HasVolumeNumber = 0x0002; + public const ushort Solid = 0x0004; + public const ushort Protect = 0x0008; + public const ushort Lock = 0x0010; } internal enum HostOS diff --git a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs index 364202fc..30225bc7 100644 --- a/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/MarkHeader.cs @@ -8,6 +8,7 @@ namespace SharpCompress.Common.Rar.Headers 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) { diff --git a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs index e2f78c32..c47c5676 100644 --- a/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ProtectHeader.cs @@ -13,7 +13,7 @@ namespace SharpCompress.Common.Rar.Headers Mark = reader.ReadBytes(8); } - internal uint DataSize => AdditionalSize; + internal uint DataSize => AdditionalDataSize; internal byte Version { get; private set; } internal ushort RecSectors { get; private set; } internal uint TotalBlocks { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 55434c94..310e8bbf 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -6,8 +6,32 @@ namespace SharpCompress.Common.Rar.Headers { internal class RarHeader : IRarHeader { - internal const short BaseBlockSize = 7; - internal const short LONG_BLOCK = -0x8000; + protected readonly bool isRar5; + + protected RarHeader() { + //TODO + //throw new NotImplementedException(); + } + + private RarHeader(bool isRar5) + { + this.isRar5 = isRar5; + } + + protected RarHeader(RarHeader header, RarCrcBinaryReader reader) { + this.isRar5 = header.isRar5; + FillBase(header); + ReadFromReader(reader); + ReadBytes = reader.CurrentReadByteCount; + + int headerSizeDiff = HeaderSize - (int)ReadBytes; + if (headerSizeDiff > 0) + { + reader.ReadBytes(headerSizeDiff); + } + + VerifyHeaderCrc(reader.GetCrc32()); + } private void FillBase(RarHeader baseHeader) { @@ -15,18 +39,18 @@ namespace SharpCompress.Common.Rar.Headers HeaderType = baseHeader.HeaderType; Flags = baseHeader.Flags; HeaderSize = baseHeader.HeaderSize; - AdditionalSize = baseHeader.AdditionalSize; + ExtraSize = baseHeader.ExtraSize; + AdditionalDataSize = baseHeader.AdditionalDataSize; ReadBytes = baseHeader.ReadBytes; ArchiveEncoding = baseHeader.ArchiveEncoding; } - internal static RarHeader Create(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding) + internal static RarHeader ReadBase(RarCrcBinaryReader reader, ArchiveEncoding archiveEncoding, bool isRar5) { try { - var header = new RarHeader(); + var header = new RarHeader(isRar5); header.ArchiveEncoding = archiveEncoding; - reader.Mark(); header.ReadStartFromReader(reader); header.ReadBytes += reader.CurrentReadByteCount; return header; @@ -39,14 +63,33 @@ namespace SharpCompress.Common.Rar.Headers private void ReadStartFromReader(RarCrcBinaryReader reader) { - HeadCRC = reader.ReadUInt16(); - reader.ResetCrc(); - HeaderType = (HeaderType)reader.ReadByte(); - Flags = reader.ReadInt16(); - HeaderSize = reader.ReadInt16(); - if (FlagUtility.HasFlag(Flags, LONG_BLOCK)) + if (this.isRar5) { - AdditionalSize = reader.ReadUInt32(); + HeadCRC = reader.ReadUInt32(); + reader.ResetCrc(); + HeaderSize = (int)reader.ReadRarVIntUInt32(3); + reader.Mark(); + HeaderType = (HeaderType)reader.ReadRarVIntUInt32(1); + Flags = reader.ReadRarVIntUInt16(2); + if ((Flags & HeaderFlagsV5.HasExtra) == HeaderFlagsV5.HasExtra) + { + ExtraSize = reader.ReadRarVIntUInt32(); + } + if ((Flags & HeaderFlagsV5.HasData) == HeaderFlagsV5.HasData) + { + AdditionalDataSize = reader.ReadRarVIntUInt32(); + } + } else { + reader.Mark(); + HeadCRC = reader.ReadUInt16(); + reader.ResetCrc(); + HeaderType = (HeaderType)reader.ReadByte(); + Flags = reader.ReadUInt16(); + HeaderSize = reader.ReadInt16(); + if ((Flags & HeaderFlagsV4.HasData) == HeaderFlagsV4.HasData) + { + AdditionalDataSize = reader.ReadUInt32(); + } } } @@ -54,16 +97,14 @@ namespace SharpCompress.Common.Rar.Headers { throw new NotImplementedException(); } - +//x internal T PromoteHeader(RarCrcBinaryReader reader) where T : RarHeader, new() { T header = new T(); header.FillBase(this); - - reader.Mark(); header.ReadFromReader(reader); - header.ReadBytes += reader.CurrentReadByteCount; + header.ReadBytes = reader.CurrentReadByteCount; int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes; if (headerSizeDiff > 0) @@ -71,16 +112,17 @@ namespace SharpCompress.Common.Rar.Headers reader.ReadBytes(headerSizeDiff); } - VerifyHeaderCrc(reader.GetCrc()); + VerifyHeaderCrc(reader.GetCrc32()); return header; } - private void VerifyHeaderCrc(ushort crc) + private void VerifyHeaderCrc(uint crc32) { if (HeaderType != HeaderType.MarkHeader) { - if (crc != HeadCRC) + var b = (this.isRar5 ? crc32 : (ushort)crc32) == HeadCRC; + if (!b) { throw new InvalidFormatException("rar header crc mismatch"); } @@ -96,22 +138,27 @@ namespace SharpCompress.Common.Rar.Headers /// protected long ReadBytes { get; private set; } - protected ushort HeadCRC { get; private set; } + protected uint HeadCRC { get; private set; } public HeaderType HeaderType { get; private set; } /// /// Untyped flags. These should be typed when Promoting to another header /// - protected short Flags { get; private set; } + protected ushort Flags { get; private set; } - protected short HeaderSize { get; private set; } + protected int HeaderSize { get; private set; } internal ArchiveEncoding ArchiveEncoding { get; private set; } /// - /// This additional size of the header could be file data + /// Extra header data. /// - protected uint AdditionalSize { get; private set; } + protected uint ExtraSize { get; private set; } + + /// + /// Additional data size, eg recovery record + /// + protected uint AdditionalDataSize { get; private set; } } } \ No newline at end of file diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index 66d4c024..22ac412f 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -54,16 +54,18 @@ namespace SharpCompress.Common.Rar.Headers #endif } - var header = RarHeader.Create(reader, Options.ArchiveEncoding); + var header = RarHeader.ReadBase(reader, Options.ArchiveEncoding, this.isRar5); if (header == null) { return null; } switch (header.HeaderType) { + case HeaderType.Rar5ArchiveHeader: case HeaderType.ArchiveHeader: { - var ah = header.PromoteHeader(reader); + var ah = new ArchiveHeader(header, reader); + //x var ah = header.PromoteHeader(reader); IsEncrypted = ah.HasPassword; return ah; } diff --git a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs index aa8c73fd..3251552b 100644 --- a/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCrcBinaryReader.cs @@ -10,9 +10,9 @@ namespace SharpCompress.Common.Rar { { } - public ushort GetCrc() + public uint GetCrc32() { - return (ushort)~currentCrc; + return ~currentCrc; } public void ResetCrc() diff --git a/src/SharpCompress/Common/Rar/RarVolume.cs b/src/SharpCompress/Common/Rar/RarVolume.cs index 4bed1696..521cae19 100644 --- a/src/SharpCompress/Common/Rar/RarVolume.cs +++ b/src/SharpCompress/Common/Rar/RarVolume.cs @@ -81,7 +81,8 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.FIRSTVOLUME); + //return (ArchiveHeader.ArchiveHeaderFlags & ArchiveFlagsV4.FirstVolume) == ArchiveFlagsV4.FirstVolume; + return ArchiveHeader.IsFirstVolume; } } @@ -93,7 +94,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.VOLUME); + return ArchiveHeader.IsVolume; } } @@ -106,7 +107,7 @@ namespace SharpCompress.Common.Rar get { EnsureArchiveHeaderLoaded(); - return ArchiveHeader.ArchiveHeaderFlags.HasFlag(ArchiveFlags.SOLID); + return ArchiveHeader.IsSolid; } } } diff --git a/src/SharpCompress/IO/MarkingBinaryReader.cs b/src/SharpCompress/IO/MarkingBinaryReader.cs index 42163d53..ed33d8a4 100644 --- a/src/SharpCompress/IO/MarkingBinaryReader.cs +++ b/src/SharpCompress/IO/MarkingBinaryReader.cs @@ -143,18 +143,56 @@ namespace SharpCompress.IO int shift = 0; ulong result = 0; do { - ulong n = ReadByte(); + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + ulong n = (ulong)b1; result |= n << shift; - shift += 7; - if ((n & 0x80) == 0) { + if (b0 == b1) { 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 + shift += 7; } while (shift < 63); throw new FormatException("malformed vint"); } + + public uint ReadRarVIntUInt32(int maxBytes = 5) { + int shift = 0; + uint result = 0; + do { + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + uint n = (uint)b1; + result |= n << shift; + if (b0 == b1) { + return result; + } + shift += 7; + // NOTE: we are too strict here but handling the full range adds complexity and we don't need it + } while (shift < 28 && --maxBytes > 0); + + throw new FormatException("malformed vint"); + } + + public ushort ReadRarVIntUInt16(int maxBytes = 3) { + int shift = 0; + uint result = 0; + do { + byte b0 = ReadByte(); + var b1 = b0 & 0x7f; + uint n = (uint)b1; + result |= n << shift; + if (b0 == b1) { + return checked((ushort)result); + } + shift += 7; + // NOTE: we are too strict here but handling the full range adds complexity and we don't need it + } while (shift < 14 && --maxBytes > 0); + + throw new FormatException("malformed vint"); + } } } \ No newline at end of file