| // Shared.cs | |
| // ------------------------------------------------------------------ | |
| // | |
| // Copyright (c) 2006-2011 Dino Chiesa. | |
| // All rights reserved. | |
| // | |
| // This code module is part of DotNetZip, a zipfile class library. | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This code is licensed under the Microsoft Public License. | |
| // See the file License.txt for the license details. | |
| // More info on: http://dotnetzip.codeplex.com | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // Last Saved: <2011-August-02 19:41:01> | |
| // | |
| // ------------------------------------------------------------------ | |
| // | |
| // This module defines some shared utility classes and methods. | |
| // | |
| // Created: Tue, 27 Mar 2007 15:30 | |
| // | |
| using System; | |
| using System.IO; | |
| using System.Security.Permissions; | |
| namespace OfficeOpenXml.Packaging.Ionic.Zip | |
| { | |
| /// <summary> | |
| /// Collects general purpose utility methods. | |
| /// </summary> | |
| internal static class SharedUtilities | |
| { | |
| /// private null constructor | |
| //private SharedUtilities() { } | |
| // workitem 8423 | |
| public static Int64 GetFileLength(string fileName) | |
| { | |
| if (!File.Exists(fileName)) | |
| throw new System.IO.FileNotFoundException(fileName); | |
| long fileLength = 0L; | |
| FileShare fs = FileShare.ReadWrite; | |
| #if !NETCF | |
| // FileShare.Delete is not defined for the Compact Framework | |
| fs |= FileShare.Delete; | |
| #endif | |
| using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, fs)) | |
| { | |
| fileLength = s.Length; | |
| } | |
| return fileLength; | |
| } | |
| [System.Diagnostics.Conditional("NETCF")] | |
| public static void Workaround_Ladybug318918(Stream s) | |
| { | |
| // This is a workaround for this issue: | |
| // https://connect.microsoft.com/VisualStudio/feedback/details/318918 | |
| // It's required only on NETCF. | |
| s.Flush(); | |
| } | |
| #if LEGACY | |
| /// <summary> | |
| /// Round the given DateTime value to an even second value. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// <para> | |
| /// Round up in the case of an odd second value. The rounding does not consider | |
| /// fractional seconds. | |
| /// </para> | |
| /// <para> | |
| /// This is useful because the Zip spec allows storage of time only to the nearest | |
| /// even second. So if you want to compare the time of an entry in the archive with | |
| /// it's actual time in the filesystem, you need to round the actual filesystem | |
| /// time, or use a 2-second threshold for the comparison. | |
| /// </para> | |
| /// <para> | |
| /// This is most nautrally an extension method for the DateTime class but this | |
| /// library is built for .NET 2.0, not for .NET 3.5; This means extension methods | |
| /// are a no-no. | |
| /// </para> | |
| /// </remarks> | |
| /// <param name="source">The DateTime value to round</param> | |
| /// <returns>The ruonded DateTime value</returns> | |
| public static DateTime RoundToEvenSecond(DateTime source) | |
| { | |
| // round to nearest second: | |
| if ((source.Second % 2) == 1) | |
| source += new TimeSpan(0, 0, 1); | |
| DateTime dtRounded = new DateTime(source.Year, source.Month, source.Day, source.Hour, source.Minute, source.Second); | |
| //if (source.Millisecond >= 500) dtRounded = dtRounded.AddSeconds(1); | |
| return dtRounded; | |
| } | |
| #endif | |
| #if YOU_LIKE_REDUNDANT_CODE | |
| internal static string NormalizePath(string path) | |
| { | |
| // remove leading single dot slash | |
| if (path.StartsWith(".\\")) path = path.Substring(2); | |
| // remove intervening dot-slash | |
| path = path.Replace("\\.\\", "\\"); | |
| // remove double dot when preceded by a directory name | |
| var re = new System.Text.RegularExpressions.Regex(@"^(.*\\)?([^\\\.]+\\\.\.\\)(.+)$"); | |
| path = re.Replace(path, "$1$3"); | |
| return path; | |
| } | |
| #endif | |
| private static System.Text.RegularExpressions.Regex doubleDotRegex1 = | |
| new System.Text.RegularExpressions.Regex(@"^(.*/)?([^/\\.]+/\\.\\./)(.+)$"); | |
| private static string SimplifyFwdSlashPath(string path) | |
| { | |
| if (path.StartsWith("./")) path = path.Substring(2); | |
| path = path.Replace("/./", "/"); | |
| // Replace foo/anything/../bar with foo/bar | |
| path = doubleDotRegex1.Replace(path, "$1$3"); | |
| return path; | |
| } | |
| /// <summary> | |
| /// Utility routine for transforming path names from filesystem format (on Windows that means backslashes) to | |
| /// a format suitable for use within zipfiles. This means trimming the volume letter and colon (if any) And | |
| /// swapping backslashes for forward slashes. | |
| /// </summary> | |
| /// <param name="pathName">source path.</param> | |
| /// <returns>transformed path</returns> | |
| public static string NormalizePathForUseInZipFile(string pathName) | |
| { | |
| // boundary case | |
| if (String.IsNullOrEmpty(pathName)) return pathName; | |
| // trim volume if necessary | |
| if ((pathName.Length >= 2) && ((pathName[1] == ':') && (pathName[2] == '\\'))) | |
| pathName = pathName.Substring(3); | |
| // swap slashes | |
| pathName = pathName.Replace('\\', '/'); | |
| // trim all leading slashes | |
| while (pathName.StartsWith("/")) pathName = pathName.Substring(1); | |
| return SimplifyFwdSlashPath(pathName); | |
| } | |
| static System.Text.Encoding ibm437 = System.Text.Encoding.GetEncoding("IBM437"); | |
| static System.Text.Encoding utf8 = System.Text.Encoding.GetEncoding("UTF-8"); | |
| internal static byte[] StringToByteArray(string value, System.Text.Encoding encoding) | |
| { | |
| byte[] a = encoding.GetBytes(value); | |
| return a; | |
| } | |
| internal static byte[] StringToByteArray(string value) | |
| { | |
| return StringToByteArray(value, ibm437); | |
| } | |
| //internal static byte[] Utf8StringToByteArray(string value) | |
| //{ | |
| // return StringToByteArray(value, utf8); | |
| //} | |
| //internal static string StringFromBuffer(byte[] buf, int maxlength) | |
| //{ | |
| // return StringFromBuffer(buf, maxlength, ibm437); | |
| //} | |
| internal static string Utf8StringFromBuffer(byte[] buf) | |
| { | |
| return StringFromBuffer(buf, utf8); | |
| } | |
| internal static string StringFromBuffer(byte[] buf, System.Text.Encoding encoding) | |
| { | |
| // this form of the GetString() method is required for .NET CF compatibility | |
| string s = encoding.GetString(buf, 0, buf.Length); | |
| return s; | |
| } | |
| internal static int ReadSignature(System.IO.Stream s) | |
| { | |
| int x = 0; | |
| try { x = _ReadFourBytes(s, "n/a"); } | |
| catch (BadReadException) { } | |
| return x; | |
| } | |
| internal static int ReadEntrySignature(System.IO.Stream s) | |
| { | |
| // handle the case of ill-formatted zip archives - includes a data descriptor | |
| // when none is expected. | |
| int x = 0; | |
| try | |
| { | |
| x = _ReadFourBytes(s, "n/a"); | |
| if (x == ZipConstants.ZipEntryDataDescriptorSignature) | |
| { | |
| // advance past data descriptor - 12 bytes if not zip64 | |
| s.Seek(12, SeekOrigin.Current); | |
| // workitem 10178 | |
| Workaround_Ladybug318918(s); | |
| x = _ReadFourBytes(s, "n/a"); | |
| if (x != ZipConstants.ZipEntrySignature) | |
| { | |
| // Maybe zip64 was in use for the prior entry. | |
| // Therefore, skip another 8 bytes. | |
| s.Seek(8, SeekOrigin.Current); | |
| // workitem 10178 | |
| Workaround_Ladybug318918(s); | |
| x = _ReadFourBytes(s, "n/a"); | |
| if (x != ZipConstants.ZipEntrySignature) | |
| { | |
| // seek back to the first spot | |
| s.Seek(-24, SeekOrigin.Current); | |
| // workitem 10178 | |
| Workaround_Ladybug318918(s); | |
| x = _ReadFourBytes(s, "n/a"); | |
| } | |
| } | |
| } | |
| } | |
| catch (BadReadException) { } | |
| return x; | |
| } | |
| internal static int ReadInt(System.IO.Stream s) | |
| { | |
| return _ReadFourBytes(s, "Could not read block - no data! (position 0x{0:X8})"); | |
| } | |
| private static int _ReadFourBytes(System.IO.Stream s, string message) | |
| { | |
| int n = 0; | |
| byte[] block = new byte[4]; | |
| #if NETCF | |
| // workitem 9181 | |
| // Reading here in NETCF sometimes reads "backwards". Seems to happen for | |
| // larger files. Not sure why. Maybe an error in caching. If the data is: | |
| // | |
| // 00100210: 9efa 0f00 7072 6f6a 6563 742e 6963 7750 ....project.icwP | |
| // 00100220: 4b05 0600 0000 0006 0006 0091 0100 008e K............... | |
| // 00100230: 0010 0000 00 ..... | |
| // | |
| // ...and the stream Position is 10021F, then a Read of 4 bytes is returning | |
| // 50776369, instead of 06054b50. This seems to happen the 2nd time Read() | |
| // is called from that Position.. | |
| // | |
| // submitted to connect.microsoft.com | |
| // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=318918#tabs | |
| // | |
| for (int i = 0; i < block.Length; i++) | |
| { | |
| n+= s.Read(block, i, 1); | |
| } | |
| #else | |
| n = s.Read(block, 0, block.Length); | |
| #endif | |
| if (n != block.Length) throw new BadReadException(String.Format(message, s.Position)); | |
| int data = unchecked((((block[3] * 256 + block[2]) * 256) + block[1]) * 256 + block[0]); | |
| return data; | |
| } | |
| /// <summary> | |
| /// Finds a signature in the zip stream. This is useful for finding | |
| /// the end of a zip entry, for example, or the beginning of the next ZipEntry. | |
| /// </summary> | |
| /// | |
| /// <remarks> | |
| /// <para> | |
| /// Scans through 64k at a time. | |
| /// </para> | |
| /// | |
| /// <para> | |
| /// If the method fails to find the requested signature, the stream Position | |
| /// after completion of this method is unchanged. If the method succeeds in | |
| /// finding the requested signature, the stream position after completion is | |
| /// direct AFTER the signature found in the stream. | |
| /// </para> | |
| /// </remarks> | |
| /// | |
| /// <param name="stream">The stream to search</param> | |
| /// <param name="SignatureToFind">The 4-byte signature to find</param> | |
| /// <returns>The number of bytes read</returns> | |
| internal static long FindSignature(System.IO.Stream stream, int SignatureToFind) | |
| { | |
| long startingPosition = stream.Position; | |
| int BATCH_SIZE = 65536; // 8192; | |
| byte[] targetBytes = new byte[4]; | |
| targetBytes[0] = (byte)(SignatureToFind >> 24); | |
| targetBytes[1] = (byte)((SignatureToFind & 0x00FF0000) >> 16); | |
| targetBytes[2] = (byte)((SignatureToFind & 0x0000FF00) >> 8); | |
| targetBytes[3] = (byte)(SignatureToFind & 0x000000FF); | |
| byte[] batch = new byte[BATCH_SIZE]; | |
| int n = 0; | |
| bool success = false; | |
| do | |
| { | |
| n = stream.Read(batch, 0, batch.Length); | |
| if (n != 0) | |
| { | |
| for (int i = 0; i < n; i++) | |
| { | |
| if (batch[i] == targetBytes[3]) | |
| { | |
| long curPosition = stream.Position; | |
| stream.Seek(i - n, System.IO.SeekOrigin.Current); | |
| // workitem 10178 | |
| Workaround_Ladybug318918(stream); | |
| // workitem 7711 | |
| int sig = ReadSignature(stream); | |
| success = (sig == SignatureToFind); | |
| if (!success) | |
| { | |
| stream.Seek(curPosition, System.IO.SeekOrigin.Begin); | |
| // workitem 10178 | |
| Workaround_Ladybug318918(stream); | |
| } | |
| else | |
| break; // out of for loop | |
| } | |
| } | |
| } | |
| else break; | |
| if (success) break; | |
| } while (true); | |
| if (!success) | |
| { | |
| stream.Seek(startingPosition, System.IO.SeekOrigin.Begin); | |
| // workitem 10178 | |
| Workaround_Ladybug318918(stream); | |
| return -1; // or throw? | |
| } | |
| // subtract 4 for the signature. | |
| long bytesRead = (stream.Position - startingPosition) - 4; | |
| return bytesRead; | |
| } | |
| // If I have a time in the .NET environment, and I want to use it for | |
| // SetWastWriteTime() etc, then I need to adjust it for Win32. | |
| internal static DateTime AdjustTime_Reverse(DateTime time) | |
| { | |
| if (time.Kind == DateTimeKind.Utc) return time; | |
| DateTime adjusted = time; | |
| if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime()) | |
| adjusted = time - new System.TimeSpan(1, 0, 0); | |
| else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime()) | |
| adjusted = time + new System.TimeSpan(1, 0, 0); | |
| return adjusted; | |
| } | |
| #if NECESSARY | |
| // If I read a time from a file with GetLastWriteTime() (etc), I need | |
| // to adjust it for display in the .NET environment. | |
| internal static DateTime AdjustTime_Forward(DateTime time) | |
| { | |
| if (time.Kind == DateTimeKind.Utc) return time; | |
| DateTime adjusted = time; | |
| if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime()) | |
| adjusted = time + new System.TimeSpan(1, 0, 0); | |
| else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime()) | |
| adjusted = time - new System.TimeSpan(1, 0, 0); | |
| return adjusted; | |
| } | |
| #endif | |
| internal static DateTime PackedToDateTime(Int32 packedDateTime) | |
| { | |
| // workitem 7074 & workitem 7170 | |
| if (packedDateTime == 0xFFFF || packedDateTime == 0) | |
| return new System.DateTime(1995, 1, 1, 0, 0, 0, 0); // return a fixed date when none is supplied. | |
| Int16 packedTime = unchecked((Int16)(packedDateTime & 0x0000ffff)); | |
| Int16 packedDate = unchecked((Int16)((packedDateTime & 0xffff0000) >> 16)); | |
| int year = 1980 + ((packedDate & 0xFE00) >> 9); | |
| int month = (packedDate & 0x01E0) >> 5; | |
| int day = packedDate & 0x001F; | |
| int hour = (packedTime & 0xF800) >> 11; | |
| int minute = (packedTime & 0x07E0) >> 5; | |
| //int second = packedTime & 0x001F; | |
| int second = (packedTime & 0x001F) * 2; | |
| // validation and error checking. | |
| // this is not foolproof but will catch most errors. | |
| if (second >= 60) { minute++; second = 0; } | |
| if (minute >= 60) { hour++; minute = 0; } | |
| if (hour >= 24) { day++; hour = 0; } | |
| DateTime d = System.DateTime.Now; | |
| bool success= false; | |
| try | |
| { | |
| d = new System.DateTime(year, month, day, hour, minute, second, 0); | |
| success= true; | |
| } | |
| catch (System.ArgumentOutOfRangeException) | |
| { | |
| if (year == 1980 && (month == 0 || day == 0)) | |
| { | |
| try | |
| { | |
| d = new System.DateTime(1980, 1, 1, hour, minute, second, 0); | |
| success= true; | |
| } | |
| catch (System.ArgumentOutOfRangeException) | |
| { | |
| try | |
| { | |
| d = new System.DateTime(1980, 1, 1, 0, 0, 0, 0); | |
| success= true; | |
| } | |
| catch (System.ArgumentOutOfRangeException) { } | |
| } | |
| } | |
| // workitem 8814 | |
| // my god, I can't believe how many different ways applications | |
| // can mess up a simple date format. | |
| else | |
| { | |
| try | |
| { | |
| while (year < 1980) year++; | |
| while (year > 2030) year--; | |
| while (month < 1) month++; | |
| while (month > 12) month--; | |
| while (day < 1) day++; | |
| while (day > 28) day--; | |
| while (minute < 0) minute++; | |
| while (minute > 59) minute--; | |
| while (second < 0) second++; | |
| while (second > 59) second--; | |
| d = new System.DateTime(year, month, day, hour, minute, second, 0); | |
| success= true; | |
| } | |
| catch (System.ArgumentOutOfRangeException) { } | |
| } | |
| } | |
| if (!success) | |
| { | |
| string msg = String.Format("y({0}) m({1}) d({2}) h({3}) m({4}) s({5})", year, month, day, hour, minute, second); | |
| throw new ZipException(String.Format("Bad date/time format in the zip file. ({0})", msg)); | |
| } | |
| // workitem 6191 | |
| //d = AdjustTime_Reverse(d); | |
| d = DateTime.SpecifyKind(d, DateTimeKind.Local); | |
| return d; | |
| } | |
| internal | |
| static Int32 DateTimeToPacked(DateTime time) | |
| { | |
| // The time is passed in here only for purposes of writing LastModified to the | |
| // zip archive. It should always be LocalTime, but we convert anyway. And, | |
| // since the time is being written out, it needs to be adjusted. | |
| time = time.ToLocalTime(); | |
| // workitem 7966 | |
| //time = AdjustTime_Forward(time); | |
| // see http://www.vsft.com/hal/dostime.htm for the format | |
| UInt16 packedDate = (UInt16)((time.Day & 0x0000001F) | ((time.Month << 5) & 0x000001E0) | (((time.Year - 1980) << 9) & 0x0000FE00)); | |
| UInt16 packedTime = (UInt16)((time.Second / 2 & 0x0000001F) | ((time.Minute << 5) & 0x000007E0) | ((time.Hour << 11) & 0x0000F800)); | |
| Int32 result = (Int32)(((UInt32)(packedDate << 16)) | packedTime); | |
| return result; | |
| } | |
| /// <summary> | |
| /// Create a pseudo-random filename, suitable for use as a temporary | |
| /// file, and open it. | |
| /// </summary> | |
| /// <remarks> | |
| /// <para> | |
| /// The System.IO.Path.GetRandomFileName() method is not available on | |
| /// the Compact Framework, so this library provides its own substitute | |
| /// on NETCF. | |
| /// </para> | |
| /// <para> | |
| /// This method produces a filename of the form | |
| /// DotNetZip-xxxxxxxx.tmp, where xxxxxxxx is replaced by randomly | |
| /// chosen characters, and creates that file. | |
| /// </para> | |
| /// </remarks> | |
| public static void CreateAndOpenUniqueTempFile(string dir, | |
| out Stream fs, | |
| out string filename) | |
| { | |
| // workitem 9763 | |
| // http://dotnet.org.za/markn/archive/2006/04/15/51594.aspx | |
| // try 3 times: | |
| for (int i = 0; i < 3; i++) | |
| { | |
| try | |
| { | |
| filename = Path.Combine(dir, InternalGetTempFileName()); | |
| fs = new FileStream(filename, FileMode.CreateNew); | |
| return; | |
| } | |
| catch (IOException) | |
| { | |
| if (i == 2) throw; | |
| } | |
| } | |
| throw new IOException(); | |
| } | |
| #if NETCF || SILVERLIGHT | |
| public static string InternalGetTempFileName() | |
| { | |
| return "DotNetZip-" + GenerateRandomStringImpl(8,0) + ".tmp"; | |
| } | |
| internal static string GenerateRandomStringImpl(int length, int delta) | |
| { | |
| bool WantMixedCase = (delta == 0); | |
| System.Random rnd = new System.Random(); | |
| string result = ""; | |
| char[] a = new char[length]; | |
| for (int i = 0; i < length; i++) | |
| { | |
| // delta == 65 means uppercase | |
| // delta == 97 means lowercase | |
| if (WantMixedCase) | |
| delta = (rnd.Next(2) == 0) ? 65 : 97; | |
| a[i] = (char)(rnd.Next(26) + delta); | |
| } | |
| result = new System.String(a); | |
| return result; | |
| } | |
| #else | |
| public static string InternalGetTempFileName() | |
| { | |
| return "DotNetZip-" + Path.GetRandomFileName().Substring(0, 8) + ".tmp"; | |
| } | |
| #endif | |
| /// <summary> | |
| /// Workitem 7889: handle ERROR_LOCK_VIOLATION during read | |
| /// </summary> | |
| /// <remarks> | |
| /// This could be gracefully handled with an extension attribute, but | |
| /// This assembly is built for .NET 2.0, so I cannot use them. | |
| /// </remarks> | |
| internal static int ReadWithRetry(System.IO.Stream s, byte[] buffer, int offset, int count, string FileName) | |
| { | |
| int n = 0; | |
| bool done = false; | |
| #if !NETCF && !SILVERLIGHT | |
| int retries = 0; | |
| #endif | |
| do | |
| { | |
| try | |
| { | |
| n = s.Read(buffer, offset, count); | |
| done = true; | |
| } | |
| #if NETCF || SILVERLIGHT | |
| catch (System.IO.IOException) | |
| { | |
| throw; | |
| } | |
| #else | |
| catch (System.IO.IOException ioexc1) | |
| { | |
| // Check if we can call GetHRForException, | |
| // which makes unmanaged code calls. | |
| var p = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode); | |
| if (p.IsUnrestricted()) | |
| { | |
| uint hresult = _HRForException(ioexc1); | |
| if (hresult != 0x80070021) // ERROR_LOCK_VIOLATION | |
| throw new System.IO.IOException(String.Format("Cannot read file {0}", FileName), ioexc1); | |
| retries++; | |
| if (retries > 10) | |
| throw new System.IO.IOException(String.Format("Cannot read file {0}, at offset 0x{1:X8} after 10 retries", FileName, offset), ioexc1); | |
| // max time waited on last retry = 250 + 10*550 = 5.75s | |
| // aggregate time waited after 10 retries: 250 + 55*550 = 30.5s | |
| System.Threading.Thread.Sleep(250 + retries * 550); | |
| } | |
| else | |
| { | |
| // The permission.Demand() failed. Therefore, we cannot call | |
| // GetHRForException, and cannot do the subtle handling of | |
| // ERROR_LOCK_VIOLATION. Just bail. | |
| throw; | |
| } | |
| } | |
| #endif | |
| } | |
| while (!done); | |
| return n; | |
| } | |
| #if !NETCF | |
| // workitem 8009 | |
| // | |
| // This method must remain separate. | |
| // | |
| // Marshal.GetHRForException() is needed to do special exception handling for | |
| // the read. But, that method requires UnmanagedCode permissions, and is marked | |
| // with LinkDemand for UnmanagedCode. In an ASP.NET medium trust environment, | |
| // where UnmanagedCode is restricted, will generate a SecurityException at the | |
| // time of JIT of the method that calls a method that is marked with LinkDemand | |
| // for UnmanagedCode. The SecurityException, if it is restricted, will occur | |
| // when this method is JITed. | |
| // | |
| // The Marshal.GetHRForException() is factored out of ReadWithRetry in order to | |
| // avoid the SecurityException at JIT compile time. Because _HRForException is | |
| // called only when the UnmanagedCode is allowed. This means .NET never | |
| // JIT-compiles this method when UnmanagedCode is disallowed, and thus never | |
| // generates the JIT-compile time exception. | |
| // | |
| #endif | |
| private static uint _HRForException(System.Exception ex1) | |
| { | |
| return unchecked((uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex1)); | |
| } | |
| } | |
| /// <summary> | |
| /// A decorator stream. It wraps another stream, and performs bookkeeping | |
| /// to keep track of the stream Position. | |
| /// </summary> | |
| /// <remarks> | |
| /// <para> | |
| /// In some cases, it is not possible to get the Position of a stream, let's | |
| /// say, on a write-only output stream like ASP.NET's | |
| /// <c>Response.OutputStream</c>, or on a different write-only stream | |
| /// provided as the destination for the zip by the application. In this | |
| /// case, programmers can use this counting stream to count the bytes read | |
| /// or written. | |
| /// </para> | |
| /// <para> | |
| /// Consider the scenario of an application that saves a self-extracting | |
| /// archive (SFX), that uses a custom SFX stub. | |
| /// </para> | |
| /// <para> | |
| /// Saving to a filesystem file, the application would open the | |
| /// filesystem file (getting a <c>FileStream</c>), save the custom sfx stub | |
| /// into it, and then call <c>ZipFile.Save()</c>, specifying the same | |
| /// FileStream. <c>ZipFile.Save()</c> does the right thing for the zipentry | |
| /// offsets, by inquiring the Position of the <c>FileStream</c> before writing | |
| /// any data, and then adding that initial offset into any ZipEntry | |
| /// offsets in the zip directory. Everything works fine. | |
| /// </para> | |
| /// <para> | |
| /// Now suppose the application is an ASPNET application and it saves | |
| /// directly to <c>Response.OutputStream</c>. It's not possible for DotNetZip to | |
| /// inquire the <c>Position</c>, so the offsets for the SFX will be wrong. | |
| /// </para> | |
| /// <para> | |
| /// The workaround is for the application to use this class to wrap | |
| /// <c>HttpResponse.OutputStream</c>, then write the SFX stub and the ZipFile | |
| /// into that wrapper stream. Because <c>ZipFile.Save()</c> can inquire the | |
| /// <c>Position</c>, it will then do the right thing with the offsets. | |
| /// </para> | |
| /// </remarks> | |
| internal class CountingStream : System.IO.Stream | |
| { | |
| // workitem 12374: this class is now public | |
| private System.IO.Stream _s; | |
| private Int64 _bytesWritten; | |
| private Int64 _bytesRead; | |
| private Int64 _initialOffset; | |
| /// <summary> | |
| /// The constructor. | |
| /// </summary> | |
| /// <param name="stream">The underlying stream</param> | |
| public CountingStream(System.IO.Stream stream) | |
| : base() | |
| { | |
| _s = stream; | |
| try | |
| { | |
| _initialOffset = _s.Position; | |
| } | |
| catch | |
| { | |
| _initialOffset = 0L; | |
| } | |
| } | |
| /// <summary> | |
| /// Gets the wrapped stream. | |
| /// </summary> | |
| public Stream WrappedStream | |
| { | |
| get | |
| { | |
| return _s; | |
| } | |
| } | |
| /// <summary> | |
| /// The count of bytes written out to the stream. | |
| /// </summary> | |
| public Int64 BytesWritten | |
| { | |
| get { return _bytesWritten; } | |
| } | |
| /// <summary> | |
| /// the count of bytes that have been read from the stream. | |
| /// </summary> | |
| public Int64 BytesRead | |
| { | |
| get { return _bytesRead; } | |
| } | |
| /// <summary> | |
| /// Adjust the byte count on the stream. | |
| /// </summary> | |
| /// | |
| /// <param name='delta'> | |
| /// the number of bytes to subtract from the count. | |
| /// </param> | |
| /// | |
| /// <remarks> | |
| /// <para> | |
| /// Subtract delta from the count of bytes written to the stream. | |
| /// This is necessary when seeking back, and writing additional data, | |
| /// as happens in some cases when saving Zip files. | |
| /// </para> | |
| /// </remarks> | |
| public void Adjust(Int64 delta) | |
| { | |
| _bytesWritten -= delta; | |
| if (_bytesWritten < 0) | |
| throw new InvalidOperationException(); | |
| if (_s as CountingStream != null) | |
| ((CountingStream)_s).Adjust(delta); | |
| } | |
| /// <summary> | |
| /// The read method. | |
| /// </summary> | |
| /// <param name="buffer">The buffer to hold the data read from the stream.</param> | |
| /// <param name="offset">the offset within the buffer to copy the first byte read.</param> | |
| /// <param name="count">the number of bytes to read.</param> | |
| /// <returns>the number of bytes read, after decryption and decompression.</returns> | |
| public override int Read(byte[] buffer, int offset, int count) | |
| { | |
| int n = _s.Read(buffer, offset, count); | |
| _bytesRead += n; | |
| return n; | |
| } | |
| /// <summary> | |
| /// Write data into the stream. | |
| /// </summary> | |
| /// <param name="buffer">The buffer holding data to write to the stream.</param> | |
| /// <param name="offset">the offset within that data array to find the first byte to write.</param> | |
| /// <param name="count">the number of bytes to write.</param> | |
| public override void Write(byte[] buffer, int offset, int count) | |
| { | |
| if (count == 0) return; | |
| _s.Write(buffer, offset, count); | |
| _bytesWritten += count; | |
| } | |
| /// <summary> | |
| /// Whether the stream can be read. | |
| /// </summary> | |
| public override bool CanRead | |
| { | |
| get { return _s.CanRead; } | |
| } | |
| /// <summary> | |
| /// Whether it is possible to call Seek() on the stream. | |
| /// </summary> | |
| public override bool CanSeek | |
| { | |
| get { return _s.CanSeek; } | |
| } | |
| /// <summary> | |
| /// Whether it is possible to call Write() on the stream. | |
| /// </summary> | |
| public override bool CanWrite | |
| { | |
| get { return _s.CanWrite; } | |
| } | |
| /// <summary> | |
| /// Flushes the underlying stream. | |
| /// </summary> | |
| public override void Flush() | |
| { | |
| _s.Flush(); | |
| } | |
| /// <summary> | |
| /// The length of the underlying stream. | |
| /// </summary> | |
| public override long Length | |
| { | |
| get { return _s.Length; } // bytesWritten?? | |
| } | |
| /// <summary> | |
| /// Returns the sum of number of bytes written, plus the initial | |
| /// offset before writing. | |
| /// </summary> | |
| public long ComputedPosition | |
| { | |
| get { return _initialOffset + _bytesWritten; } | |
| } | |
| /// <summary> | |
| /// The Position of the stream. | |
| /// </summary> | |
| public override long Position | |
| { | |
| get { return _s.Position; } | |
| set | |
| { | |
| _s.Seek(value, System.IO.SeekOrigin.Begin); | |
| // workitem 10178 | |
| Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_s); | |
| } | |
| } | |
| /// <summary> | |
| /// Seek in the stream. | |
| /// </summary> | |
| /// <param name="offset">the offset point to seek to</param> | |
| /// <param name="origin">the reference point from which to seek</param> | |
| /// <returns>The new position</returns> | |
| public override long Seek(long offset, System.IO.SeekOrigin origin) | |
| { | |
| return _s.Seek(offset, origin); | |
| } | |
| /// <summary> | |
| /// Set the length of the underlying stream. Be careful with this! | |
| /// </summary> | |
| /// | |
| /// <param name='value'>the length to set on the underlying stream.</param> | |
| public override void SetLength(long value) | |
| { | |
| _s.SetLength(value); | |
| } | |
| } | |
| } |