Free cookie consent management tool by TermsFeed Policy Generator

source: branches/3107_LearningALPS/HeuristicLab.ExtLibs/HeuristicLab.EPPlus/4.0.3/EPPlus-4.0.3/Packaging/DotNetZip/Shared.cs @ 17852

Last change on this file since 17852 was 12074, checked in by sraggl, 10 years ago

#2341: Added EPPlus-4.0.3 to ExtLibs

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