BSA_ZipArchive.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. // Better Streaming Assets, Piotr Gwiazdowski <gwiazdorrr+github at gmail.com>, 2017
  2. // Bits below are copied from or inspired by System.IO.Compression.dll; leaving comments from
  3. // original source code and attaching license
  4. // The MIT License(MIT)
  5. //
  6. // Copyright(c) .NET Foundation and Contributors
  7. //
  8. // All rights reserved.
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining a copy
  11. // of this software and associated documentation files (the "Software"), to deal
  12. // in the Software without restriction, including without limitation the rights
  13. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. // copies of the Software, and to permit persons to whom the Software is
  15. // furnished to do so, subject to the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be included in all
  18. // copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  23. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  26. // SOFTWARE.
  27. using System;
  28. using System.Collections.Generic;
  29. using System.IO;
  30. using System.Linq;
  31. using System.Text;
  32. using UnityEngine;
  33. namespace Better.StreamingAssets.ZipArchive
  34. {
  35. // All blocks.TryReadBlock do a check to see if signature is correct. Generic extra field is slightly different
  36. // all of the TryReadBlocks will throw if there are not enough bytes in the stream
  37. internal struct ZipGenericExtraField
  38. {
  39. private const int SizeOfHeader = 4;
  40. private ushort _tag;
  41. private ushort _size;
  42. private byte[] _data;
  43. public ushort Tag { get { return _tag; } }
  44. // returns size of data, not of the entire block
  45. public ushort Size { get { return _size; } }
  46. public byte[] Data { get { return _data; } }
  47. // shouldn't ever read the byte at position endExtraField
  48. // assumes we are positioned at the beginning of an extra field subfield
  49. public static bool TryReadBlock(BinaryReader reader, long endExtraField, out ZipGenericExtraField field)
  50. {
  51. field = new ZipGenericExtraField();
  52. // not enough bytes to read tag + size
  53. if ( endExtraField - reader.BaseStream.Position < 4 )
  54. return false;
  55. field._tag = reader.ReadUInt16();
  56. field._size = reader.ReadUInt16();
  57. // not enough bytes to read the data
  58. if ( endExtraField - reader.BaseStream.Position < field._size )
  59. return false;
  60. field._data = reader.ReadBytes(field._size);
  61. return true;
  62. }
  63. }
  64. internal struct Zip64ExtraField
  65. {
  66. // Size is size of the record not including the tag or size fields
  67. // If the extra field is going in the local header, it cannot include only
  68. // one of uncompressed/compressed size
  69. public const int OffsetToFirstField = 4;
  70. private const ushort TagConstant = 1;
  71. private ushort _size;
  72. private long? _uncompressedSize;
  73. private long? _compressedSize;
  74. private long? _localHeaderOffset;
  75. private int? _startDiskNumber;
  76. public long? UncompressedSize
  77. {
  78. get { return _uncompressedSize; }
  79. set { _uncompressedSize = value; UpdateSize(); }
  80. }
  81. public long? CompressedSize
  82. {
  83. get { return _compressedSize; }
  84. set { _compressedSize = value; UpdateSize(); }
  85. }
  86. public long? LocalHeaderOffset
  87. {
  88. get { return _localHeaderOffset; }
  89. set { _localHeaderOffset = value; UpdateSize(); }
  90. }
  91. public int? StartDiskNumber { get { return _startDiskNumber; } }
  92. private void UpdateSize()
  93. {
  94. _size = 0;
  95. if ( _uncompressedSize != null ) _size += 8;
  96. if ( _compressedSize != null ) _size += 8;
  97. if ( _localHeaderOffset != null ) _size += 8;
  98. if ( _startDiskNumber != null ) _size += 4;
  99. }
  100. // There is a small chance that something very weird could happen here. The code calling into this function
  101. // will ask for a value from the extra field if the field was masked with FF's. It's theoretically possible
  102. // that a field was FF's legitimately, and the writer didn't decide to write the corresponding extra field.
  103. // Also, at the same time, other fields were masked with FF's to indicate looking in the zip64 record.
  104. // Then, the search for the zip64 record will fail because the expected size is wrong,
  105. // and a nulled out Zip64ExtraField will be returned. Thus, even though there was Zip64 data,
  106. // it will not be used. It is questionable whether this situation is possible to detect
  107. // unlike the other functions that have try-pattern semantics, these functions always return a
  108. // Zip64ExtraField. If a Zip64 extra field actually doesn't exist, all of the fields in the
  109. // returned struct will be null
  110. //
  111. // If there are more than one Zip64 extra fields, we take the first one that has the expected size
  112. //
  113. public static Zip64ExtraField GetJustZip64Block(Stream extraFieldStream,
  114. bool readUncompressedSize, bool readCompressedSize,
  115. bool readLocalHeaderOffset, bool readStartDiskNumber)
  116. {
  117. Zip64ExtraField zip64Field;
  118. using ( BinaryReader reader = new BinaryReader(extraFieldStream) )
  119. {
  120. ZipGenericExtraField currentExtraField;
  121. while ( ZipGenericExtraField.TryReadBlock(reader, extraFieldStream.Length, out currentExtraField) )
  122. {
  123. if ( TryGetZip64BlockFromGenericExtraField(currentExtraField, readUncompressedSize,
  124. readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field) )
  125. {
  126. return zip64Field;
  127. }
  128. }
  129. }
  130. zip64Field = new Zip64ExtraField();
  131. zip64Field._compressedSize = null;
  132. zip64Field._uncompressedSize = null;
  133. zip64Field._localHeaderOffset = null;
  134. zip64Field._startDiskNumber = null;
  135. return zip64Field;
  136. }
  137. private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField extraField,
  138. bool readUncompressedSize, bool readCompressedSize,
  139. bool readLocalHeaderOffset, bool readStartDiskNumber,
  140. out Zip64ExtraField zip64Block)
  141. {
  142. zip64Block = new Zip64ExtraField();
  143. zip64Block._compressedSize = null;
  144. zip64Block._uncompressedSize = null;
  145. zip64Block._localHeaderOffset = null;
  146. zip64Block._startDiskNumber = null;
  147. if ( extraField.Tag != TagConstant )
  148. return false;
  149. // this pattern needed because nested using blocks trigger CA2202
  150. MemoryStream ms = null;
  151. try
  152. {
  153. ms = new MemoryStream(extraField.Data);
  154. using ( BinaryReader reader = new BinaryReader(ms) )
  155. {
  156. ms = null;
  157. zip64Block._size = extraField.Size;
  158. ushort expectedSize = 0;
  159. if ( readUncompressedSize ) expectedSize += 8;
  160. if ( readCompressedSize ) expectedSize += 8;
  161. if ( readLocalHeaderOffset ) expectedSize += 8;
  162. if ( readStartDiskNumber ) expectedSize += 4;
  163. // if it is not the expected size, perhaps there is another extra field that matches
  164. if ( expectedSize != zip64Block._size )
  165. return false;
  166. if ( readUncompressedSize ) zip64Block._uncompressedSize = reader.ReadInt64();
  167. if ( readCompressedSize ) zip64Block._compressedSize = reader.ReadInt64();
  168. if ( readLocalHeaderOffset ) zip64Block._localHeaderOffset = reader.ReadInt64();
  169. if ( readStartDiskNumber ) zip64Block._startDiskNumber = reader.ReadInt32();
  170. // original values are unsigned, so implies value is too big to fit in signed integer
  171. if ( zip64Block._uncompressedSize < 0 ) throw new ZipArchiveException("FieldTooBigUncompressedSize");
  172. if ( zip64Block._compressedSize < 0 ) throw new ZipArchiveException("FieldTooBigCompressedSize");
  173. if ( zip64Block._localHeaderOffset < 0 ) throw new ZipArchiveException("FieldTooBigLocalHeaderOffset");
  174. if ( zip64Block._startDiskNumber < 0 ) throw new ZipArchiveException("FieldTooBigStartDiskNumber");
  175. return true;
  176. }
  177. }
  178. finally
  179. {
  180. if ( ms != null )
  181. ms.Dispose();
  182. }
  183. }
  184. }
  185. internal struct Zip64EndOfCentralDirectoryLocator
  186. {
  187. public const uint SignatureConstant = 0x07064B50;
  188. public const int SizeOfBlockWithoutSignature = 16;
  189. public uint NumberOfDiskWithZip64EOCD;
  190. public ulong OffsetOfZip64EOCD;
  191. public uint TotalNumberOfDisks;
  192. public static bool TryReadBlock(BinaryReader reader, out Zip64EndOfCentralDirectoryLocator zip64EOCDLocator)
  193. {
  194. zip64EOCDLocator = new Zip64EndOfCentralDirectoryLocator();
  195. if ( reader.ReadUInt32() != SignatureConstant )
  196. return false;
  197. zip64EOCDLocator.NumberOfDiskWithZip64EOCD = reader.ReadUInt32();
  198. zip64EOCDLocator.OffsetOfZip64EOCD = reader.ReadUInt64();
  199. zip64EOCDLocator.TotalNumberOfDisks = reader.ReadUInt32();
  200. return true;
  201. }
  202. }
  203. internal struct Zip64EndOfCentralDirectoryRecord
  204. {
  205. private const uint SignatureConstant = 0x06064B50;
  206. private const ulong NormalSize = 0x2C; // the size of the data excluding the size/signature fields if no extra data included
  207. public ulong SizeOfThisRecord;
  208. public ushort VersionMadeBy;
  209. public ushort VersionNeededToExtract;
  210. public uint NumberOfThisDisk;
  211. public uint NumberOfDiskWithStartOfCD;
  212. public ulong NumberOfEntriesOnThisDisk;
  213. public ulong NumberOfEntriesTotal;
  214. public ulong SizeOfCentralDirectory;
  215. public ulong OffsetOfCentralDirectory;
  216. public static bool TryReadBlock(BinaryReader reader, out Zip64EndOfCentralDirectoryRecord zip64EOCDRecord)
  217. {
  218. zip64EOCDRecord = new Zip64EndOfCentralDirectoryRecord();
  219. if ( reader.ReadUInt32() != SignatureConstant )
  220. return false;
  221. zip64EOCDRecord.SizeOfThisRecord = reader.ReadUInt64();
  222. zip64EOCDRecord.VersionMadeBy = reader.ReadUInt16();
  223. zip64EOCDRecord.VersionNeededToExtract = reader.ReadUInt16();
  224. zip64EOCDRecord.NumberOfThisDisk = reader.ReadUInt32();
  225. zip64EOCDRecord.NumberOfDiskWithStartOfCD = reader.ReadUInt32();
  226. zip64EOCDRecord.NumberOfEntriesOnThisDisk = reader.ReadUInt64();
  227. zip64EOCDRecord.NumberOfEntriesTotal = reader.ReadUInt64();
  228. zip64EOCDRecord.SizeOfCentralDirectory = reader.ReadUInt64();
  229. zip64EOCDRecord.OffsetOfCentralDirectory = reader.ReadUInt64();
  230. return true;
  231. }
  232. }
  233. internal struct ZipLocalFileHeader
  234. {
  235. public const uint DataDescriptorSignature = 0x08074B50;
  236. public const uint SignatureConstant = 0x04034B50;
  237. public const int OffsetToCrcFromHeaderStart = 14;
  238. public const int OffsetToBitFlagFromHeaderStart = 6;
  239. public const int SizeOfLocalHeader = 30;
  240. // will not throw end of stream exception
  241. public static bool TrySkipBlock(BinaryReader reader)
  242. {
  243. const int OffsetToFilenameLength = 22; // from the point after the signature
  244. if ( reader.ReadUInt32() != SignatureConstant )
  245. return false;
  246. if ( reader.BaseStream.Length < reader.BaseStream.Position + OffsetToFilenameLength )
  247. return false;
  248. reader.BaseStream.Seek(OffsetToFilenameLength, SeekOrigin.Current);
  249. ushort filenameLength = reader.ReadUInt16();
  250. ushort extraFieldLength = reader.ReadUInt16();
  251. if ( reader.BaseStream.Length < reader.BaseStream.Position + filenameLength + extraFieldLength )
  252. return false;
  253. reader.BaseStream.Seek(filenameLength + extraFieldLength, SeekOrigin.Current);
  254. return true;
  255. }
  256. }
  257. internal struct ZipCentralDirectoryFileHeader
  258. {
  259. public const uint SignatureConstant = 0x02014B50;
  260. public byte VersionMadeByCompatibility;
  261. public byte VersionMadeBySpecification;
  262. public ushort VersionNeededToExtract;
  263. public ushort GeneralPurposeBitFlag;
  264. public ushort CompressionMethod;
  265. public uint LastModified; // convert this on the fly
  266. public uint Crc32;
  267. public long CompressedSize;
  268. public long UncompressedSize;
  269. public ushort FilenameLength;
  270. public ushort ExtraFieldLength;
  271. public ushort FileCommentLength;
  272. public int DiskNumberStart;
  273. public ushort InternalFileAttributes;
  274. public uint ExternalFileAttributes;
  275. public long RelativeOffsetOfLocalHeader;
  276. public byte[] Filename;
  277. public byte[] FileComment;
  278. public List<ZipGenericExtraField> ExtraFields;
  279. // if saveExtraFieldsAndComments is false, FileComment and ExtraFields will be null
  280. // in either case, the zip64 extra field info will be incorporated into other fields
  281. public static bool TryReadBlock(BinaryReader reader, out ZipCentralDirectoryFileHeader header)
  282. {
  283. header = new ZipCentralDirectoryFileHeader();
  284. if ( reader.ReadUInt32() != SignatureConstant )
  285. return false;
  286. header.VersionMadeBySpecification = reader.ReadByte();
  287. header.VersionMadeByCompatibility = reader.ReadByte();
  288. header.VersionNeededToExtract = reader.ReadUInt16();
  289. header.GeneralPurposeBitFlag = reader.ReadUInt16();
  290. header.CompressionMethod = reader.ReadUInt16();
  291. header.LastModified = reader.ReadUInt32();
  292. header.Crc32 = reader.ReadUInt32();
  293. uint compressedSizeSmall = reader.ReadUInt32();
  294. uint uncompressedSizeSmall = reader.ReadUInt32();
  295. header.FilenameLength = reader.ReadUInt16();
  296. header.ExtraFieldLength = reader.ReadUInt16();
  297. header.FileCommentLength = reader.ReadUInt16();
  298. ushort diskNumberStartSmall = reader.ReadUInt16();
  299. header.InternalFileAttributes = reader.ReadUInt16();
  300. header.ExternalFileAttributes = reader.ReadUInt32();
  301. uint relativeOffsetOfLocalHeaderSmall = reader.ReadUInt32();
  302. header.Filename = reader.ReadBytes(header.FilenameLength);
  303. bool uncompressedSizeInZip64 = uncompressedSizeSmall == ZipHelper.Mask32Bit;
  304. bool compressedSizeInZip64 = compressedSizeSmall == ZipHelper.Mask32Bit;
  305. bool relativeOffsetInZip64 = relativeOffsetOfLocalHeaderSmall == ZipHelper.Mask32Bit;
  306. bool diskNumberStartInZip64 = diskNumberStartSmall == ZipHelper.Mask16Bit;
  307. Zip64ExtraField zip64;
  308. long endExtraFields = reader.BaseStream.Position + header.ExtraFieldLength;
  309. using ( Stream str = new SubReadOnlyStream(reader.BaseStream, reader.BaseStream.Position, header.ExtraFieldLength, leaveOpen: true) )
  310. {
  311. header.ExtraFields = null;
  312. zip64 = Zip64ExtraField.GetJustZip64Block(str,
  313. uncompressedSizeInZip64, compressedSizeInZip64,
  314. relativeOffsetInZip64, diskNumberStartInZip64);
  315. }
  316. // There are zip files that have malformed ExtraField blocks in which GetJustZip64Block() silently bails out without reading all the way to the end
  317. // of the ExtraField block. Thus we must force the stream's position to the proper place.
  318. reader.BaseStream.AdvanceToPosition(endExtraFields);
  319. reader.BaseStream.Position += header.FileCommentLength;
  320. header.FileComment = null;
  321. header.UncompressedSize = zip64.UncompressedSize == null
  322. ? uncompressedSizeSmall
  323. : zip64.UncompressedSize.Value;
  324. header.CompressedSize = zip64.CompressedSize == null
  325. ? compressedSizeSmall
  326. : zip64.CompressedSize.Value;
  327. header.RelativeOffsetOfLocalHeader = zip64.LocalHeaderOffset == null
  328. ? relativeOffsetOfLocalHeaderSmall
  329. : zip64.LocalHeaderOffset.Value;
  330. header.DiskNumberStart = zip64.StartDiskNumber == null
  331. ? diskNumberStartSmall
  332. : zip64.StartDiskNumber.Value;
  333. return true;
  334. }
  335. }
  336. internal struct ZipEndOfCentralDirectoryBlock
  337. {
  338. public const uint SignatureConstant = 0x06054B50;
  339. public const int SizeOfBlockWithoutSignature = 18;
  340. public uint Signature;
  341. public ushort NumberOfThisDisk;
  342. public ushort NumberOfTheDiskWithTheStartOfTheCentralDirectory;
  343. public ushort NumberOfEntriesInTheCentralDirectoryOnThisDisk;
  344. public ushort NumberOfEntriesInTheCentralDirectory;
  345. public uint SizeOfCentralDirectory;
  346. public uint OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
  347. public byte[] ArchiveComment;
  348. public static bool TryReadBlock(BinaryReader reader, out ZipEndOfCentralDirectoryBlock eocdBlock)
  349. {
  350. eocdBlock = new ZipEndOfCentralDirectoryBlock();
  351. if ( reader.ReadUInt32() != SignatureConstant )
  352. return false;
  353. eocdBlock.Signature = SignatureConstant;
  354. eocdBlock.NumberOfThisDisk = reader.ReadUInt16();
  355. eocdBlock.NumberOfTheDiskWithTheStartOfTheCentralDirectory = reader.ReadUInt16();
  356. eocdBlock.NumberOfEntriesInTheCentralDirectoryOnThisDisk = reader.ReadUInt16();
  357. eocdBlock.NumberOfEntriesInTheCentralDirectory = reader.ReadUInt16();
  358. eocdBlock.SizeOfCentralDirectory = reader.ReadUInt32();
  359. eocdBlock.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = reader.ReadUInt32();
  360. ushort commentLength = reader.ReadUInt16();
  361. eocdBlock.ArchiveComment = reader.ReadBytes(commentLength);
  362. return true;
  363. }
  364. }
  365. internal static class ZipHelper
  366. {
  367. internal const uint Mask32Bit = 0xFFFFFFFF;
  368. internal const ushort Mask16Bit = 0xFFFF;
  369. private const int BackwardsSeekingBufferSize = 32;
  370. /// <summary>
  371. /// Reads exactly bytesToRead out of stream, unless it is out of bytes
  372. /// </summary>
  373. internal static void ReadBytes(Stream stream, byte[] buffer, int bytesToRead)
  374. {
  375. int bytesLeftToRead = bytesToRead;
  376. int totalBytesRead = 0;
  377. while (bytesLeftToRead > 0)
  378. {
  379. int bytesRead = stream.Read(buffer, totalBytesRead, bytesLeftToRead);
  380. if (bytesRead == 0) throw new IOException();
  381. totalBytesRead += bytesRead;
  382. bytesLeftToRead -= bytesRead;
  383. }
  384. }
  385. // assumes all bytes of signatureToFind are non zero, looks backwards from current position in stream,
  386. // if the signature is found then returns true and positions stream at first byte of signature
  387. // if the signature is not found, returns false
  388. internal static bool SeekBackwardsToSignature(Stream stream, uint signatureToFind)
  389. {
  390. int bufferPointer = 0;
  391. uint currentSignature = 0;
  392. byte[] buffer = new byte[BackwardsSeekingBufferSize];
  393. bool outOfBytes = false;
  394. bool signatureFound = false;
  395. while (!signatureFound && !outOfBytes)
  396. {
  397. outOfBytes = SeekBackwardsAndRead(stream, buffer, out bufferPointer);
  398. Debug.Assert(bufferPointer < buffer.Length);
  399. while (bufferPointer >= 0 && !signatureFound)
  400. {
  401. currentSignature = (currentSignature << 8) | ((uint)buffer[bufferPointer]);
  402. if (currentSignature == signatureToFind)
  403. {
  404. signatureFound = true;
  405. }
  406. else
  407. {
  408. bufferPointer--;
  409. }
  410. }
  411. }
  412. if (!signatureFound)
  413. {
  414. return false;
  415. }
  416. else
  417. {
  418. stream.Seek(bufferPointer, SeekOrigin.Current);
  419. return true;
  420. }
  421. }
  422. // Skip to a further position downstream (without relying on the stream being seekable)
  423. internal static void AdvanceToPosition(this Stream stream, long position)
  424. {
  425. long numBytesLeft = position - stream.Position;
  426. Debug.Assert(numBytesLeft >= 0);
  427. while (numBytesLeft != 0)
  428. {
  429. const int throwAwayBufferSize = 64;
  430. int numBytesToSkip = (numBytesLeft > throwAwayBufferSize) ? throwAwayBufferSize : (int)numBytesLeft;
  431. int numBytesActuallySkipped = stream.Read(new byte[throwAwayBufferSize], 0, numBytesToSkip);
  432. if (numBytesActuallySkipped == 0)
  433. throw new IOException();
  434. numBytesLeft -= numBytesActuallySkipped;
  435. }
  436. }
  437. // Returns true if we are out of bytes
  438. private static bool SeekBackwardsAndRead(Stream stream, byte[] buffer, out int bufferPointer)
  439. {
  440. if (stream.Position >= buffer.Length)
  441. {
  442. stream.Seek(-buffer.Length, SeekOrigin.Current);
  443. ReadBytes(stream, buffer, buffer.Length);
  444. stream.Seek(-buffer.Length, SeekOrigin.Current);
  445. bufferPointer = buffer.Length - 1;
  446. return false;
  447. }
  448. else
  449. {
  450. int bytesToRead = (int)stream.Position;
  451. stream.Seek(0, SeekOrigin.Begin);
  452. ReadBytes(stream, buffer, bytesToRead);
  453. stream.Seek(0, SeekOrigin.Begin);
  454. bufferPointer = bytesToRead - 1;
  455. return true;
  456. }
  457. }
  458. }
  459. public class ZipArchiveException : Exception
  460. {
  461. public ZipArchiveException(string msg) : base(msg)
  462. { }
  463. public ZipArchiveException(string msg, Exception inner)
  464. : base(msg, inner)
  465. {
  466. }
  467. }
  468. public static class ZipArchiveUtils
  469. {
  470. public static void ReadEndOfCentralDirectory(Stream stream, BinaryReader reader, out long expectedNumberOfEntries, out long centralDirectoryStart)
  471. {
  472. try
  473. {
  474. // this seeks to the start of the end of central directory record
  475. stream.Seek(-ZipEndOfCentralDirectoryBlock.SizeOfBlockWithoutSignature, SeekOrigin.End);
  476. if (!ZipHelper.SeekBackwardsToSignature(stream, ZipEndOfCentralDirectoryBlock.SignatureConstant))
  477. throw new ZipArchiveException("SignatureConstant");
  478. long eocdStart = stream.Position;
  479. // read the EOCD
  480. ZipEndOfCentralDirectoryBlock eocd;
  481. bool eocdProper = ZipEndOfCentralDirectoryBlock.TryReadBlock(reader, out eocd);
  482. Debug.Assert(eocdProper); // we just found this using the signature finder, so it should be okay
  483. if (eocd.NumberOfThisDisk != eocd.NumberOfTheDiskWithTheStartOfTheCentralDirectory)
  484. throw new ZipArchiveException("SplitSpanned");
  485. centralDirectoryStart = eocd.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
  486. if (eocd.NumberOfEntriesInTheCentralDirectory != eocd.NumberOfEntriesInTheCentralDirectoryOnThisDisk)
  487. throw new ZipArchiveException("SplitSpanned");
  488. expectedNumberOfEntries = eocd.NumberOfEntriesInTheCentralDirectory;
  489. // only bother looking for zip64 EOCD stuff if we suspect it is needed because some value is FFFFFFFFF
  490. // because these are the only two values we need, we only worry about these
  491. // if we don't find the zip64 EOCD, we just give up and try to use the original values
  492. if (eocd.NumberOfThisDisk == ZipHelper.Mask16Bit ||
  493. eocd.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber == ZipHelper.Mask32Bit ||
  494. eocd.NumberOfEntriesInTheCentralDirectory == ZipHelper.Mask16Bit)
  495. {
  496. // we need to look for zip 64 EOCD stuff
  497. // seek to the zip 64 EOCD locator
  498. stream.Seek(eocdStart - Zip64EndOfCentralDirectoryLocator.SizeOfBlockWithoutSignature, SeekOrigin.Begin);
  499. // if we don't find it, assume it doesn't exist and use data from normal eocd
  500. if (ZipHelper.SeekBackwardsToSignature(stream, Zip64EndOfCentralDirectoryLocator.SignatureConstant))
  501. {
  502. // use locator to get to Zip64EOCD
  503. Zip64EndOfCentralDirectoryLocator locator;
  504. bool zip64eocdLocatorProper = Zip64EndOfCentralDirectoryLocator.TryReadBlock(reader, out locator);
  505. Debug.Assert(zip64eocdLocatorProper); // we just found this using the signature finder, so it should be okay
  506. if (locator.OffsetOfZip64EOCD > long.MaxValue)
  507. throw new ZipArchiveException("FieldTooBigOffsetToZip64EOCD");
  508. long zip64EOCDOffset = (long)locator.OffsetOfZip64EOCD;
  509. stream.Seek(zip64EOCDOffset, SeekOrigin.Begin);
  510. // read Zip64EOCD
  511. Zip64EndOfCentralDirectoryRecord record;
  512. if (!Zip64EndOfCentralDirectoryRecord.TryReadBlock(reader, out record))
  513. throw new ZipArchiveException("Zip64EOCDNotWhereExpected");
  514. if (record.NumberOfEntriesTotal > long.MaxValue)
  515. throw new ZipArchiveException("FieldTooBigNumEntries");
  516. if (record.OffsetOfCentralDirectory > long.MaxValue)
  517. throw new ZipArchiveException("FieldTooBigOffsetToCD");
  518. if (record.NumberOfEntriesTotal != record.NumberOfEntriesOnThisDisk)
  519. throw new ZipArchiveException("SplitSpanned");
  520. expectedNumberOfEntries = (long)record.NumberOfEntriesTotal;
  521. centralDirectoryStart = (long)record.OffsetOfCentralDirectory;
  522. }
  523. }
  524. if (centralDirectoryStart > stream.Length)
  525. {
  526. throw new ZipArchiveException("FieldTooBigOffsetToCD");
  527. }
  528. }
  529. catch (EndOfStreamException ex)
  530. {
  531. throw new ZipArchiveException("CDCorrupt", ex);
  532. }
  533. catch (IOException ex)
  534. {
  535. throw new ZipArchiveException("CDCorrupt", ex);
  536. }
  537. }
  538. }
  539. }