ZipDirEntry.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // ZipDirEntry.cs
  2. // ------------------------------------------------------------------
  3. //
  4. // Copyright (c) 2006-2011 Dino Chiesa .
  5. // All rights reserved.
  6. //
  7. // This code module is part of DotNetZip, a zipfile class library.
  8. //
  9. // ------------------------------------------------------------------
  10. //
  11. // This code is licensed under the Microsoft Public License.
  12. // See the file License.txt for the license details.
  13. // More info on: http://dotnetzip.codeplex.com
  14. //
  15. // ------------------------------------------------------------------
  16. //
  17. // last saved (in emacs):
  18. // Time-stamp: <2011-July-11 12:03:03>
  19. //
  20. // ------------------------------------------------------------------
  21. //
  22. // This module defines members of the ZipEntry class for reading the
  23. // Zip file central directory.
  24. //
  25. // Created: Tue, 27 Mar 2007 15:30
  26. //
  27. // ------------------------------------------------------------------
  28. using System;
  29. using System.Collections.Generic;
  30. namespace Ionic.Zip
  31. {
  32. partial class ZipEntry
  33. {
  34. /// <summary>
  35. /// True if the referenced entry is a directory.
  36. /// </summary>
  37. internal bool AttributesIndicateDirectory
  38. {
  39. get { return ((_InternalFileAttrs == 0) && ((_ExternalFileAttrs & 0x0010) == 0x0010)); }
  40. }
  41. internal void ResetDirEntry()
  42. {
  43. // __FileDataPosition is the position of the file data for an entry.
  44. // It is _RelativeOffsetOfLocalHeader + size of local header.
  45. // We cannot know the __FileDataPosition until we read the local
  46. // header.
  47. // The local header is not necessarily the same length as the record
  48. // in the central directory.
  49. // Set to -1, to indicate we need to read this later.
  50. this.__FileDataPosition = -1;
  51. // set _LengthOfHeader to 0, to indicate we need to read later.
  52. this._LengthOfHeader = 0;
  53. // reset the copy counter because we've got a good entry now
  54. CopyHelper.Reset();
  55. }
  56. /// <summary>
  57. /// Provides a human-readable string with information about the ZipEntry.
  58. /// </summary>
  59. public string Info
  60. {
  61. get
  62. {
  63. var builder = new System.Text.StringBuilder();
  64. builder
  65. .Append(string.Format(" ZipEntry: {0}\n", this.FileName))
  66. .Append(string.Format(" Version Made By: {0}\n", this._VersionMadeBy))
  67. .Append(string.Format(" Needed to extract: {0}\n", this.VersionNeeded));
  68. if (this._IsDirectory)
  69. builder.Append(" Entry type: directory\n");
  70. else
  71. {
  72. builder.Append(string.Format(" File type: {0}\n", this._IsText? "text":"binary"))
  73. .Append(string.Format(" Compression: {0}\n", this.CompressionMethod))
  74. .Append(string.Format(" Compressed: 0x{0:X}\n", this.CompressedSize))
  75. .Append(string.Format(" Uncompressed: 0x{0:X}\n", this.UncompressedSize))
  76. .Append(string.Format(" CRC32: 0x{0:X8}\n", this._Crc32));
  77. }
  78. builder.Append(string.Format(" Disk Number: {0}\n", this._diskNumber));
  79. if (this._RelativeOffsetOfLocalHeader > 0xFFFFFFFF)
  80. builder
  81. .Append(string.Format(" Relative Offset: 0x{0:X16}\n", this._RelativeOffsetOfLocalHeader));
  82. else
  83. builder
  84. .Append(string.Format(" Relative Offset: 0x{0:X8}\n", this._RelativeOffsetOfLocalHeader));
  85. builder
  86. .Append(string.Format(" Bit Field: 0x{0:X4}\n", this._BitField))
  87. .Append(string.Format(" Encrypted?: {0}\n", this._sourceIsEncrypted))
  88. .Append(string.Format(" Timeblob: 0x{0:X8}\n", this._TimeBlob))
  89. .Append(string.Format(" Time: {0}\n", Ionic.Zip.SharedUtilities.PackedToDateTime(this._TimeBlob)));
  90. builder.Append(string.Format(" Is Zip64?: {0}\n", this._InputUsesZip64));
  91. if (!string.IsNullOrEmpty(this._Comment))
  92. {
  93. builder.Append(string.Format(" Comment: {0}\n", this._Comment));
  94. }
  95. builder.Append("\n");
  96. return builder.ToString();
  97. }
  98. }
  99. // workitem 10330
  100. private class CopyHelper
  101. {
  102. private static System.Text.RegularExpressions.Regex re =
  103. new System.Text.RegularExpressions.Regex(" \\(copy (\\d+)\\)$");
  104. private static int callCount = 0;
  105. internal static void Reset()
  106. {
  107. callCount = 0;
  108. }
  109. internal static string AppendCopyToFileName(string f)
  110. {
  111. callCount++;
  112. if (callCount > 25)
  113. throw new OverflowException("overflow while creating filename");
  114. int n = 1;
  115. int r = f.LastIndexOf(".");
  116. if (r == -1)
  117. {
  118. // there is no extension
  119. System.Text.RegularExpressions.Match m = re.Match(f);
  120. if (m.Success)
  121. {
  122. n = Int32.Parse(m.Groups[1].Value) + 1;
  123. string copy = String.Format(" (copy {0})", n);
  124. f = f.Substring(0, m.Index) + copy;
  125. }
  126. else
  127. {
  128. string copy = String.Format(" (copy {0})", n);
  129. f = f + copy;
  130. }
  131. }
  132. else
  133. {
  134. //System.Console.WriteLine("HasExtension");
  135. System.Text.RegularExpressions.Match m = re.Match(f.Substring(0, r));
  136. if (m.Success)
  137. {
  138. n = Int32.Parse(m.Groups[1].Value) + 1;
  139. string copy = String.Format(" (copy {0})", n);
  140. f = f.Substring(0, m.Index) + copy + f.Substring(r);
  141. }
  142. else
  143. {
  144. string copy = String.Format(" (copy {0})", n);
  145. f = f.Substring(0, r) + copy + f.Substring(r);
  146. }
  147. //System.Console.WriteLine("returning f({0})", f);
  148. }
  149. return f;
  150. }
  151. }
  152. /// <summary>
  153. /// Reads one entry from the zip directory structure in the zip file.
  154. /// </summary>
  155. ///
  156. /// <param name="zf">
  157. /// The zipfile for which a directory entry will be read. From this param, the
  158. /// method gets the ReadStream and the expected text encoding
  159. /// (ProvisionalAlternateEncoding) which is used if the entry is not marked
  160. /// UTF-8.
  161. /// </param>
  162. ///
  163. /// <param name="previouslySeen">
  164. /// a list of previously seen entry names; used to prevent duplicates.
  165. /// </param>
  166. ///
  167. /// <returns>the entry read from the archive.</returns>
  168. internal static ZipEntry ReadDirEntry(ZipFile zf,
  169. Dictionary<String,Object> previouslySeen)
  170. {
  171. System.IO.Stream s = zf.ReadStream;
  172. System.Text.Encoding expectedEncoding = (zf.AlternateEncodingUsage == ZipOption.Always)
  173. ? zf.AlternateEncoding
  174. : ZipFile.DefaultEncoding;
  175. while (true)
  176. {
  177. int signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
  178. // return null if this is not a local file header signature
  179. if (IsNotValidZipDirEntrySig(signature))
  180. {
  181. s.Seek(-4, System.IO.SeekOrigin.Current);
  182. // workitem 10178
  183. Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
  184. // Getting "not a ZipDirEntry signature" here is not always wrong or an
  185. // error. This can happen when walking through a zipfile. After the
  186. // last ZipDirEntry, we expect to read an
  187. // EndOfCentralDirectorySignature. When we get this is how we know
  188. // we've reached the end of the central directory.
  189. if (signature != ZipConstants.EndOfCentralDirectorySignature &&
  190. signature != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature &&
  191. signature != ZipConstants.ZipEntrySignature // workitem 8299
  192. )
  193. {
  194. throw new BadReadException(String.Format(" Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, s.Position));
  195. }
  196. return null;
  197. }
  198. int bytesRead = 42 + 4;
  199. byte[] block = new byte[42];
  200. int n = s.Read(block, 0, block.Length);
  201. if (n != block.Length) return null;
  202. int i = 0;
  203. ZipEntry zde = new ZipEntry();
  204. zde.AlternateEncoding = expectedEncoding;
  205. zde._Source = ZipEntrySource.ZipFile;
  206. zde._container = new ZipContainer(zf);
  207. unchecked
  208. {
  209. zde._VersionMadeBy = (short)(block[i++] + block[i++] * 256);
  210. zde._VersionNeeded = (short)(block[i++] + block[i++] * 256);
  211. zde._BitField = (short)(block[i++] + block[i++] * 256);
  212. zde._CompressionMethod = (Int16)(block[i++] + block[i++] * 256);
  213. zde._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
  214. zde._LastModified = Ionic.Zip.SharedUtilities.PackedToDateTime(zde._TimeBlob);
  215. zde._timestamp |= ZipEntryTimestamp.DOS;
  216. zde._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
  217. zde._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  218. zde._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  219. }
  220. // preserve
  221. zde._CompressionMethod_FromZipFile = zde._CompressionMethod;
  222. zde._filenameLength = (short)(block[i++] + block[i++] * 256);
  223. zde._extraFieldLength = (short)(block[i++] + block[i++] * 256);
  224. zde._commentLength = (short)(block[i++] + block[i++] * 256);
  225. zde._diskNumber = (UInt32)(block[i++] + block[i++] * 256);
  226. zde._InternalFileAttrs = (short)(block[i++] + block[i++] * 256);
  227. zde._ExternalFileAttrs = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
  228. zde._RelativeOffsetOfLocalHeader = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
  229. // workitem 7801
  230. zde.IsText = ((zde._InternalFileAttrs & 0x01) == 0x01);
  231. block = new byte[zde._filenameLength];
  232. n = s.Read(block, 0, block.Length);
  233. bytesRead += n;
  234. if ((zde._BitField & 0x0800) == 0x0800)
  235. {
  236. // UTF-8 is in use
  237. zde._FileNameInArchive = Ionic.Zip.SharedUtilities.Utf8StringFromBuffer(block);
  238. }
  239. else
  240. {
  241. zde._FileNameInArchive = Ionic.Zip.SharedUtilities.StringFromBuffer(block, expectedEncoding);
  242. }
  243. // workitem 10330
  244. // insure unique entry names
  245. while (!zf.IgnoreDuplicateFiles && previouslySeen.ContainsKey(zde._FileNameInArchive))
  246. {
  247. zde._FileNameInArchive = CopyHelper.AppendCopyToFileName(zde._FileNameInArchive);
  248. zde._metadataChanged = true;
  249. }
  250. if (zde.AttributesIndicateDirectory)
  251. zde.MarkAsDirectory(); // may append a slash to filename if nec.
  252. // workitem 6898
  253. else if (zde._FileNameInArchive.EndsWith("/")) zde.MarkAsDirectory();
  254. zde._CompressedFileDataSize = zde._CompressedSize;
  255. if ((zde._BitField & 0x01) == 0x01)
  256. {
  257. // this may change after processing the Extra field
  258. zde._Encryption_FromZipFile = zde._Encryption =
  259. EncryptionAlgorithm.PkzipWeak;
  260. zde._sourceIsEncrypted = true;
  261. }
  262. if (zde._extraFieldLength > 0)
  263. {
  264. zde._InputUsesZip64 = (zde._CompressedSize == 0xFFFFFFFF ||
  265. zde._UncompressedSize == 0xFFFFFFFF ||
  266. zde._RelativeOffsetOfLocalHeader == 0xFFFFFFFF);
  267. // Console.WriteLine(" Input uses Z64?: {0}", zde._InputUsesZip64);
  268. bytesRead += zde.ProcessExtraField(s, zde._extraFieldLength);
  269. zde._CompressedFileDataSize = zde._CompressedSize;
  270. }
  271. // we've processed the extra field, so we know the encryption method is set now.
  272. if (zde._Encryption == EncryptionAlgorithm.PkzipWeak)
  273. {
  274. // the "encryption header" of 12 bytes precedes the file data
  275. zde._CompressedFileDataSize -= 12;
  276. }
  277. #if AESCRYPTO
  278. else if (zde.Encryption == EncryptionAlgorithm.WinZipAes128 ||
  279. zde.Encryption == EncryptionAlgorithm.WinZipAes256)
  280. {
  281. zde._CompressedFileDataSize = zde.CompressedSize -
  282. (ZipEntry.GetLengthOfCryptoHeaderBytes(zde.Encryption) + 10);
  283. zde._LengthOfTrailer = 10;
  284. }
  285. #endif
  286. // tally the trailing descriptor
  287. if ((zde._BitField & 0x0008) == 0x0008)
  288. {
  289. // sig, CRC, Comp and Uncomp sizes
  290. if (zde._InputUsesZip64)
  291. zde._LengthOfTrailer += 24;
  292. else
  293. zde._LengthOfTrailer += 16;
  294. }
  295. // workitem 12744
  296. zde.AlternateEncoding = ((zde._BitField & 0x0800) == 0x0800)
  297. ? System.Text.Encoding.UTF8
  298. :expectedEncoding;
  299. zde.AlternateEncodingUsage = ZipOption.Always;
  300. if (zde._commentLength > 0)
  301. {
  302. block = new byte[zde._commentLength];
  303. n = s.Read(block, 0, block.Length);
  304. bytesRead += n;
  305. if ((zde._BitField & 0x0800) == 0x0800)
  306. {
  307. // UTF-8 is in use
  308. zde._Comment = Ionic.Zip.SharedUtilities.Utf8StringFromBuffer(block);
  309. }
  310. else
  311. {
  312. zde._Comment = Ionic.Zip.SharedUtilities.StringFromBuffer(block, expectedEncoding);
  313. }
  314. }
  315. //zde._LengthOfDirEntry = bytesRead;
  316. if (zf.IgnoreDuplicateFiles && previouslySeen.ContainsKey(zde._FileNameInArchive))
  317. {
  318. continue;
  319. }
  320. return zde;
  321. }
  322. }
  323. /// <summary>
  324. /// Returns true if the passed-in value is a valid signature for a ZipDirEntry.
  325. /// </summary>
  326. /// <param name="signature">the candidate 4-byte signature value.</param>
  327. /// <returns>true, if the signature is valid according to the PKWare spec.</returns>
  328. internal static bool IsNotValidZipDirEntrySig(int signature)
  329. {
  330. return (signature != ZipConstants.ZipDirEntrySignature);
  331. }
  332. private Int16 _VersionMadeBy;
  333. private Int16 _InternalFileAttrs;
  334. private Int32 _ExternalFileAttrs;
  335. //private Int32 _LengthOfDirEntry;
  336. private Int16 _filenameLength;
  337. private Int16 _extraFieldLength;
  338. private Int16 _commentLength;
  339. }
  340. }