Shared.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. // Shared.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: <2011-August-02 19:41:01>
  18. //
  19. // ------------------------------------------------------------------
  20. //
  21. // This module defines some shared utility classes and methods.
  22. //
  23. // Created: Tue, 27 Mar 2007 15:30
  24. //
  25. using System;
  26. using System.IO;
  27. namespace Ionic.Zip
  28. {
  29. /// <summary>
  30. /// Collects general purpose utility methods.
  31. /// </summary>
  32. internal static class SharedUtilities
  33. {
  34. /// private null constructor
  35. //private SharedUtilities() { }
  36. // workitem 8423
  37. public static Int64 GetFileLength(string fileName)
  38. {
  39. if (!File.Exists(fileName))
  40. throw new FileNotFoundException(String.Format("Could not find file '{0}'.", fileName), fileName);
  41. long fileLength;
  42. FileShare fs = FileShare.ReadWrite;
  43. #if !NETCF
  44. // FileShare.Delete is not defined for the Compact Framework
  45. fs |= FileShare.Delete;
  46. #endif
  47. using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, fs))
  48. {
  49. fileLength = s.Length;
  50. }
  51. return fileLength;
  52. }
  53. [System.Diagnostics.Conditional("NETCF")]
  54. public static void Workaround_Ladybug318918(Stream s)
  55. {
  56. // This is a workaround for this issue:
  57. // https://connect.microsoft.com/VisualStudio/feedback/details/318918
  58. // It's required only on NETCF.
  59. s.Flush();
  60. }
  61. #if LEGACY
  62. /// <summary>
  63. /// Round the given DateTime value to an even second value.
  64. /// </summary>
  65. ///
  66. /// <remarks>
  67. /// <para>
  68. /// Round up in the case of an odd second value. The rounding does not consider
  69. /// fractional seconds.
  70. /// </para>
  71. /// <para>
  72. /// This is useful because the Zip spec allows storage of time only to the nearest
  73. /// even second. So if you want to compare the time of an entry in the archive with
  74. /// it's actual time in the filesystem, you need to round the actual filesystem
  75. /// time, or use a 2-second threshold for the comparison.
  76. /// </para>
  77. /// <para>
  78. /// This is most nautrally an extension method for the DateTime class but this
  79. /// library is built for .NET 2.0, not for .NET 3.5; This means extension methods
  80. /// are a no-no.
  81. /// </para>
  82. /// </remarks>
  83. /// <param name="source">The DateTime value to round</param>
  84. /// <returns>The ruonded DateTime value</returns>
  85. public static DateTime RoundToEvenSecond(DateTime source)
  86. {
  87. // round to nearest second:
  88. if ((source.Second % 2) == 1)
  89. source += new TimeSpan(0, 0, 1);
  90. DateTime dtRounded = new DateTime(source.Year, source.Month, source.Day, source.Hour, source.Minute, source.Second);
  91. //if (source.Millisecond >= 500) dtRounded = dtRounded.AddSeconds(1);
  92. return dtRounded;
  93. }
  94. #endif
  95. #if YOU_LIKE_REDUNDANT_CODE
  96. internal static string NormalizePath(string path)
  97. {
  98. // remove leading single dot slash
  99. if (path.StartsWith(".\\")) path = path.Substring(2);
  100. // remove intervening dot-slash
  101. path = path.Replace("\\.\\", "\\");
  102. // remove double dot when preceded by a directory name
  103. var re = new System.Text.RegularExpressions.Regex(@"^(.*\\)?([^\\\.]+\\\.\.\\)(.+)$");
  104. path = re.Replace(path, "$1$3");
  105. return path;
  106. }
  107. #endif
  108. private static System.Text.RegularExpressions.Regex doubleDotRegex1 =
  109. new System.Text.RegularExpressions.Regex(@"^(.*/)?([^/\\.]+/\\.\\./)(.+)$");
  110. private static string SimplifyFwdSlashPath(string path)
  111. {
  112. if (path.StartsWith("./")) path = path.Substring(2);
  113. path = path.Replace("/./", "/");
  114. // Replace foo/anything/../bar with foo/bar
  115. path = doubleDotRegex1.Replace(path, "$1$3");
  116. return path;
  117. }
  118. /// <summary>
  119. /// Utility routine for transforming path names from filesystem format (on Windows that means backslashes) to
  120. /// a format suitable for use within zipfiles. This means trimming the volume letter and colon (if any) And
  121. /// swapping backslashes for forward slashes.
  122. /// </summary>
  123. /// <param name="pathName">source path.</param>
  124. /// <returns>transformed path</returns>
  125. public static string NormalizePathForUseInZipFile(string pathName)
  126. {
  127. // boundary case
  128. if (String.IsNullOrEmpty(pathName)) return pathName;
  129. // trim volume if necessary
  130. if ((pathName.Length >= 2) && ((pathName[1] == ':') && (pathName[2] == '\\')))
  131. pathName = pathName.Substring(3);
  132. // swap slashes
  133. pathName = pathName.Replace('\\', '/');
  134. // trim all leading slashes
  135. while (pathName.StartsWith("/")) pathName = pathName.Substring(1);
  136. return SimplifyFwdSlashPath(pathName);
  137. }
  138. static System.Text.Encoding ibm437 = System.Text.Encoding.GetEncoding("IBM437");
  139. static System.Text.Encoding utf8 = System.Text.Encoding.GetEncoding("UTF-8");
  140. internal static byte[] StringToByteArray(string value, System.Text.Encoding encoding)
  141. {
  142. byte[] a = encoding.GetBytes(value);
  143. return a;
  144. }
  145. internal static byte[] StringToByteArray(string value)
  146. {
  147. return StringToByteArray(value, ibm437);
  148. }
  149. //internal static byte[] Utf8StringToByteArray(string value)
  150. //{
  151. // return StringToByteArray(value, utf8);
  152. //}
  153. //internal static string StringFromBuffer(byte[] buf, int maxlength)
  154. //{
  155. // return StringFromBuffer(buf, maxlength, ibm437);
  156. //}
  157. internal static string Utf8StringFromBuffer(byte[] buf)
  158. {
  159. return StringFromBuffer(buf, utf8);
  160. }
  161. internal static string StringFromBuffer(byte[] buf, System.Text.Encoding encoding)
  162. {
  163. // this form of the GetString() method is required for .NET CF compatibility
  164. string s = encoding.GetString(buf, 0, buf.Length);
  165. return s;
  166. }
  167. internal static int ReadSignature(System.IO.Stream s)
  168. {
  169. int x = 0;
  170. try { x = _ReadFourBytes(s, "n/a"); }
  171. catch (BadReadException) { }
  172. return x;
  173. }
  174. internal static int ReadEntrySignature(System.IO.Stream s)
  175. {
  176. // handle the case of ill-formatted zip archives - includes a data descriptor
  177. // when none is expected.
  178. int x = 0;
  179. try
  180. {
  181. x = _ReadFourBytes(s, "n/a");
  182. if (x == ZipConstants.ZipEntryDataDescriptorSignature)
  183. {
  184. // advance past data descriptor - 12 bytes if not zip64
  185. s.Seek(12, SeekOrigin.Current);
  186. // workitem 10178
  187. Workaround_Ladybug318918(s);
  188. x = _ReadFourBytes(s, "n/a");
  189. if (x != ZipConstants.ZipEntrySignature)
  190. {
  191. // Maybe zip64 was in use for the prior entry.
  192. // Therefore, skip another 8 bytes.
  193. s.Seek(8, SeekOrigin.Current);
  194. // workitem 10178
  195. Workaround_Ladybug318918(s);
  196. x = _ReadFourBytes(s, "n/a");
  197. if (x != ZipConstants.ZipEntrySignature)
  198. {
  199. // seek back to the first spot
  200. s.Seek(-24, SeekOrigin.Current);
  201. // workitem 10178
  202. Workaround_Ladybug318918(s);
  203. x = _ReadFourBytes(s, "n/a");
  204. }
  205. }
  206. }
  207. }
  208. catch (BadReadException) { }
  209. return x;
  210. }
  211. internal static int ReadInt(System.IO.Stream s)
  212. {
  213. return _ReadFourBytes(s, "Could not read block - no data! (position 0x{0:X8})");
  214. }
  215. private static int _ReadFourBytes(System.IO.Stream s, string message)
  216. {
  217. int n = 0;
  218. byte[] block = new byte[4];
  219. #if NETCF
  220. // workitem 9181
  221. // Reading here in NETCF sometimes reads "backwards". Seems to happen for
  222. // larger files. Not sure why. Maybe an error in caching. If the data is:
  223. //
  224. // 00100210: 9efa 0f00 7072 6f6a 6563 742e 6963 7750 ....project.icwP
  225. // 00100220: 4b05 0600 0000 0006 0006 0091 0100 008e K...............
  226. // 00100230: 0010 0000 00 .....
  227. //
  228. // ...and the stream Position is 10021F, then a Read of 4 bytes is returning
  229. // 50776369, instead of 06054b50. This seems to happen the 2nd time Read()
  230. // is called from that Position..
  231. //
  232. // submitted to connect.microsoft.com
  233. // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=318918#tabs
  234. //
  235. for (int i = 0; i < block.Length; i++)
  236. {
  237. n+= s.Read(block, i, 1);
  238. }
  239. #else
  240. n = s.Read(block, 0, block.Length);
  241. #endif
  242. if (n != block.Length) throw new BadReadException(String.Format(message, s.Position));
  243. int data = unchecked((((block[3] * 256 + block[2]) * 256) + block[1]) * 256 + block[0]);
  244. return data;
  245. }
  246. /// <summary>
  247. /// Finds a signature in the zip stream. This is useful for finding
  248. /// the end of a zip entry, for example, or the beginning of the next ZipEntry.
  249. /// </summary>
  250. ///
  251. /// <remarks>
  252. /// <para>
  253. /// Scans through 64k at a time.
  254. /// </para>
  255. ///
  256. /// <para>
  257. /// If the method fails to find the requested signature, the stream Position
  258. /// after completion of this method is unchanged. If the method succeeds in
  259. /// finding the requested signature, the stream position after completion is
  260. /// direct AFTER the signature found in the stream.
  261. /// </para>
  262. /// </remarks>
  263. ///
  264. /// <param name="stream">The stream to search</param>
  265. /// <param name="SignatureToFind">The 4-byte signature to find</param>
  266. /// <returns>The number of bytes read</returns>
  267. internal static long FindSignature(System.IO.Stream stream, int SignatureToFind)
  268. {
  269. long startingPosition = stream.Position;
  270. int BATCH_SIZE = 65536; // 8192;
  271. byte[] targetBytes = new byte[4];
  272. targetBytes[0] = (byte)(SignatureToFind >> 24);
  273. targetBytes[1] = (byte)((SignatureToFind & 0x00FF0000) >> 16);
  274. targetBytes[2] = (byte)((SignatureToFind & 0x0000FF00) >> 8);
  275. targetBytes[3] = (byte)(SignatureToFind & 0x000000FF);
  276. byte[] batch = new byte[BATCH_SIZE];
  277. int n = 0;
  278. bool success = false;
  279. do
  280. {
  281. n = stream.Read(batch, 0, batch.Length);
  282. if (n != 0)
  283. {
  284. for (int i = 0; i < n; i++)
  285. {
  286. if (batch[i] == targetBytes[3])
  287. {
  288. long curPosition = stream.Position;
  289. stream.Seek(i - n, System.IO.SeekOrigin.Current);
  290. // workitem 10178
  291. Workaround_Ladybug318918(stream);
  292. // workitem 7711
  293. int sig = ReadSignature(stream);
  294. success = (sig == SignatureToFind);
  295. if (!success)
  296. {
  297. stream.Seek(curPosition, System.IO.SeekOrigin.Begin);
  298. // workitem 10178
  299. Workaround_Ladybug318918(stream);
  300. }
  301. else
  302. break; // out of for loop
  303. }
  304. }
  305. }
  306. else break;
  307. if (success) break;
  308. } while (true);
  309. if (!success)
  310. {
  311. stream.Seek(startingPosition, System.IO.SeekOrigin.Begin);
  312. // workitem 10178
  313. Workaround_Ladybug318918(stream);
  314. return -1; // or throw?
  315. }
  316. // subtract 4 for the signature.
  317. long bytesRead = (stream.Position - startingPosition) - 4;
  318. return bytesRead;
  319. }
  320. // If I have a time in the .NET environment, and I want to use it for
  321. // SetWastWriteTime() etc, then I need to adjust it for Win32.
  322. internal static DateTime AdjustTime_Reverse(DateTime time)
  323. {
  324. if (time.Kind == DateTimeKind.Utc) return time;
  325. DateTime adjusted = time;
  326. if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime())
  327. adjusted = time - new System.TimeSpan(1, 0, 0);
  328. else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime())
  329. adjusted = time + new System.TimeSpan(1, 0, 0);
  330. return adjusted;
  331. }
  332. #if NECESSARY
  333. // If I read a time from a file with GetLastWriteTime() (etc), I need
  334. // to adjust it for display in the .NET environment.
  335. internal static DateTime AdjustTime_Forward(DateTime time)
  336. {
  337. if (time.Kind == DateTimeKind.Utc) return time;
  338. DateTime adjusted = time;
  339. if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime())
  340. adjusted = time + new System.TimeSpan(1, 0, 0);
  341. else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime())
  342. adjusted = time - new System.TimeSpan(1, 0, 0);
  343. return adjusted;
  344. }
  345. #endif
  346. internal static DateTime PackedToDateTime(Int32 packedDateTime)
  347. {
  348. // workitem 7074 & workitem 7170
  349. if (packedDateTime == 0xFFFF || packedDateTime == 0)
  350. return new System.DateTime(1995, 1, 1, 0, 0, 0, 0); // return a fixed date when none is supplied.
  351. Int16 packedTime = unchecked((Int16)(packedDateTime & 0x0000ffff));
  352. Int16 packedDate = unchecked((Int16)((packedDateTime & 0xffff0000) >> 16));
  353. int year = 1980 + ((packedDate & 0xFE00) >> 9);
  354. int month = (packedDate & 0x01E0) >> 5;
  355. int day = packedDate & 0x001F;
  356. int hour = (packedTime & 0xF800) >> 11;
  357. int minute = (packedTime & 0x07E0) >> 5;
  358. //int second = packedTime & 0x001F;
  359. int second = (packedTime & 0x001F) * 2;
  360. // validation and error checking.
  361. // this is not foolproof but will catch most errors.
  362. if (second >= 60) { minute++; second = 0; }
  363. if (minute >= 60) { hour++; minute = 0; }
  364. if (hour >= 24) { day++; hour = 0; }
  365. DateTime d = System.DateTime.Now;
  366. bool success= false;
  367. try
  368. {
  369. d = new System.DateTime(year, month, day, hour, minute, second, 0);
  370. success= true;
  371. }
  372. catch (System.ArgumentOutOfRangeException)
  373. {
  374. if (year == 1980 && (month == 0 || day == 0))
  375. {
  376. try
  377. {
  378. d = new System.DateTime(1980, 1, 1, hour, minute, second, 0);
  379. success= true;
  380. }
  381. catch (System.ArgumentOutOfRangeException)
  382. {
  383. try
  384. {
  385. d = new System.DateTime(1980, 1, 1, 0, 0, 0, 0);
  386. success= true;
  387. }
  388. catch (System.ArgumentOutOfRangeException) { }
  389. }
  390. }
  391. // workitem 8814
  392. // my god, I can't believe how many different ways applications
  393. // can mess up a simple date format.
  394. else
  395. {
  396. try
  397. {
  398. while (year < 1980) year++;
  399. while (year > 2030) year--;
  400. while (month < 1) month++;
  401. while (month > 12) month--;
  402. while (day < 1) day++;
  403. while (day > 28) day--;
  404. while (minute < 0) minute++;
  405. while (minute > 59) minute--;
  406. while (second < 0) second++;
  407. while (second > 59) second--;
  408. d = new System.DateTime(year, month, day, hour, minute, second, 0);
  409. success= true;
  410. }
  411. catch (System.ArgumentOutOfRangeException) { }
  412. }
  413. }
  414. if (!success)
  415. {
  416. string msg = String.Format("y({0}) m({1}) d({2}) h({3}) m({4}) s({5})", year, month, day, hour, minute, second);
  417. throw new ZipException(String.Format("Bad date/time format in the zip file. ({0})", msg));
  418. }
  419. // workitem 6191
  420. //d = AdjustTime_Reverse(d);
  421. d = DateTime.SpecifyKind(d, DateTimeKind.Local);
  422. return d;
  423. }
  424. internal
  425. static Int32 DateTimeToPacked(DateTime time)
  426. {
  427. // The time is passed in here only for purposes of writing LastModified to the
  428. // zip archive. It should always be LocalTime, but we convert anyway. And,
  429. // since the time is being written out, it needs to be adjusted.
  430. time = time.ToLocalTime();
  431. // workitem 7966
  432. //time = AdjustTime_Forward(time);
  433. // see http://www.vsft.com/hal/dostime.htm for the format
  434. UInt16 packedDate = (UInt16)((time.Day & 0x0000001F) | ((time.Month << 5) & 0x000001E0) | (((time.Year - 1980) << 9) & 0x0000FE00));
  435. UInt16 packedTime = (UInt16)((time.Second / 2 & 0x0000001F) | ((time.Minute << 5) & 0x000007E0) | ((time.Hour << 11) & 0x0000F800));
  436. Int32 result = (Int32)(((UInt32)(packedDate << 16)) | packedTime);
  437. return result;
  438. }
  439. /// <summary>
  440. /// Create a pseudo-random filename, suitable for use as a temporary
  441. /// file, and open it.
  442. /// </summary>
  443. /// <remarks>
  444. /// <para>
  445. /// The System.IO.Path.GetRandomFileName() method is not available on
  446. /// the Compact Framework, so this library provides its own substitute
  447. /// on NETCF.
  448. /// </para>
  449. /// <para>
  450. /// This method produces a filename of the form
  451. /// DotNetZip-xxxxxxxx.tmp, where xxxxxxxx is replaced by randomly
  452. /// chosen characters, and creates that file.
  453. /// </para>
  454. /// </remarks>
  455. public static void CreateAndOpenUniqueTempFile(string dir,
  456. out Stream fs,
  457. out string filename)
  458. {
  459. // workitem 9763
  460. // http://dotnet.org.za/markn/archive/2006/04/15/51594.aspx
  461. // try 3 times:
  462. for (int i = 0; i < 3; i++)
  463. {
  464. try
  465. {
  466. filename = Path.Combine(dir, InternalGetTempFileName());
  467. fs = new FileStream(filename, FileMode.CreateNew);
  468. return;
  469. }
  470. catch (IOException)
  471. {
  472. if (i == 2) throw;
  473. }
  474. }
  475. throw new IOException();
  476. }
  477. #if NETCF || SILVERLIGHT
  478. public static string InternalGetTempFileName()
  479. {
  480. return "DotNetZip-" + GenerateRandomStringImpl(8,0) + ".tmp";
  481. }
  482. internal static string GenerateRandomStringImpl(int length, int delta)
  483. {
  484. bool WantMixedCase = (delta == 0);
  485. System.Random rnd = new System.Random();
  486. string result = "";
  487. char[] a = new char[length];
  488. for (int i = 0; i < length; i++)
  489. {
  490. // delta == 65 means uppercase
  491. // delta == 97 means lowercase
  492. if (WantMixedCase)
  493. delta = (rnd.Next(2) == 0) ? 65 : 97;
  494. a[i] = (char)(rnd.Next(26) + delta);
  495. }
  496. result = new System.String(a);
  497. return result;
  498. }
  499. #else
  500. public static string InternalGetTempFileName()
  501. {
  502. return "DotNetZip-" + Path.GetRandomFileName().Substring(0, 8) + ".tmp";
  503. }
  504. #endif
  505. /// <summary>
  506. /// Workitem 7889: handle ERROR_LOCK_VIOLATION during read
  507. /// </summary>
  508. /// <remarks>
  509. /// This could be gracefully handled with an extension attribute, but
  510. /// This assembly is built for .NET 2.0, so I cannot use them.
  511. /// </remarks>
  512. internal static int ReadWithRetry(System.IO.Stream s, byte[] buffer, int offset, int count, string FileName)
  513. {
  514. int n = 0;
  515. bool done = false;
  516. #if !NETCF && !SILVERLIGHT
  517. int retries = 0;
  518. #endif
  519. do
  520. {
  521. try
  522. {
  523. n = s.Read(buffer, offset, count);
  524. done = true;
  525. }
  526. #if NETCF || SILVERLIGHT
  527. catch (System.IO.IOException)
  528. {
  529. throw;
  530. }
  531. #else
  532. catch (System.IO.IOException ioexc1)
  533. {
  534. // Check if we can call GetHRForException,
  535. // which makes unmanaged code calls.
  536. var p = new System.Security.Permissions.SecurityPermission(
  537. System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode);
  538. if (p.IsUnrestricted())
  539. {
  540. uint hresult = _HRForException(ioexc1);
  541. if (hresult != 0x80070021) // ERROR_LOCK_VIOLATION
  542. throw new System.IO.IOException(String.Format("Cannot read file {0}", FileName), ioexc1);
  543. retries++;
  544. if (retries > 10)
  545. throw new System.IO.IOException(String.Format("Cannot read file {0}, at offset 0x{1:X8} after 10 retries", FileName, offset), ioexc1);
  546. // max time waited on last retry = 250 + 10*550 = 5.75s
  547. // aggregate time waited after 10 retries: 250 + 55*550 = 30.5s
  548. System.Threading.Thread.Sleep(250 + retries * 550);
  549. }
  550. else
  551. {
  552. // The permission.Demand() failed. Therefore, we cannot call
  553. // GetHRForException, and cannot do the subtle handling of
  554. // ERROR_LOCK_VIOLATION. Just bail.
  555. throw;
  556. }
  557. }
  558. #endif
  559. }
  560. while (!done);
  561. return n;
  562. }
  563. #if !NETCF
  564. // workitem 8009
  565. //
  566. // This method must remain separate.
  567. //
  568. // Marshal.GetHRForException() is needed to do special exception handling for
  569. // the read. But, that method requires UnmanagedCode permissions, and is marked
  570. // with LinkDemand for UnmanagedCode. In an ASP.NET medium trust environment,
  571. // where UnmanagedCode is restricted, will generate a SecurityException at the
  572. // time of JIT of the method that calls a method that is marked with LinkDemand
  573. // for UnmanagedCode. The SecurityException, if it is restricted, will occur
  574. // when this method is JITed.
  575. //
  576. // The Marshal.GetHRForException() is factored out of ReadWithRetry in order to
  577. // avoid the SecurityException at JIT compile time. Because _HRForException is
  578. // called only when the UnmanagedCode is allowed. This means .NET never
  579. // JIT-compiles this method when UnmanagedCode is disallowed, and thus never
  580. // generates the JIT-compile time exception.
  581. //
  582. #endif
  583. private static uint _HRForException(System.Exception ex1)
  584. {
  585. #if IOS
  586. return 0;
  587. #else
  588. return unchecked((uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex1));
  589. #endif
  590. }
  591. }
  592. /// <summary>
  593. /// A decorator stream. It wraps another stream, and performs bookkeeping
  594. /// to keep track of the stream Position.
  595. /// </summary>
  596. /// <remarks>
  597. /// <para>
  598. /// In some cases, it is not possible to get the Position of a stream, let's
  599. /// say, on a write-only output stream like ASP.NET's
  600. /// <c>Response.OutputStream</c>, or on a different write-only stream
  601. /// provided as the destination for the zip by the application. In this
  602. /// case, programmers can use this counting stream to count the bytes read
  603. /// or written.
  604. /// </para>
  605. /// <para>
  606. /// Consider the scenario of an application that saves a self-extracting
  607. /// archive (SFX), that uses a custom SFX stub.
  608. /// </para>
  609. /// <para>
  610. /// Saving to a filesystem file, the application would open the
  611. /// filesystem file (getting a <c>FileStream</c>), save the custom sfx stub
  612. /// into it, and then call <c>ZipFile.Save()</c>, specifying the same
  613. /// FileStream. <c>ZipFile.Save()</c> does the right thing for the zipentry
  614. /// offsets, by inquiring the Position of the <c>FileStream</c> before writing
  615. /// any data, and then adding that initial offset into any ZipEntry
  616. /// offsets in the zip directory. Everything works fine.
  617. /// </para>
  618. /// <para>
  619. /// Now suppose the application is an ASPNET application and it saves
  620. /// directly to <c>Response.OutputStream</c>. It's not possible for DotNetZip to
  621. /// inquire the <c>Position</c>, so the offsets for the SFX will be wrong.
  622. /// </para>
  623. /// <para>
  624. /// The workaround is for the application to use this class to wrap
  625. /// <c>HttpResponse.OutputStream</c>, then write the SFX stub and the ZipFile
  626. /// into that wrapper stream. Because <c>ZipFile.Save()</c> can inquire the
  627. /// <c>Position</c>, it will then do the right thing with the offsets.
  628. /// </para>
  629. /// </remarks>
  630. public class CountingStream : System.IO.Stream
  631. {
  632. // workitem 12374: this class is now public
  633. private System.IO.Stream _s;
  634. private Int64 _bytesWritten;
  635. private Int64 _bytesRead;
  636. private Int64 _initialOffset;
  637. /// <summary>
  638. /// The constructor.
  639. /// </summary>
  640. /// <param name="stream">The underlying stream</param>
  641. public CountingStream(System.IO.Stream stream)
  642. : base()
  643. {
  644. _s = stream;
  645. try
  646. {
  647. _initialOffset = _s.Position;
  648. }
  649. catch
  650. {
  651. _initialOffset = 0L;
  652. }
  653. }
  654. /// <summary>
  655. /// Gets the wrapped stream.
  656. /// </summary>
  657. public Stream WrappedStream
  658. {
  659. get
  660. {
  661. return _s;
  662. }
  663. }
  664. /// <summary>
  665. /// The count of bytes written out to the stream.
  666. /// </summary>
  667. public Int64 BytesWritten
  668. {
  669. get { return _bytesWritten; }
  670. }
  671. /// <summary>
  672. /// the count of bytes that have been read from the stream.
  673. /// </summary>
  674. public Int64 BytesRead
  675. {
  676. get { return _bytesRead; }
  677. }
  678. /// <summary>
  679. /// Adjust the byte count on the stream.
  680. /// </summary>
  681. ///
  682. /// <param name='delta'>
  683. /// the number of bytes to subtract from the count.
  684. /// </param>
  685. ///
  686. /// <remarks>
  687. /// <para>
  688. /// Subtract delta from the count of bytes written to the stream.
  689. /// This is necessary when seeking back, and writing additional data,
  690. /// as happens in some cases when saving Zip files.
  691. /// </para>
  692. /// </remarks>
  693. public void Adjust(Int64 delta)
  694. {
  695. _bytesWritten -= delta;
  696. if (_bytesWritten < 0)
  697. throw new InvalidOperationException();
  698. if (_s as CountingStream != null)
  699. ((CountingStream)_s).Adjust(delta);
  700. }
  701. /// <summary>
  702. /// The read method.
  703. /// </summary>
  704. /// <param name="buffer">The buffer to hold the data read from the stream.</param>
  705. /// <param name="offset">the offset within the buffer to copy the first byte read.</param>
  706. /// <param name="count">the number of bytes to read.</param>
  707. /// <returns>the number of bytes read, after decryption and decompression.</returns>
  708. public override int Read(byte[] buffer, int offset, int count)
  709. {
  710. int n = _s.Read(buffer, offset, count);
  711. _bytesRead += n;
  712. return n;
  713. }
  714. /// <summary>
  715. /// Write data into the stream.
  716. /// </summary>
  717. /// <param name="buffer">The buffer holding data to write to the stream.</param>
  718. /// <param name="offset">the offset within that data array to find the first byte to write.</param>
  719. /// <param name="count">the number of bytes to write.</param>
  720. public override void Write(byte[] buffer, int offset, int count)
  721. {
  722. if (count == 0) return;
  723. _s.Write(buffer, offset, count);
  724. _bytesWritten += count;
  725. }
  726. /// <summary>
  727. /// Whether the stream can be read.
  728. /// </summary>
  729. public override bool CanRead
  730. {
  731. get { return _s.CanRead; }
  732. }
  733. /// <summary>
  734. /// Whether it is possible to call Seek() on the stream.
  735. /// </summary>
  736. public override bool CanSeek
  737. {
  738. get { return _s.CanSeek; }
  739. }
  740. /// <summary>
  741. /// Whether it is possible to call Write() on the stream.
  742. /// </summary>
  743. public override bool CanWrite
  744. {
  745. get { return _s.CanWrite; }
  746. }
  747. /// <summary>
  748. /// Flushes the underlying stream.
  749. /// </summary>
  750. public override void Flush()
  751. {
  752. _s.Flush();
  753. }
  754. /// <summary>
  755. /// The length of the underlying stream.
  756. /// </summary>
  757. public override long Length
  758. {
  759. get { return _s.Length; } // bytesWritten??
  760. }
  761. /// <summary>
  762. /// Returns the sum of number of bytes written, plus the initial
  763. /// offset before writing.
  764. /// </summary>
  765. public long ComputedPosition
  766. {
  767. get { return _initialOffset + _bytesWritten; }
  768. }
  769. /// <summary>
  770. /// The Position of the stream.
  771. /// </summary>
  772. public override long Position
  773. {
  774. get { return _s.Position; }
  775. set
  776. {
  777. _s.Seek(value, System.IO.SeekOrigin.Begin);
  778. // workitem 10178
  779. Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_s);
  780. }
  781. }
  782. /// <summary>
  783. /// Seek in the stream.
  784. /// </summary>
  785. /// <param name="offset">the offset point to seek to</param>
  786. /// <param name="origin">the reference point from which to seek</param>
  787. /// <returns>The new position</returns>
  788. public override long Seek(long offset, System.IO.SeekOrigin origin)
  789. {
  790. return _s.Seek(offset, origin);
  791. }
  792. /// <summary>
  793. /// Set the length of the underlying stream. Be careful with this!
  794. /// </summary>
  795. ///
  796. /// <param name='value'>the length to set on the underlying stream.</param>
  797. public override void SetLength(long value)
  798. {
  799. _s.SetLength(value);
  800. }
  801. }
  802. }