FileHeader.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #if !Rar2017_64bit
  2. using nint = System.Int32;
  3. using nuint = System.UInt32;
  4. using size_t = System.UInt32;
  5. #else
  6. using nint = System.Int64;
  7. using nuint = System.UInt64;
  8. using size_t = System.UInt64;
  9. #endif
  10. using SharpCompress.IO;
  11. using System;
  12. using System.IO;
  13. using System.Text;
  14. namespace SharpCompress.Common.Rar.Headers
  15. {
  16. internal class FileHeader : RarHeader
  17. {
  18. private uint _fileCrc;
  19. public FileHeader(RarHeader header, RarCrcBinaryReader reader, HeaderType headerType)
  20. : base(header, reader, headerType)
  21. {
  22. }
  23. protected override void ReadFinish(MarkingBinaryReader reader)
  24. {
  25. if (IsRar5)
  26. {
  27. ReadFromReaderV5(reader);
  28. }
  29. else
  30. {
  31. ReadFromReaderV4(reader);
  32. }
  33. }
  34. private void ReadFromReaderV5(MarkingBinaryReader reader)
  35. {
  36. Flags = reader.ReadRarVIntUInt16();
  37. var lvalue = checked((long)reader.ReadRarVInt());
  38. // long.MaxValue causes the unpack code to finish when the input stream is exhausted
  39. UncompressedSize = HasFlag(FileFlagsV5.UNPACKED_SIZE_UNKNOWN) ? long.MaxValue : lvalue;
  40. FileAttributes = reader.ReadRarVIntUInt32();
  41. if (HasFlag(FileFlagsV5.HAS_MOD_TIME)) {
  42. FileLastModifiedTime = Utility.UnixTimeToDateTime(reader.ReadUInt32());
  43. }
  44. if (HasFlag(FileFlagsV5.HAS_CRC32)) {
  45. FileCrc = reader.ReadUInt32();
  46. }
  47. var compressionInfo = reader.ReadRarVIntUInt16();
  48. // Lower 6 bits (0x003f mask) contain the version of compression algorithm, resulting in possible 0 - 63 values. Current version is 0.
  49. // "+ 50" to not mix with old RAR format algorithms. For example,
  50. // we may need to use the compression algorithm 15 in the future,
  51. // but it was already used in RAR 1.5 and Unpack needs to distinguish
  52. // them.
  53. CompressionAlgorithm = (byte)((compressionInfo & 0x3f) + 50);
  54. // 7th bit (0x0040) defines the solid flag. If it is set, RAR continues to use the compression dictionary left after processing preceding files.
  55. // It can be set only for file headers and is never set for service headers.
  56. IsSolid = (compressionInfo & 0x40) == 0x40;
  57. // Bits 8 - 10 (0x0380 mask) define the compression method. Currently only values 0 - 5 are used. 0 means no compression.
  58. CompressionMethod = (byte)((compressionInfo >> 7) & 0x7);
  59. // 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.
  60. WindowSize = IsDirectory ? 0 : ((size_t)0x20000) << ((compressionInfo>>10) & 0xf);
  61. HostOs = reader.ReadRarVIntByte();
  62. var nameSize = reader.ReadRarVIntUInt16();
  63. // Variable length field containing Name length bytes in UTF-8 format without trailing zero.
  64. // 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.
  65. // 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.
  66. //
  67. // TODO: not sure if anything needs to be done to handle the following:
  68. // If Unix file name contains any high ASCII characters which cannot be correctly converted to Unicode and UTF-8
  69. // we map such characters to to 0xE080 - 0xE0FF private use Unicode area and insert 0xFFFE Unicode non-character
  70. // to resulting string to indicate that it contains mapped characters, which need to be converted back when extracting.
  71. // Concrete position of 0xFFFE is not defined, we need to search the entire string for it. Such mapped names are not
  72. // portable and can be correctly unpacked only on the same system where they were created.
  73. //
  74. // For service header this field contains a name of service header. Now the following names are used:
  75. // CMT Archive comment
  76. // QO Archive quick open data
  77. // ACL NTFS file permissions
  78. // STM NTFS alternate data stream
  79. // RR Recovery record
  80. var b = reader.ReadBytes(nameSize);
  81. FileName = ConvertPathV5(Encoding.UTF8.GetString(b, 0, b.Length));
  82. // extra size seems to be redudant since we know the total header size
  83. if (ExtraSize != RemainingHeaderBytes(reader))
  84. {
  85. throw new InvalidFormatException("rar5 header size / extra size inconsistency");
  86. }
  87. isEncryptedRar5 = false;
  88. while (RemainingHeaderBytes(reader) > 0) {
  89. var size = reader.ReadRarVIntUInt16();
  90. int n = RemainingHeaderBytes(reader);
  91. var type = reader.ReadRarVIntUInt16();
  92. switch (type) {
  93. //TODO
  94. case 1: // file encryption
  95. {
  96. isEncryptedRar5 = true;
  97. //var version = reader.ReadRarVIntByte();
  98. //if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version);
  99. }
  100. break;
  101. // case 2: // file hash
  102. // {
  103. //
  104. // }
  105. // break;
  106. case 3: // file time
  107. {
  108. ushort flags = reader.ReadRarVIntUInt16();
  109. var isWindowsTime = (flags & 1) == 0;
  110. if ((flags & 0x2) == 0x2) {
  111. FileLastModifiedTime = ReadExtendedTimeV5(reader, isWindowsTime);
  112. }
  113. if ((flags & 0x4) == 0x4) {
  114. FileCreatedTime = ReadExtendedTimeV5(reader, isWindowsTime);
  115. }
  116. if ((flags & 0x8) == 0x8) {
  117. FileLastAccessedTime = ReadExtendedTimeV5(reader, isWindowsTime);
  118. }
  119. }
  120. break;
  121. //TODO
  122. // case 4: // file version
  123. // {
  124. //
  125. // }
  126. // break;
  127. // case 5: // file system redirection
  128. // {
  129. //
  130. // }
  131. // break;
  132. // case 6: // unix owner
  133. // {
  134. //
  135. // }
  136. // break;
  137. // case 7: // service data
  138. // {
  139. //
  140. // }
  141. // break;
  142. default:
  143. // skip unknown record types to allow new record types to be added in the future
  144. break;
  145. }
  146. // drain any trailing bytes of extra record
  147. int did = n - RemainingHeaderBytes(reader);
  148. int drain = size - did;
  149. if (drain > 0)
  150. {
  151. reader.ReadBytes(drain);
  152. }
  153. }
  154. if (AdditionalDataSize != 0) {
  155. CompressedSize = AdditionalDataSize;
  156. }
  157. }
  158. private static DateTime ReadExtendedTimeV5(MarkingBinaryReader reader, bool isWindowsTime)
  159. {
  160. if (isWindowsTime)
  161. {
  162. return DateTime.FromFileTime(reader.ReadInt64());
  163. }
  164. else
  165. {
  166. return Utility.UnixTimeToDateTime(reader.ReadUInt32());
  167. }
  168. }
  169. private static string ConvertPathV5(string path)
  170. {
  171. #if NO_FILE
  172. // not sure what to do here
  173. throw new NotImplementedException("TODO");
  174. #else
  175. if (Path.DirectorySeparatorChar == '\\')
  176. {
  177. // replace embedded \\ with valid filename char
  178. return path.Replace('\\', '-').Replace('/', '\\');
  179. }
  180. return path;
  181. #endif
  182. }
  183. private void ReadFromReaderV4(MarkingBinaryReader reader)
  184. {
  185. Flags = HeaderFlags;
  186. IsSolid = HasFlag(FileFlagsV4.SOLID);
  187. WindowSize = IsDirectory ? 0U : ((size_t)0x10000) << ((Flags & FileFlagsV4.WINDOW_MASK) >> 5);
  188. uint lowUncompressedSize = reader.ReadUInt32();
  189. HostOs = reader.ReadByte();
  190. FileCrc = reader.ReadUInt32();
  191. FileLastModifiedTime = Utility.DosDateToDateTime(reader.ReadUInt32());
  192. CompressionAlgorithm = reader.ReadByte();
  193. CompressionMethod = (byte)(reader.ReadByte() - 0x30);
  194. short nameSize = reader.ReadInt16();
  195. FileAttributes = reader.ReadUInt32();
  196. uint highCompressedSize = 0;
  197. uint highUncompressedkSize = 0;
  198. if (HasFlag(FileFlagsV4.LARGE))
  199. {
  200. highCompressedSize = reader.ReadUInt32();
  201. highUncompressedkSize = reader.ReadUInt32();
  202. }
  203. else
  204. {
  205. if (lowUncompressedSize == 0xffffffff)
  206. {
  207. lowUncompressedSize = 0xffffffff;
  208. highUncompressedkSize = int.MaxValue;
  209. }
  210. }
  211. CompressedSize = UInt32To64(highCompressedSize, checked((uint)AdditionalDataSize));
  212. UncompressedSize = UInt32To64(highUncompressedkSize, lowUncompressedSize);
  213. nameSize = nameSize > 4 * 1024 ? (short)(4 * 1024) : nameSize;
  214. byte[] fileNameBytes = reader.ReadBytes(nameSize);
  215. const int saltSize = 8;
  216. const int newLhdSize = 32;
  217. switch (HeaderCode)
  218. {
  219. case HeaderCodeV.RAR4_FILE_HEADER:
  220. {
  221. if (HasFlag(FileFlagsV4.UNICODE))
  222. {
  223. int length = 0;
  224. while (length < fileNameBytes.Length
  225. && fileNameBytes[length] != 0)
  226. {
  227. length++;
  228. }
  229. if (length != nameSize)
  230. {
  231. length++;
  232. FileName = FileNameDecoder.Decode(fileNameBytes, length);
  233. }
  234. else
  235. {
  236. FileName = ArchiveEncoding.Decode(fileNameBytes);
  237. }
  238. }
  239. else
  240. {
  241. FileName = ArchiveEncoding.Decode(fileNameBytes);
  242. }
  243. FileName = ConvertPathV4(FileName);
  244. }
  245. break;
  246. case HeaderCodeV.RAR4_NEW_SUB_HEADER:
  247. {
  248. int datasize = HeaderSize - newLhdSize - nameSize;
  249. if (HasFlag(FileFlagsV4.SALT))
  250. {
  251. datasize -= saltSize;
  252. }
  253. if (datasize > 0)
  254. {
  255. SubData = reader.ReadBytes(datasize);
  256. }
  257. if (NewSubHeaderType.SUBHEAD_TYPE_RR.Equals(fileNameBytes))
  258. {
  259. RecoverySectors = SubData[8] + (SubData[9] << 8)
  260. + (SubData[10] << 16) + (SubData[11] << 24);
  261. }
  262. }
  263. break;
  264. }
  265. if (HasFlag(FileFlagsV4.SALT))
  266. {
  267. R4Salt = reader.ReadBytes(saltSize);
  268. }
  269. if (HasFlag(FileFlagsV4.EXT_TIME))
  270. {
  271. // verify that the end of the header hasn't been reached before reading the Extended Time.
  272. // some tools incorrectly omit Extended Time despite specifying FileFlags.EXTTIME, which most parsers tolerate.
  273. if (RemainingHeaderBytes(reader) >= 2)
  274. {
  275. ushort extendedFlags = reader.ReadUInt16();
  276. FileLastModifiedTime = ProcessExtendedTimeV4(extendedFlags, FileLastModifiedTime, reader, 0);
  277. FileCreatedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 1);
  278. FileLastAccessedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 2);
  279. FileArchivedTime = ProcessExtendedTimeV4(extendedFlags, null, reader, 3);
  280. }
  281. }
  282. }
  283. private static long UInt32To64(uint x, uint y)
  284. {
  285. long l = x;
  286. l <<= 32;
  287. return l + y;
  288. }
  289. private static DateTime? ProcessExtendedTimeV4(ushort extendedFlags, DateTime? time, MarkingBinaryReader reader, int i)
  290. {
  291. uint rmode = (uint)extendedFlags >> (3 - i) * 4;
  292. if ((rmode & 8) == 0)
  293. {
  294. return null;
  295. }
  296. if (i != 0)
  297. {
  298. uint dosTime = reader.ReadUInt32();
  299. time = Utility.DosDateToDateTime(dosTime);
  300. }
  301. if ((rmode & 4) == 0)
  302. {
  303. time = time.Value.AddSeconds(1);
  304. }
  305. uint nanosecondHundreds = 0;
  306. int count = (int)rmode & 3;
  307. for (int j = 0; j < count; j++)
  308. {
  309. byte b = reader.ReadByte();
  310. nanosecondHundreds |= (((uint)b) << ((j + 3 - count) * 8));
  311. }
  312. //10^-7 to 10^-3
  313. return time.Value.AddMilliseconds(nanosecondHundreds * Math.Pow(10, -4));
  314. }
  315. private static string ConvertPathV4(string path)
  316. {
  317. #if NO_FILE
  318. return path.Replace('\\', '/');
  319. #else
  320. if (Path.DirectorySeparatorChar == '/')
  321. {
  322. return path.Replace('\\', '/');
  323. }
  324. else if (Path.DirectorySeparatorChar == '\\')
  325. {
  326. return path.Replace('/', '\\');
  327. }
  328. return path;
  329. #endif
  330. }
  331. public override string ToString()
  332. {
  333. return FileName;
  334. }
  335. private ushort Flags { get; set; }
  336. private bool HasFlag(ushort flag)
  337. {
  338. return (Flags & flag) == flag;
  339. }
  340. internal uint FileCrc
  341. {
  342. get {
  343. if (IsRar5 && !HasFlag(FileFlagsV5.HAS_CRC32)) {
  344. //!!! rar5:
  345. throw new InvalidOperationException("TODO rar5");
  346. }
  347. return _fileCrc;
  348. }
  349. private set => _fileCrc = value;
  350. }
  351. // 0 - storing
  352. // 1 - fastest compression
  353. // 2 - fast compression
  354. // 3 - normal compression
  355. // 4 - good compression
  356. // 5 - best compression
  357. internal byte CompressionMethod { get; private set; }
  358. internal bool IsStored => CompressionMethod == 0;
  359. // eg (see DoUnpack())
  360. //case 15: // rar 1.5 compression
  361. //case 20: // rar 2.x compression
  362. //case 26: // files larger than 2GB
  363. //case 29: // rar 3.x compression
  364. //case 50: // RAR 5.0 compression algorithm.
  365. internal byte CompressionAlgorithm { get; private set; }
  366. public bool IsSolid { get; private set; }
  367. // unused for UnpackV1 implementation (limitation)
  368. internal size_t WindowSize { get; private set; }
  369. internal byte[] R4Salt { get; private set; }
  370. private byte HostOs { get; set; }
  371. internal uint FileAttributes { get; private set; }
  372. internal long CompressedSize { get; private set; }
  373. internal long UncompressedSize { get; private set; }
  374. internal string FileName { get; private set; }
  375. internal byte[] SubData { get; private set; }
  376. internal int RecoverySectors { get; private set; }
  377. internal long DataStartPosition { get; set; }
  378. public Stream PackedStream { get; set; }
  379. public bool IsSplitAfter => IsRar5 ? HasHeaderFlag(HeaderFlagsV5.SPLIT_AFTER) : HasFlag(FileFlagsV4.SPLIT_AFTER);
  380. public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY);
  381. private bool isEncryptedRar5 = false;
  382. public bool IsEncrypted => IsRar5 ? isEncryptedRar5: HasFlag(FileFlagsV4.PASSWORD);
  383. internal DateTime? FileLastModifiedTime { get; private set; }
  384. internal DateTime? FileCreatedTime { get; private set; }
  385. internal DateTime? FileLastAccessedTime { get; private set; }
  386. internal DateTime? FileArchivedTime { get; private set; }
  387. }
  388. }