123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905 |
- // 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;
- namespace 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 FileNotFoundException(String.Format("Could not find file '{0}'.", fileName), fileName);
- long fileLength;
- 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 System.Security.Permissions.SecurityPermission(
- System.Security.Permissions.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)
- {
- #if IOS
- return 0;
- #else
- return unchecked((uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex1));
- #endif
- }
- }
- /// <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>
- public 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);
- }
- }
- }
|