| // 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.ASCII; |
| 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); |
| } |
| } |
| |
| |
| } |