123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- #if !Rar2017_64bit
- using nint = System.Int32;
- using nuint = System.UInt32;
- using size_t = System.UInt32;
- #else
- using nint = System.Int64;
- using nuint = System.UInt64;
- using size_t = System.UInt64;
- #endif
- using SharpCompress.IO;
- using System;
- using System.IO;
- using System.Text;
- namespace SharpCompress.Common.Rar.Headers
- {
- internal class FileHeader : RarHeader
- {
- private uint _fileCrc;
- public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
- : base(header, reader, headerType)
- {
- }
- protected override void ReadFinish(MarkingBinaryReader reader)
- {
- if (IsRar5)
- {
- ReadFromReaderV5(reader);
- }
- else
- {
- ReadFromReaderV4(reader);
- }
- }
- private void ReadFromReaderV5(MarkingBinaryReader reader)
- {
- Flags = reader.ReadRarVIntUInt16();
- var lvalue = checked((long)reader.ReadRarVInt());
- // long.MaxValue causes the unpack code to finish when the input stream is exhausted
- UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue;
- FileAttributes = reader.ReadRarVIntUInt32();
- if (HasFlag(FileFlagsV5.HAS_MOD_TIME)) {
- FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32());
- }
- if (HasFlag(FileFlagsV5.HAS_CRC32)) {
- FileCrc = reader.ReadUInt32();
- }
- var compressionInfo = reader.ReadRarVIntUInt16();
- // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0.
- // "+ 50" to not mix with old RAR format algorithms. For example,
- // we may need to use the compression algorithm 15 in the future,
- // but it was already used in RAR 1.5 and Unpack needs to distinguish
- // them.
- CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50);
-
- // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files.
- // It can be set only for file headers and is never set for service headers.
- IsSolid = (compressionInfo & 0x40) == 0x40;
- // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression.
- CompressionMethod = (byte)((compressionInfo >> 7) & 0x7);
- // Bits 11 - 14 (0x3c00) define the minimum size of dictionary size required to extract data. Value 0 means 128 KB, 1 - 256 KB, ..., 14 - 2048 MB, 15 - 4096 MB.
- WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo>>10) & 0xf);
- HostOs = reader.ReadRarVIntByte();
- var nameSize = reader.ReadRarVIntUInt16();
- // Variable length field containing Name length bytes in UTF-8 format without trailing zero.
- // For file header this is a name of archived file. Forward slash character is used as the path separator both for Unix and Windows names.
- // Backslashes are treated as a part of name for Unix names and as invalid character for Windows file names. Type of name is defined by Host OS field.
- //
- // TODO: not sure if anything needs to be done to handle the following:
- // If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8
- // we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character
- // to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting.
- // Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not
- // portable and can be correctly unpacked only on the same system where they were created.
- //
- // For service header this field contains a name of service header. Now the following names are used:
- // CMT Archive comment
- // QO Archive quick open data
- // ACL NTFS file permissions
- // STM NTFS alternate data stream
- // RR Recovery record
- var b = reader.ReadBytes(nameSize);
- FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length));
- // extra size seems to be redudant since we know the total header size
- if (ExtraSize != RemainingHeaderBytes(reader))
- {
- throw new InvalidFormatException("rar5 header size / extra size inconsistency");
- }
- isEncryptedRar5 = false;
- while (RemainingHeaderBytes(reader) > 0) {
- var size = reader.ReadRarVIntUInt16();
- int n = RemainingHeaderBytes(reader);
- var type = reader.ReadRarVIntUInt16();
- switch (type) {
- //TODO
- case 1: // file encryption
- {
- isEncryptedRar5 = true;
- //var version = reader.ReadRarVIntByte();
- //if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version);
- }
- break;
- // case 2: // file hash
- // {
- //
- // }
- // break;
- case 3: // file time
- {
- ushort flags = reader.ReadRarVIntUInt16();
- var isWindowsTime = (flags & 1) == 0;
- if ((flags & 0x2) == 0x2) {
- FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime);
- }
- if ((flags & 0x4) == 0x4) {
- FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime);
- }
- if ((flags & 0x8) == 0x8) {
- FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime);
- }
- }
- break;
- //TODO
- // case 4: // file version
- // {
- //
- // }
- // break;
- // case 5: // file system redirection
- // {
- //
- // }
- // break;
- // case 6: // unix owner
- // {
- //
- // }
- // break;
- // case 7: // service data
- // {
- //
- // }
- // break;
- default:
- // skip unknown record types to allow new record types to be added in the future
- break;
- }
- // drain any trailing bytes of extra record
- int did = n - RemainingHeaderBytes(reader);
- int drain = size - did;
- if (drain > 0)
- {
- reader.ReadBytes(drain);
- }
- }
- if (AdditionalDataSize != 0) {
- CompressedSize = AdditionalDataSize;
- }
- }
- private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime)
- {
- if (isWindowsTime)
- {
- return DateTime.FromFileTime(reader.ReadInt64());
- }
- else
- {
- return Utility.UnixTimeToDateTime(reader.ReadUInt32());
- }
- }
- private static string ConvertPathV5(string path)
- {
- #if NO_FILE
- // not sure what to do here
- throw new NotImplementedException("TODO");
- #else
- if (Path.DirectorySeparatorChar == '\\')
- {
- // replace embedded \\ with valid filename char
- return path.Replace('\\', '-').Replace('/', '\\');
- }
- return path;
- #endif
- }
- private void ReadFromReaderV4(MarkingBinaryReader reader)
- {
- Flags = HeaderFlags;
- IsSolid = HasFlag(FileFlagsV4.SOLID);
- WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5);
- uint lowUncompressedSize = reader.ReadUInt32();
- HostOs = reader.ReadByte();
- FileCrc = reader.ReadUInt32();
- FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32());
- CompressionAlgorithm = reader.ReadByte();
- CompressionMethod = (byte)(reader.ReadByte() - 0x30);
- short nameSize = reader.ReadInt16();
- FileAttributes = reader.ReadUInt32();
- uint highCompressedSize = 0;
- uint highUncompressedkSize = 0;
- if (HasFlag(FileFlagsV4.LARGE))
- {
- highCompressedSize = reader.ReadUInt32();
- highUncompressedkSize = reader.ReadUInt32();
- }
- else
- {
- if (lowUncompressedSize == 0xffffffff)
- {
- lowUncompressedSize = 0xffffffff;
- highUncompressedkSize = int.MaxValue;
- }
- }
- CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize));
- UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize);
- nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize;
- byte[] fileNameBytes = reader.ReadBytes(nameSize);
- const int saltSize = 8;
- const int newLhdSize = 32;
- switch (HeaderCode)
- {
- case HeaderCodeV.RAR4_FILE_HEADER:
- {
- if (HasFlag(FileFlagsV4.UNICODE))
- {
- int length = 0;
- while (length < fileNameBytes.Length
- && fileNameBytes[length] != 0)
- {
- length++;
- }
- if (length != nameSize)
- {
- length++;
- FileName = FileNameDecoder.Decode(fileNameBytes, length);
- }
- else
- {
- FileName = ArchiveEncoding.Decode(fileNameBytes);
- }
- }
- else
- {
- FileName = ArchiveEncoding.Decode(fileNameBytes);
- }
- FileName = ConvertPathV4(FileName);
- }
- break;
- case HeaderCodeV.RAR4_NEW_SUB_HEADER:
- {
- int datasize = HeaderSize - newLhdSize - nameSize;
- if (HasFlag(FileFlagsV4.SALT))
- {
- datasize -= saltSize;
- }
- if (datasize > 0)
- {
- SubData = reader.ReadBytes(datasize);
- }
- if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
- {
- RecoverySectors = SubData[8] + (SubData[9] << 8)
- + (SubData[10] << 16) + (SubData[11] << 24);
- }
- }
- break;
- }
- if (HasFlag(FileFlagsV4.SALT))
- {
- R4Salt = reader.ReadBytes(saltSize);
- }
- if (HasFlag(FileFlagsV4.EXT_TIME))
- {
- // verify that the end of the header hasn't been reached before reading the Extended Time.
- // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate.
- if (RemainingHeaderBytes(reader) >= 2)
- {
- ushort extendedFlags = reader.ReadUInt16();
- FileLastModifiedTime = ProcessExtendedTimeV4(extendedFlags, FileLastModifiedTime, reader, 0);
- FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1);
- FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2);
- FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3);
- }
- }
- }
- private static long UInt32To64(uint x, uint y)
- {
- long l = x;
- l <<= 32;
- return l + y;
- }
- private static DateTime? ProcessExtendedTimeV4(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, int i)
- {
- uint rmode = (uint)extendedFlags >> (3 - i) * 4;
- if ((rmode & 8) == 0)
- {
- return null;
- }
- if (i != 0)
- {
- uint dosTime = reader.ReadUInt32();
- time = Utility.DosDateToDateTime(dosTime);
- }
- if ((rmode & 4) == 0)
- {
- time = time.Value.AddSeconds(1);
- }
- uint nanosecondHundreds = 0;
- int count = (int)rmode & 3;
- for (int j = 0; j < count; j++)
- {
- byte b = reader.ReadByte();
- nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8));
- }
- //10^-7 to 10^-3
- return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
- }
- private static string ConvertPathV4(string path)
- {
- #if NO_FILE
- return path.Replace('\\', '/');
- #else
- if (Path.DirectorySeparatorChar == '/')
- {
- return path.Replace('\\', '/');
- }
- else if (Path.DirectorySeparatorChar == '\\')
- {
- return path.Replace('/', '\\');
- }
- return path;
- #endif
- }
- public override string ToString()
- {
- return FileName;
- }
- private ushort Flags { get; set; }
- private bool HasFlag(ushort flag)
- {
- return (Flags & flag) == flag;
- }
- internal uint FileCrc
- {
- get {
- if (IsRar5 && !HasFlag(FileFlagsV5.HAS_CRC32)) {
- //!!! rar5:
- throw new InvalidOperationException("TODO rar5");
- }
- return _fileCrc;
- }
- private set => _fileCrc = value;
- }
- // 0 - storing
- // 1 - fastest compression
- // 2 - fast compression
- // 3 - normal compression
- // 4 - good compression
- // 5 - best compression
- internal byte CompressionMethod { get; private set; }
- internal bool IsStored => CompressionMethod == 0;
- // eg (see DoUnpack())
- //case 15: // rar 1.5 compression
- //case 20: // rar 2.x compression
- //case 26: // files larger than 2GB
- //case 29: // rar 3.x compression
- //case 50: // RAR 5.0 compression algorithm.
- internal byte CompressionAlgorithm { get; private set; }
-
- public bool IsSolid { get; private set; }
- // unused for UnpackV1 implementation (limitation)
- internal size_t WindowSize { get; private set; }
- internal byte[] R4Salt { get; private set; }
- private byte HostOs { get; set; }
- internal uint FileAttributes { get; private set; }
- internal long CompressedSize { get; private set; }
- internal long UncompressedSize { get; private set; }
- internal string FileName { get; private set; }
- internal byte[] SubData { get; private set; }
- internal int RecoverySectors { get; private set; }
- internal long DataStartPosition { get; set; }
- public Stream PackedStream { get; set; }
- public bool IsSplitAfter => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER);
- public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY);
- private bool isEncryptedRar5 = false;
- public bool IsEncrypted => IsRar5 ? isEncryptedRar5: HasFlag(FileFlagsV4.PASSWORD);
-
- internal DateTime? FileLastModifiedTime { get; private set; }
- internal DateTime? FileCreatedTime { get; private set; }
- internal DateTime? FileLastAccessedTime { get; private set; }
- internal DateTime? FileArchivedTime { get; private set; }
- }
- }
|