root/trunk/Updater/ICSharpCode.SharpZipLib/Zip/ZipFile.cs @ 597

Wersja 597, 133.6 KB (wprowadzona przez marek, 17 years temu)

re #165

Line 
1// ZipFile.cs
2//
3// Copyright (C) 2001 Mike Krueger
4// Copyright (C) 2004 John Reilly
5//
6// This file was translated from java, it was part of the GNU Classpath
7// Copyright (C) 2001 Free Software Foundation, Inc.
8//
9// This program is free software; you can redistribute it and/or
10// modify it under the terms of the GNU General Public License
11// as published by the Free Software Foundation; either version 2
12// of the License, or (at your option) any later version.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17// GNU General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License
20// along with this program; if not, write to the Free Software
21// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22//
23// Linking this library statically or dynamically with other modules is
24// making a combined work based on this library.  Thus, the terms and
25// conditions of the GNU General Public License cover the whole
26// combination.
27//
28// As a special exception, the copyright holders of this library give you
29// permission to link this library with independent modules to produce an
30// executable, regardless of the license terms of these independent
31// modules, and to copy and distribute the resulting executable under
32// terms of your choice, provided that you also meet, for each linked
33// independent module, the terms and conditions of the license of that
34// module.  An independent module is a module which is not derived from
35// or based on this library.  If you modify this library, you may extend
36// this exception to your version of the library, but you are not
37// obligated to do so.  If you do not wish to do so, delete this
38// exception statement from your version.
39
40using System;
41using System.Collections;
42using System.IO;
43using System.Text;
44using System.Globalization;
45
46#if !NETCF_1_0
47using System.Security.Cryptography;
48using ICSharpCode.SharpZipLib.Encryption;
49#endif
50
51using ICSharpCode.SharpZipLib.Core;
52using ICSharpCode.SharpZipLib.Checksums;
53using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
54using ICSharpCode.SharpZipLib.Zip.Compression;
55
56namespace ICSharpCode.SharpZipLib.Zip
57{
58        #region Keys Required Event Args
59        /// <summary>
60        /// Arguments used with KeysRequiredEvent
61        /// </summary>
62        public class KeysRequiredEventArgs : EventArgs
63        {
64                #region Constructors
65                /// <summary>
66                /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see>
67                /// </summary>
68                /// <param name="name">The name of the file for which keys are required.</param>
69                public KeysRequiredEventArgs(string name)
70                {
71                        fileName = name;
72                }
73       
74                /// <summary>
75                /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see>
76                /// </summary>
77                /// <param name="name">The name of the file for which keys are required.</param>
78                /// <param name="keyValue">The current key value.</param>
79                public KeysRequiredEventArgs(string name, byte[] keyValue)
80                {
81                        fileName = name;
82                        key = keyValue;
83                }
84
85                #endregion
86                #region Properties
87                /// <summary>
88                /// Gets the name of the file for which keys are required.
89                /// </summary>
90                public string FileName
91                {
92                        get { return fileName; }
93                }
94       
95                /// <summary>
96                /// Gets or sets the key value
97                /// </summary>
98                public byte[] Key
99                {
100                        get { return key; }
101                        set { key = value; }
102                }
103                #endregion
104               
105                #region Instance Fields
106                string fileName;
107                byte[] key;
108                #endregion
109        }
110        #endregion
111       
112        #region Test Definitions
113        /// <summary>
114        /// The strategy to apply to testing.
115        /// </summary>
116        public enum TestStrategy
117        {
118                /// <summary>
119                /// Find the first error only.
120                /// </summary>
121                FindFirstError,
122                /// <summary>
123                /// Find all possible errors.
124                /// </summary>
125                FindAllErrors,
126        }
127
128        /// <summary>
129        /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing.
130        /// </summary>
131        /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso>
132        public enum TestOperation
133        {
134                /// <summary>
135                /// Setting up testing.
136                /// </summary>
137                Initialising,
138               
139                /// <summary>
140                /// Testing an individual entries header
141                /// </summary>
142                EntryHeader,
143               
144                /// <summary>
145                /// Testing an individual entries data
146                /// </summary>
147                EntryData,
148               
149                /// <summary>
150                /// Testing an individual entry has completed.
151                /// </summary>
152                EntryComplete,
153               
154                /// <summary>
155                /// Running miscellaneous tests
156                /// </summary>
157                MiscellaneousTests,
158               
159                /// <summary>
160                /// Testing is complete
161                /// </summary>
162                Complete,
163        }
164
165        /// <summary>
166        /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing.
167        /// </summary>
168        /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso>
169        public class TestStatus
170        {
171                #region Constructors
172                /// <summary>
173                /// Initialise a new instance of <see cref="TestStatus"/>
174                /// </summary>
175                /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param>
176                public TestStatus(ZipFile file)
177                {
178                        file_ = file;
179                }
180                #endregion
181               
182                #region Properties
183
184                /// <summary>
185                /// Get the current <see cref="TestOperation"/> in progress.
186                /// </summary>
187                public TestOperation Operation
188                {
189                        get { return operation_; }
190                }
191
192                /// <summary>
193                /// Get the <see cref="ZipFile"/> this status is applicable to.
194                /// </summary>
195                public ZipFile File
196                {
197                        get { return file_; }
198                }
199
200                /// <summary>
201                /// Get the current/last entry tested.
202                /// </summary>
203                public ZipEntry Entry
204                {
205                        get { return entry_; }
206                }
207
208                /// <summary>
209                /// Get the number of errors detected so far.
210                /// </summary>
211                public int ErrorCount
212                {
213                        get { return errorCount_; }
214                }
215
216                /// <summary>
217                /// Get the number of bytes tested so far for the current entry.
218                /// </summary>
219                public long BytesTested
220                {
221                        get { return bytesTested_; }
222                }
223
224                /// <summary>
225                /// Get a value indicating wether the last entry test was valid.
226                /// </summary>
227                public bool EntryValid
228                {
229                        get { return entryValid_; }
230                }
231                #endregion
232               
233                #region Internal API
234                internal void AddError()
235                {
236                        errorCount_++;
237                        entryValid_ = false;
238                }
239
240                internal void SetOperation(TestOperation operation)
241                {
242                        operation_ = operation;
243                }
244
245                internal void SetEntry(ZipEntry entry)
246                {
247                        entry_ = entry;
248                        entryValid_ = true;
249                        bytesTested_ = 0;
250                }
251
252                internal void SetBytesTested(long value)
253                {
254                        bytesTested_ = value;
255                }
256                #endregion
257               
258                #region Instance Fields
259                ZipFile file_;
260                ZipEntry entry_;
261                bool entryValid_;
262                int errorCount_;
263                long bytesTested_;
264                TestOperation operation_;
265                #endregion
266        }
267
268        /// <summary>
269        /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if supplied indicating current progress and status.
270        /// </summary>
271        /// <remarks>If the message is non-null an error has occured.  If the message is null
272        /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks>
273        public delegate void ZipTestResultHandler(TestStatus status, string message);
274        #endregion
275       
276        #region Update Definitions
277        /// <summary>
278        /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive.
279        /// </summary>
280        public enum FileUpdateMode
281        {
282                /// <summary>
283                /// Perform all updates on temporary files ensuring that the original file is saved.
284                /// </summary>
285                Safe,
286                /// <summary>
287                /// Update the archive directly, which is faster but less safe.
288                /// </summary>
289                Direct,
290        }
291        #endregion
292       
293        #region ZipFile Class
294        /// <summary>
295        /// This class represents a Zip archive.  You can ask for the contained
296        /// entries, or get an input stream for a file entry.  The entry is
297        /// automatically decompressed.
298        ///
299        /// You can also update the archive adding or deleting entries.
300        ///
301        /// This class is thread safe for input:  You can open input streams for arbitrary
302        /// entries in different threads.
303        /// <br/>
304        /// <br/>Author of the original java version : Jochen Hoenicke
305        /// </summary>
306        /// <example>
307        /// <code>
308        /// using System;
309        /// using System.Text;
310        /// using System.Collections;
311        /// using System.IO;
312        ///
313        /// using ICSharpCode.SharpZipLib.Zip;
314        ///
315        /// class MainClass
316        /// {
317        ///     static public void Main(string[] args)
318        ///     {
319        ///             using (ZipFile zFile = new ZipFile(args[0])) {
320        ///                     Console.WriteLine("Listing of : " + zFile.Name);
321        ///                     Console.WriteLine("");
322        ///                     Console.WriteLine("Raw Size    Size      Date     Time     Name");
323        ///                     Console.WriteLine("--------  --------  --------  ------  ---------");
324        ///                     foreach (ZipEntry e in zFile) {
325        ///                             if ( e.IsFile ) {
326        ///                                     DateTime d = e.DateTime;
327        ///                                     Console.WriteLine("{0, -10}{1, -10}{2}  {3}   {4}", e.Size, e.CompressedSize,
328        ///                                             d.ToString("dd-MM-yy"), d.ToString("HH:mm"),
329        ///                                             e.Name);
330        ///                             }
331        ///                     }
332        ///             }
333        ///     }
334        /// }
335        /// </code>
336        /// </example>
337        public class ZipFile : IEnumerable, IDisposable
338        {
339                #region KeyHandling
340               
341                /// <summary>
342                /// Delegate for handling keys/password setting during compresion/decompression.
343                /// </summary>
344                public delegate void KeysRequiredEventHandler(
345                        object sender,
346                        KeysRequiredEventArgs e
347                );
348
349                /// <summary>
350                /// Event handler for handling encryption keys.
351                /// </summary>
352                public KeysRequiredEventHandler KeysRequired;
353
354                /// <summary>
355                /// Handles getting of encryption keys when required.
356                /// </summary>
357                /// <param name="fileName">The file for which encryption keys are required.</param>
358                void OnKeysRequired(string fileName)
359                {
360                        if (KeysRequired != null) {
361                                KeysRequiredEventArgs krea = new KeysRequiredEventArgs(fileName, key);
362                                KeysRequired(this, krea);
363                                key = krea.Key;
364                        }
365                }
366               
367                /// <summary>
368                /// Get/set the encryption key value.
369                /// </summary>
370                byte[] Key
371                {
372                        get { return key; }
373                        set { key = value; }
374                }
375               
376#if !NETCF_1_0                         
377                /// <summary>
378                /// Password to be used for encrypting/decrypting files.
379                /// </summary>
380                /// <remarks>Set to null if no password is required.</remarks>
381                public string Password
382                {
383                        set
384                        {
385                                if ( (value == null) || (value.Length == 0) ) {
386                                        key = null;
387                                }
388                                else {
389                                        key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value));
390                                }
391                        }
392                }
393#endif
394
395                /// <summary>
396                /// Get a value indicating wether encryption keys are currently available.
397                /// </summary>
398                bool HaveKeys
399                {
400                        get { return key != null; }
401                }
402                #endregion
403               
404                #region Constructors
405                /// <summary>
406                /// Opens a Zip file with the given name for reading.
407                /// </summary>
408                /// <param name="name">The name of the file to open.</param>
409                /// <exception cref="ArgumentNullException">The argument supplied is null.</exception>
410                /// <exception cref="IOException">
411                /// An i/o error occurs
412                /// </exception>
413                /// <exception cref="ZipException">
414                /// The file doesn't contain a valid zip archive.
415                /// </exception>
416                public ZipFile(string name)
417                {
418                        if ( name == null ) {
419                                throw new ArgumentNullException("name");
420                        }
421                       
422                        name_ = name;
423
424                        baseStream_ = File.OpenRead(name);
425                        isStreamOwner = true;
426                       
427                        try {
428                                ReadEntries();
429                        }
430                        catch {
431                                DisposeInternal(true);
432                                throw;
433                        }
434                }
435               
436                /// <summary>
437                /// Opens a Zip file reading the given <see cref="FileStream"/>.
438                /// </summary>
439                /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param>
440                /// <exception cref="ArgumentNullException">The supplied argument is null.</exception>
441                /// <exception cref="IOException">
442                /// An i/o error occurs.
443                /// </exception>
444                /// <exception cref="ZipException">
445                /// The file doesn't contain a valid zip archive.
446                /// </exception>
447                public ZipFile(FileStream file)
448                {
449                        if ( file == null ) {
450                                throw new ArgumentNullException("file");
451                        }
452
453                        if ( !file.CanSeek ) {
454                                throw new ArgumentException("Stream is not seekable", "file");
455                        }
456
457                        baseStream_  = file;
458                        name_ = file.Name;
459                        isStreamOwner = true;
460                       
461                        try {
462                                ReadEntries();
463                        }
464                        catch {
465                                DisposeInternal(true);
466                                throw;
467                        }
468                }
469               
470                /// <summary>
471                /// Opens a Zip file reading the given <see cref="Stream"/>.
472                /// </summary>
473                /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param>
474                /// <exception cref="IOException">
475                /// An i/o error occurs
476                /// </exception>
477                /// <exception cref="ZipException">
478                /// The stream doesn't contain a valid zip archive.<br/>
479                /// </exception>
480                /// <exception cref="ArgumentException">
481                /// The <see cref="Stream">stream</see> doesnt support seeking.
482                /// </exception>
483                /// <exception cref="ArgumentNullException">
484                /// The <see cref="Stream">stream</see> argument is null.
485                /// </exception>
486                public ZipFile(Stream stream)
487                {
488                        if ( stream == null ) {
489                                throw new ArgumentNullException("stream");
490                        }
491
492                        if ( !stream.CanSeek ) {
493                                throw new ArgumentException("Stream is not seekable", "stream");
494                        }
495
496                        baseStream_  = stream;
497                        isStreamOwner = true;
498               
499                        if ( baseStream_.Length > 0 ) {
500                                try {
501                                        ReadEntries();
502                                }
503                                catch {
504                                        DisposeInternal(true);
505                                        throw;
506                                }
507                        } else {
508                                entries_ = new ZipEntry[0];
509                                isNewArchive_ = true;
510                        }
511                }
512
513                /// <summary>
514                /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage.
515                /// </summary>
516                internal ZipFile()
517                {
518                        entries_ = new ZipEntry[0];
519                        isNewArchive_ = true;
520                }
521               
522                #endregion
523               
524                #region Destructors and Closing
525                /// <summary>
526                /// Finalize this instance.
527                /// </summary>
528                ~ZipFile()
529                {
530                        Dispose(false);
531                }
532               
533                /// <summary>
534                /// Closes the ZipFile.  If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying input stream.
535                /// Once closed, no further instance methods should be called.
536                /// </summary>
537                /// <exception cref="System.IO.IOException">
538                /// An i/o error occurs.
539                /// </exception>
540                public void Close()
541                {
542                        DisposeInternal(true);
543                        GC.SuppressFinalize(this);
544                }
545               
546                #endregion
547               
548                #region Creators
549                /// <summary>
550                /// Create a new <see cref="ZipFile"/> whose data will be stored in a file.
551                /// </summary>
552                /// <param name="fileName">The name of the archive to create.</param>
553                /// <returns>Returns the newly created <see cref="ZipFile"/></returns>
554                /// <exception cref="ArgumentNullException"><paramref name="fileName"></paramref> is null</exception>
555                public static ZipFile Create(string fileName)
556                {
557                        if ( fileName == null ) {
558                                throw new ArgumentNullException("fileName");
559                        }
560
561                        FileStream fs = File.Create(fileName);
562                       
563                        ZipFile result = new ZipFile();
564                        result.name_ = fileName;
565                        result.baseStream_ = fs;
566                        result.isStreamOwner = true;
567                        return result;
568                }
569               
570                /// <summary>
571                /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream.
572                /// </summary>
573                /// <param name="outStream">The stream providing data storage.</param>
574                /// <returns>Returns the newly created <see cref="ZipFile"/></returns>
575                /// <exception cref="ArgumentNullException"><paramref name="outStream"> is null</paramref></exception>
576                /// <exception cref="ArgumentException"><paramref name="outStream"> doesnt support writing.</paramref></exception>
577                public static ZipFile Create(Stream outStream)
578                {
579                        if ( outStream == null ) {
580                                throw new ArgumentNullException("outStream");
581                        }
582
583                        if ( !outStream.CanWrite ) {
584                                throw new ArgumentException("Stream is not writeable", "outStream");
585                        }
586
587                        if ( !outStream.CanSeek ) {
588                                throw new ArgumentException("Stream is not seekable", "outStream");
589                        }
590
591                        ZipFile result = new ZipFile();
592                        result.baseStream_ = outStream;
593                        return result;
594                }
595               
596                #endregion
597               
598                #region Properties
599                /// <summary>
600                /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance.
601                /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called.
602                /// </summary>
603                /// <remarks>
604                /// The default value is true in all cases.
605                /// </remarks>
606                public bool IsStreamOwner
607                {
608                        get { return isStreamOwner; }
609                        set { isStreamOwner = value; }
610                }
611               
612                /// <summary>
613                /// Get a value indicating wether
614                /// this archive is embedded in another file or not.
615                /// </summary>
616                public bool IsEmbeddedArchive
617                {
618                        // Not strictly correct in all circumstances currently
619                        get { return offsetOfFirstEntry > 0; }
620                }
621
622                /// <summary>
623                /// Get a value indicating that this archive is a new one.
624                /// </summary>
625                public bool IsNewArchive
626                {
627                        get { return isNewArchive_; }
628                }
629
630                /// <summary>
631                /// Gets the comment for the zip file.
632                /// </summary>
633                public string ZipFileComment
634                {
635                        get { return comment_; }
636                }
637               
638                /// <summary>
639                /// Gets the name of this zip file.
640                /// </summary>
641                public string Name
642                {
643                        get { return name_; }
644                }
645               
646                /// <summary>
647                /// Gets the number of entries in this zip file.
648                /// </summary>
649                /// <exception cref="InvalidOperationException">
650                /// The Zip file has been closed.
651                /// </exception>
652                [Obsolete("Use the Count property instead")]
653                public int Size
654                {
655                        get
656                        {
657                                return entries_.Length;
658                        }
659                }
660               
661                /// <summary>
662                /// Get the number of entries contained in this <see cref="ZipFile"/>.
663                /// </summary>
664                public long Count
665                {
666                        get
667                        {
668                                return entries_.Length;
669                        }
670                }
671               
672                /// <summary>
673                /// Indexer property for ZipEntries
674                /// </summary>
675                [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")]
676                public ZipEntry this[int index]
677                {
678                        get {
679                                return (ZipEntry) entries_[index].Clone();     
680                        }
681                }
682               
683                #endregion
684               
685                #region Input Handling
686                /// <summary>
687                /// Gets an enumerator for the Zip entries in this Zip file.
688                /// </summary>
689                /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns>
690                /// <exception cref="ObjectDisposedException">
691                /// The Zip file has been closed.
692                /// </exception>
693                public IEnumerator GetEnumerator()
694                {
695                        if (isDisposed_) {
696                                throw new ObjectDisposedException("ZipFile");
697                        }
698
699                        return new ZipEntryEnumerator(entries_);
700                }
701               
702                /// <summary>
703                /// Return the index of the entry with a matching name
704                /// </summary>
705                /// <param name="name">Entry name to find</param>
706                /// <param name="ignoreCase">If true the comparison is case insensitive</param>
707                /// <returns>The index position of the matching entry or -1 if not found</returns>
708                /// <exception cref="ObjectDisposedException">
709                /// The Zip file has been closed.
710                /// </exception>
711                public int FindEntry(string name, bool ignoreCase)
712                {
713                        if (isDisposed_) {
714                                throw new ObjectDisposedException("ZipFile");
715                        }                       
716                       
717                        // TODO: This will be slow as the next ice age for huge archives!
718                        for (int i = 0; i < entries_.Length; i++) {
719                                if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) {
720                                        return i;
721                                }
722                        }
723                        return -1;
724                }
725               
726                /// <summary>
727                /// Searches for a zip entry in this archive with the given name.
728                /// String comparisons are case insensitive
729                /// </summary>
730                /// <param name="name">
731                /// The name to find. May contain directory components separated by slashes ('/').
732                /// </param>
733                /// <returns>
734                /// A clone of the zip entry, or null if no entry with that name exists.
735                /// </returns>
736                /// <exception cref="ObjectDisposedException">
737                /// The Zip file has been closed.
738                /// </exception>
739                public ZipEntry GetEntry(string name)
740                {
741                        if (isDisposed_) {
742                                throw new ObjectDisposedException("ZipFile");
743                        }                       
744                                               
745                        int index = FindEntry(name, true);
746                        return (index >= 0) ? (ZipEntry) entries_[index].Clone() : null;
747                }
748
749                /// <summary>
750                /// Gets an input stream for reading the given zip entry data in an uncompressed form.
751                /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry().
752                /// </summary>
753                /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param>
754                /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns>
755                /// <exception cref="ObjectDisposedException">
756                /// The ZipFile has already been closed
757                /// </exception>
758                /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
759                /// The compression method for the entry is unknown
760                /// </exception>
761                /// <exception cref="IndexOutOfRangeException">
762                /// The entry is not found in the ZipFile
763                /// </exception>
764                public Stream GetInputStream(ZipEntry entry)
765                {
766                        if ( entry == null ) {
767                                throw new ArgumentNullException("entry");
768                        }
769
770                        if ( isDisposed_ ) {
771                                throw new ObjectDisposedException("ZipFile");
772                        }
773                       
774                        long index = entry.ZipFileIndex;
775                        if ( (index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name) ) {
776                                index = FindEntry(entry.Name, true);
777                                if (index < 0) {
778                                        throw new ZipException("Entry cannot be found");
779                                }
780                        }
781                        return GetInputStream(index);                   
782                }
783               
784                /// <summary>
785                /// Creates an input stream reading a zip entry
786                /// </summary>
787                /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param>
788                /// <returns>
789                /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/>
790                /// </returns>
791                /// <exception cref="ObjectDisposedException">
792                /// The ZipFile has already been closed
793                /// </exception>
794                /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
795                /// The compression method for the entry is unknown
796                /// </exception>
797                /// <exception cref="IndexOutOfRangeException">
798                /// The entry is not found in the ZipFile
799                /// </exception>
800                public Stream GetInputStream(long entryIndex)
801                {
802                        if ( isDisposed_ ) {
803                                throw new ObjectDisposedException("ZipFile");
804                        }
805                       
806                        long start = LocateEntry(entries_[entryIndex]);
807                        CompressionMethod method = entries_[entryIndex].CompressionMethod;
808                        Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize);
809
810                        if (entries_[entryIndex].IsCrypted == true) {
811#if NETCF_1_0
812                                throw new ZipException("decryption not supported for Compact Framework 1.0");
813#else
814                                result = CreateAndInitDecryptionStream(result, entries_[entryIndex]);
815                                if (result == null) {
816                                        throw new ZipException("Unable to decrypt this entry");
817                                }
818#endif                         
819                        }
820
821                        switch (method) {
822                                case CompressionMethod.Stored:
823                                        // read as is.
824                                        break;
825
826                                case CompressionMethod.Deflated:
827                                        // No need to worry about ownership and closing as underlying stream close does nothing.
828                                        result = new InflaterInputStream(result, new Inflater(true));
829                                        break;
830
831                                default:
832                                        throw new ZipException("Unsupported compression method " + method);
833                        }
834
835                        return result;
836                }
837
838                #endregion
839               
840                #region Archive Testing
841                /// <summary>
842                /// Test an archive for integrity/validity
843                /// </summary>
844                /// <param name="testData">Perform low level data Crc check</param>
845                /// <returns>true if all tests pass, false otherwise</returns>
846                /// <remarks>Testing will terminate on the first error found.</remarks>
847                public bool TestArchive(bool testData)
848                {
849                        return TestArchive(testData, TestStrategy.FindFirstError, null);
850                }
851               
852                /// <summary>
853                /// Test an archive for integrity/validity
854                /// </summary>
855                /// <param name="testData">Perform low level data Crc check</param>
856                /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param>
857                /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param>
858                /// <returns>true if all tests pass, false otherwise</returns>
859                /// <exception cref="ObjectDisposedException">The object has already been closed.</exception>
860                public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler)
861                {
862                        if (isDisposed_) {
863                                throw new ObjectDisposedException("ZipFile");
864                        }
865                       
866                        TestStatus status = new TestStatus(this);
867
868                        if ( resultHandler != null ) {
869                                resultHandler(status, null);
870                        }
871
872                        HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header;
873
874                        bool testing = true;
875
876                        try {
877                                int entryIndex = 0;
878
879                                while ( testing && (entryIndex < Count) ) {
880                                        if ( resultHandler != null ) {
881                                                status.SetEntry(this[entryIndex]);
882                                                status.SetOperation(TestOperation.EntryHeader);
883                                                resultHandler(status, null);
884                                        }
885
886                                        try     {
887                                                TestLocalHeader(this[entryIndex], test);
888                                        }
889                                        catch(ZipException ex) {
890                                                status.AddError();
891
892                                                if ( resultHandler != null ) {
893                                                        resultHandler(status,
894                                                                string.Format("Exception during test - '{0}'", ex.Message));
895                                                }
896
897                                                if ( strategy == TestStrategy.FindFirstError ) {
898                                                        testing = false;
899                                                }
900                                        }
901
902                                        if ( testing && testData && this[entryIndex].IsFile ) {
903                                                if ( resultHandler != null ) {
904                                                        status.SetOperation(TestOperation.EntryData);
905                                                        resultHandler(status, null);
906                                                }
907
908                        Crc32 crc = new Crc32();
909
910                        using (Stream entryStream = this.GetInputStream(this[entryIndex]))
911                        {
912
913                            byte[] buffer = new byte[4096];
914                            long totalBytes = 0;
915                            int bytesRead;
916                            while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0)
917                            {
918                                crc.Update(buffer, 0, bytesRead);
919
920                                if (resultHandler != null)
921                                {
922                                    totalBytes += bytesRead;
923                                    status.SetBytesTested(totalBytes);
924                                    resultHandler(status, null);
925                                }
926                            }
927                        }
928
929                                                if (this[entryIndex].Crc != crc.Value) {
930                                                        status.AddError();
931                                                       
932                                                        if ( resultHandler != null ) {
933                                                                resultHandler(status, "CRC mismatch");
934                                                        }
935
936                                                        if ( strategy == TestStrategy.FindFirstError ) {
937                                                                testing = false;
938                                                        }
939                                                }
940
941                                                if (( this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0 ) {
942                                                        ZipHelperStream helper = new ZipHelperStream(baseStream_);
943                                                        DescriptorData data = new DescriptorData();
944                                                        helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data);
945                                                        if (this[entryIndex].Crc != data.Crc) {
946                                                                status.AddError();
947                                                        }
948
949                                                        if (this[entryIndex].CompressedSize != data.CompressedSize) {
950                                                                status.AddError();
951                                                        }
952
953                                                        if (this[entryIndex].Size != data.Size) {
954                                                                status.AddError();
955                                                        }
956                                                }
957                                        }
958
959                                        if ( resultHandler != null ) {
960                                                status.SetOperation(TestOperation.EntryComplete);
961                                                resultHandler(status, null);
962                                        }
963
964                                        entryIndex += 1;
965                                }
966
967                                if ( resultHandler != null ) {
968                                        status.SetOperation(TestOperation.MiscellaneousTests);
969                                        resultHandler(status, null);
970                                }
971
972                                // TODO: the 'Corrina Johns' test where local headers are missing from
973                                // the central directory.  They are therefore invisible to many archivers.
974                        }
975                        catch (Exception ex) {
976                                status.AddError();
977
978                                if ( resultHandler != null ) {
979                                        resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message));
980                                }
981                        }
982
983                        if ( resultHandler != null ) {
984                                status.SetOperation(TestOperation.Complete);
985                                status.SetEntry(null);
986                                resultHandler(status, null);
987                        }
988
989                        return (status.ErrorCount == 0);
990                }
991
992                [Flags]
993                enum HeaderTest
994                {
995                        Extract = 0x01,     // Check that this header represents an entry whose data can be extracted
996                        Header  = 0x02,     // Check that this header contents are valid
997                }
998       
999                /// <summary>
1000                /// Test a local header against that provided from the central directory
1001                /// </summary>
1002                /// <param name="entry">
1003                /// The entry to test against
1004                /// </param>
1005                /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param>
1006                /// <returns>The offset of the entries data in the file</returns>
1007                long TestLocalHeader(ZipEntry entry, HeaderTest tests)
1008                {
1009                        lock(baseStream_)
1010                        {
1011                                bool testHeader = (tests & HeaderTest.Header) != 0;
1012                                bool testData = (tests & HeaderTest.Extract) != 0;
1013
1014                                baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
1015                                if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) {
1016                                        throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset));
1017                                }
1018
1019                                short extractVersion = ( short )ReadLEUshort();
1020                                short localFlags = ( short )ReadLEUshort();
1021                                short compressionMethod = ( short )ReadLEUshort();
1022                                short fileTime = ( short )ReadLEUshort();
1023                                short fileDate = ( short )ReadLEUshort();
1024                                uint crcValue = ReadLEUint();
1025                                long compressedSize = ReadLEUint();
1026                                long size = ReadLEUint();
1027                                int storedNameLength = ReadLEUshort();
1028                                int extraDataLength = ReadLEUshort();
1029
1030                                byte[] nameData = new byte[storedNameLength];
1031                                StreamUtils.ReadFully(baseStream_, nameData);
1032
1033                                byte[] extraData = new byte[extraDataLength];
1034                                StreamUtils.ReadFully(baseStream_, extraData);
1035
1036                                ZipExtraData localExtraData = new ZipExtraData(extraData);
1037
1038                                // Extra data / zip64 checks
1039                                if (localExtraData.Find(1))
1040                                {
1041                                        // TODO Check for tag values being distinct..  Multiple zip64 tags means what?
1042
1043                                        // Zip64 extra data but 'extract version' is too low
1044                                        if (extractVersion < ZipConstants.VersionZip64)
1045                                        {
1046                                                throw new ZipException(
1047                                                        string.Format("Extra data contains Zip64 information but version {0}.{1} is not high enough",
1048                                                        extractVersion / 10, extractVersion % 10));
1049                                        }
1050
1051                                        // Zip64 extra data but size fields dont indicate its required.
1052                                        if (((uint)size != uint.MaxValue) && ((uint)compressedSize != uint.MaxValue))
1053                                        {
1054                                                throw new ZipException("Entry sizes not correct for Zip64");
1055                                        }
1056
1057                                        size = localExtraData.ReadLong();
1058                                        compressedSize = localExtraData.ReadLong();
1059
1060                    if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0)
1061                    {
1062                        // These may be valid if patched later
1063                        if ( (size != -1) && (size != entry.Size)) {
1064                            throw new ZipException("Size invalid for descriptor");
1065                        }
1066
1067                        if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) {
1068                            throw new ZipException("Compressed size invalid for descriptor");
1069                        }
1070                    }
1071                }
1072                                else
1073                                {
1074                                        // No zip64 extra data but entry requires it.
1075                                        if ((extractVersion >= ZipConstants.VersionZip64) &&
1076                                                (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue)))
1077                                        {
1078                                                throw new ZipException("Required Zip64 extended information missing");
1079                                        }
1080                                }
1081
1082                                if ( testData ) {
1083                                        if ( entry.IsFile ) {
1084                                                if ( !entry.IsCompressionMethodSupported() ) {
1085                                                        throw new ZipException("Compression method not supported");
1086                                                }
1087
1088                                                if ( (extractVersion > ZipConstants.VersionMadeBy)
1089                                                        || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)) ) {
1090                                                        throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion));
1091                                                }
1092
1093                                                if ( (localFlags & ( int )(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0 ) {
1094                                                        throw new ZipException("The library does not support the zip version required to extract this entry");
1095                                                }
1096                                        }
1097                                }
1098
1099                if (testHeader)
1100                {
1101                    if ((extractVersion <= 63) &&       // Ignore later versions as we dont know about them..
1102                        (extractVersion != 10) &&
1103                        (extractVersion != 11) &&
1104                        (extractVersion != 20) &&
1105                        (extractVersion != 21) &&
1106                        (extractVersion != 25) &&
1107                        (extractVersion != 27) &&
1108                        (extractVersion != 45) &&
1109                        (extractVersion != 46) &&
1110                        (extractVersion != 50) &&
1111                        (extractVersion != 51) &&
1112                        (extractVersion != 52) &&
1113                        (extractVersion != 61) &&
1114                        (extractVersion != 62) &&
1115                        (extractVersion != 63)
1116                        )
1117                    {
1118                        throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion));
1119                    }
1120
1121                    // Local entry flags dont have reserved bit set on.
1122                    if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0)
1123                    {
1124                        throw new ZipException("Reserved bit flags cannot be set.");
1125                    }
1126
1127                    // Encryption requires extract version >= 20
1128                    if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20))
1129                    {
1130                        throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
1131                    }
1132
1133                    // Strong encryption requires encryption flag to be set and extract version >= 50.
1134                    if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0)
1135                    {
1136                        if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0)
1137                        {
1138                            throw new ZipException("Strong encryption flag set but encryption flag is not set");
1139                        }
1140
1141                        if (extractVersion < 50)
1142                        {
1143                            throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
1144                        }
1145                    }
1146
1147                    // Patched entries require extract version >= 27
1148                    if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27))
1149                    {
1150                        throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
1151                    }
1152
1153                    // Central header flags match local entry flags.
1154                    if (localFlags != entry.Flags)
1155                    {
1156                        throw new ZipException("Central header/local header flags mismatch");
1157                    }
1158
1159                    // Central header compression method matches local entry
1160                    if (entry.CompressionMethod != (CompressionMethod)compressionMethod)
1161                    {
1162                        throw new ZipException("Central header/local header compression method mismatch");
1163                    }
1164
1165                    if (entry.Version != extractVersion)
1166                    {
1167                        throw new ZipException("Extract version mismatch");
1168                    }
1169
1170                    // Strong encryption and extract version match
1171                    if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0)
1172                    {
1173                        if (extractVersion < 62)
1174                        {
1175                            throw new ZipException("Strong encryption flag set but version not high enough");
1176                        }
1177                    }
1178
1179                    if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0)
1180                    {
1181                        if ((fileTime != 0) || (fileDate != 0))
1182                        {
1183                            throw new ZipException("Header masked set but date/time values non-zero");
1184                        }
1185                    }
1186
1187                    if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0)
1188                    {
1189                        if (crcValue != (uint)entry.Crc)
1190                        {
1191                            throw new ZipException("Central header/local header crc mismatch");
1192                        }
1193                    }
1194
1195                    // Crc valid for empty entry.
1196                    // This will also apply to streamed entries where size isnt known and the header cant be patched
1197                    if ((size == 0) && (compressedSize == 0))
1198                    {
1199                        if (crcValue != 0)
1200                        {
1201                            throw new ZipException("Invalid CRC for empty entry");
1202                        }
1203                    }
1204
1205                    // TODO: make test more correct...  can't compare lengths as was done originally as this can fail for MBCS strings
1206                    // Assuming a code page at this point is not valid?  Best is to store the name length in the ZipEntry probably
1207                    if (entry.Name.Length > storedNameLength)
1208                    {
1209                        throw new ZipException("File name length mismatch");
1210                    }
1211
1212                    // Name data has already been read convert it and compare.
1213                    string localName = ZipConstants.ConvertToStringExt(localFlags, nameData);
1214
1215                    // Central directory and local entry name match
1216                    if (localName != entry.Name)
1217                    {
1218                        throw new ZipException("Central header and local header file name mismatch");
1219                    }
1220
1221                    // Directories have zero actual size but can have compressed size
1222                    if (entry.IsDirectory)
1223                    {
1224                        if (size > 0)
1225                        {
1226                            throw new ZipException("Directory cannot have size");
1227                        }
1228
1229                        // There may be other cases where the compressed size can be greater than this?
1230                        // If so until details are known we will be strict.
1231                        if (entry.IsCrypted)
1232                        {
1233                            if (compressedSize > ZipConstants.CryptoHeaderSize + 2)
1234                            {
1235                                throw new ZipException("Directory compressed size invalid");
1236                            }
1237                        }
1238                        else if (compressedSize > 2)
1239                        {
1240                            // When not compressed the directory size can validly be 2 bytes
1241                            // if the true size wasnt known when data was originally being written.
1242                            // NOTE: Versions of the library 0.85.4 and earlier always added 2 bytes
1243                            throw new ZipException("Directory compressed size invalid");
1244                        }
1245                    }
1246
1247                    if (!ZipNameTransform.IsValidName(localName, true))
1248                    {
1249                        throw new ZipException("Name is invalid");
1250                    }
1251                }
1252
1253                                // Tests that apply to both data and header.
1254
1255                                // Size can be verified only if it is known in the local header.
1256                                // it will always be known in the central header.
1257                                if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) ||
1258                                        ((size > 0) || (compressedSize > 0))) {
1259
1260                                        if (size != entry.Size) {
1261                                                throw new ZipException(
1262                                                        string.Format("Size mismatch between central header({0}) and local header({1})",
1263                                                                entry.Size, size));
1264                                        }
1265
1266                                        if (compressedSize != entry.CompressedSize) {
1267                                                throw new ZipException(
1268                                                        string.Format("Compressed size mismatch between central header({0}) and local header({1})",
1269                                                        entry.CompressedSize, compressedSize));
1270                                        }
1271                                }
1272
1273                                int extraLength = storedNameLength + extraDataLength;
1274                                return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength;
1275                        }
1276                }
1277               
1278                #endregion
1279               
1280                #region Updating
1281
1282                const int DefaultBufferSize = 4096;
1283
1284                /// <summary>
1285                /// The kind of update to apply.
1286                /// </summary>
1287                enum UpdateCommand
1288                {
1289                        Copy,       // Copy original file contents.
1290                        Modify,     // Change encryption, compression, attributes, name, time etc, of an existing file.
1291                        Add,        // Add a new file to the archive.
1292                }
1293
1294                #region Properties
1295                /// <summary>
1296                /// Get / set the <see cref="INameTransform"/> to apply to names when updating.
1297                /// </summary>
1298                public INameTransform NameTransform
1299                {
1300                        get {
1301                                return updateEntryFactory_.NameTransform;
1302                        }
1303
1304                        set {
1305                                updateEntryFactory_.NameTransform = value;
1306                        }
1307                }
1308
1309                /// <summary>
1310                /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values
1311                /// during updates.
1312                /// </summary>
1313                public IEntryFactory EntryFactory
1314                {
1315                        get {
1316                                return updateEntryFactory_;
1317                        }
1318
1319                        set {
1320                                if (value == null) {
1321                                        updateEntryFactory_ = new ZipEntryFactory();
1322                                }
1323                                else {
1324                                        updateEntryFactory_ = value;
1325                                }
1326                        }
1327                }
1328
1329                /// <summary>
1330                /// Get /set the buffer size to be used when updating this zip file.
1331                /// </summary>
1332                public int BufferSize
1333                {
1334                        get { return bufferSize_; }
1335                        set {
1336                                if ( value < 1024 ) {
1337#if NETCF_1_0                                   
1338                                        throw new ArgumentOutOfRangeException("value");
1339#else
1340                                        throw new ArgumentOutOfRangeException("value", "cannot be below 1024");
1341#endif                                 
1342                                }
1343
1344                                if ( bufferSize_ != value ) {
1345                                        bufferSize_ = value;
1346                                        copyBuffer_ = null;
1347                                }
1348                        }
1349                }
1350
1351                /// <summary>
1352                /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>.
1353                /// </summary>
1354                public bool IsUpdating
1355                {
1356                        get { return updates_ != null; }
1357                }
1358
1359                /// <summary>
1360                /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
1361                /// </summary>
1362                public UseZip64 UseZip64
1363                {
1364                        get { return useZip64_; }
1365                        set { useZip64_ = value; }
1366                }
1367
1368                #endregion
1369               
1370                #region Immediate updating
1371//              TBD: Direct form of updating
1372//
1373//              public void Update(IEntryMatcher deleteMatcher)
1374//              {
1375//              }
1376//
1377//              public void Update(IScanner addScanner)
1378//              {
1379//              }
1380                #endregion
1381               
1382                #region Deferred Updating
1383                /// <summary>
1384                /// Begin updating this <see cref="ZipFile"/> archive.
1385                /// </summary>
1386                /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</param>
1387                /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param>
1388                /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
1389                /// <exception cref="ArgumentNullException">One of the arguments provided is null</exception>
1390                /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
1391                public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource)
1392                {
1393                        if ( archiveStorage == null ) {
1394                                throw new ArgumentNullException("archiveStorage");
1395                        }
1396
1397                        if ( dataSource == null ) {
1398                                throw new ArgumentNullException("dataSource");
1399                        }
1400                       
1401                        if ( isDisposed_ ) {
1402                                throw new ObjectDisposedException("ZipFile");
1403                        }
1404
1405                        if ( IsEmbeddedArchive ) {
1406                                throw new ZipException ("Cannot update embedded/SFX archives");
1407                        }
1408
1409                        archiveStorage_ = archiveStorage;
1410                        updateDataSource_ = dataSource;
1411
1412                        // NOTE: the baseStream_ may not currently support writing or seeking.
1413
1414                        updateIndex_ = new Hashtable();
1415
1416                        updates_ = new ArrayList(entries_.Length);
1417                        foreach(ZipEntry entry in entries_) {
1418                                int index = updates_.Add(new ZipUpdate(entry));
1419                                updateIndex_.Add(entry.Name, index);
1420                        }
1421
1422                        updateCount_ = updates_.Count;
1423
1424                        contentsEdited_ = false;
1425                        commentEdited_ = false;
1426                        newComment_ = null;
1427                }
1428
1429                /// <summary>
1430                /// Begin updating to this <see cref="ZipFile"/> archive.
1431                /// </summary>
1432                /// <param name="archiveStorage">The storage to use during the update.</param>
1433                public void BeginUpdate(IArchiveStorage archiveStorage)
1434                {
1435                        BeginUpdate(archiveStorage, new DynamicDiskDataSource());
1436                }
1437               
1438                /// <summary>
1439                /// Begin updating this <see cref="ZipFile"/> archive.
1440                /// </summary>
1441                /// <seealso cref="BeginUpdate(IArchiveStorage)"/>
1442                /// <seealso cref="CommitUpdate"></seealso>
1443                /// <seealso cref="AbortUpdate"></seealso>
1444                public void BeginUpdate()
1445                {
1446                        if ( Name == null ) {
1447                                BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource());
1448                        }
1449                        else {
1450                                BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource());
1451                        }
1452                }
1453
1454                /// <summary>
1455                /// Commit current updates, updating this archive.
1456                /// </summary>
1457                /// <seealso cref="BeginUpdate()"></seealso>
1458                /// <seealso cref="AbortUpdate"></seealso>
1459                /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
1460                public void CommitUpdate()
1461                {
1462                        if ( isDisposed_ ) {
1463                                throw new ObjectDisposedException("ZipFile");
1464                        }
1465                       
1466                        CheckUpdating();
1467
1468                        try {
1469                                updateIndex_.Clear();
1470                                updateIndex_=null;
1471
1472                                if( contentsEdited_ ) {
1473                                        RunUpdates();
1474                                }
1475                                else if( commentEdited_ ) {
1476                                        UpdateCommentOnly();
1477                                }
1478                                else {
1479                                        // Create an empty archive if none existed originally.
1480                                        if( entries_.Length==0 ) {
1481                                                byte[] theComment=(newComment_!=null)?newComment_.RawComment:ZipConstants.ConvertToArray(comment_);
1482                                                using( ZipHelperStream zhs=new ZipHelperStream(baseStream_) ) {
1483                                                        zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment);
1484                                                }
1485                                        }
1486                                }
1487
1488                        }
1489                        finally {
1490                                PostUpdateCleanup();
1491                        }
1492                }
1493
1494                /// <summary>
1495                /// Abort updating leaving the archive unchanged.
1496                /// </summary>
1497                /// <seealso cref="BeginUpdate()"></seealso>
1498                /// <seealso cref="CommitUpdate"></seealso>
1499                public void AbortUpdate()
1500                {
1501                        PostUpdateCleanup();
1502                }
1503
1504                /// <summary>
1505                /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>.
1506                /// </summary>
1507                /// <param name="comment">The comment to record.</param>
1508                /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
1509                public void SetComment(string comment)
1510                {
1511                        if ( isDisposed_ ) {
1512                                throw new ObjectDisposedException("ZipFile");
1513                        }
1514
1515                        CheckUpdating();
1516
1517                        newComment_ = new ZipString(comment);
1518
1519                        if ( newComment_.RawLength  > 0xffff ) {
1520                                newComment_ = null;
1521                                throw new ZipException("Comment length exceeds maximum - 65535");
1522                        }
1523
1524                        // We dont take account of the original and current comment appearing to be the same
1525                        // as encoding may be different.
1526                        commentEdited_ = true;
1527                }
1528
1529                #endregion
1530               
1531                #region Adding Entries
1532
1533                void AddUpdate(ZipUpdate update)
1534                {
1535                        contentsEdited_ = true;
1536
1537                        int index = FindExistingUpdate(update.Entry.Name);
1538
1539                        if (index >= 0) {
1540                                if ( updates_[index] == null ) {
1541                                        updateCount_ += 1;
1542                                }
1543
1544                                // Direct replacement is faster than delete and add.
1545                                updates_[index] = update;
1546                        }
1547                        else {
1548                                index = updates_.Add(update);
1549                                updateCount_ += 1;
1550                                updateIndex_.Add(update.Entry.Name, index);
1551                        }
1552                }
1553
1554                /// <summary>
1555                /// Add a new entry to the archive.
1556                /// </summary>
1557                /// <param name="fileName">The name of the file to add.</param>
1558                /// <param name="compressionMethod">The compression method to use.</param>
1559                /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param>
1560                /// <exception cref="ArgumentNullException">Argument supplied is null.</exception>
1561                /// <exception cref="ObjectDisposedException">ZipFile has been closed.</exception>
1562                /// <exception cref="ArgumentOutOfRangeException">Compression method is not supported.</exception>
1563                public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText )
1564                {
1565                        if (fileName == null) {
1566                                throw new ArgumentNullException("fileName");
1567                        }
1568
1569                        if ( isDisposed_ ) {
1570                                throw new ObjectDisposedException("ZipFile");
1571                        }
1572
1573                        if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) {
1574                                throw new ArgumentOutOfRangeException("compressionMethod");
1575                        }
1576
1577                        CheckUpdating();
1578                        contentsEdited_ = true;
1579
1580                        ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
1581                        entry.IsUnicodeText = useUnicodeText;
1582                        entry.CompressionMethod = compressionMethod;
1583
1584                        AddUpdate(new ZipUpdate(fileName, entry));
1585                }
1586
1587                /// <summary>
1588                /// Add a new entry to the archive.
1589                /// </summary>
1590                /// <param name="fileName">The name of the file to add.</param>
1591                /// <param name="compressionMethod">The compression method to use.</param>
1592                /// <exception cref="ArgumentNullException">ZipFile has been closed.</exception>
1593                /// <exception cref="ArgumentOutOfRangeException">The compression method is not supported.</exception>
1594                public void Add(string fileName, CompressionMethod compressionMethod)
1595                {
1596                        if ( fileName == null ) {
1597                                throw new ArgumentNullException("fileName");
1598                        }
1599
1600                        if ( !ZipEntry.IsCompressionMethodSupported(compressionMethod) ) {
1601                                throw new ArgumentOutOfRangeException("compressionMethod");
1602                        }
1603
1604                        CheckUpdating();
1605                        contentsEdited_ = true;
1606
1607                        ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
1608                        entry.CompressionMethod = compressionMethod;
1609                        AddUpdate(new ZipUpdate(fileName, entry));
1610                }
1611
1612                /// <summary>
1613                /// Add a file to the archive.
1614                /// </summary>
1615                /// <param name="fileName">The name of the file to add.</param>
1616                /// <exception cref="ArgumentNullException">Argument supplied is null.</exception>
1617                public void Add(string fileName)
1618                {
1619                        if ( fileName == null ) {
1620                                throw new ArgumentNullException("fileName");
1621                        }
1622
1623                        CheckUpdating();
1624                        AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName)));
1625                }
1626
1627                /// <summary>
1628                /// Add a file to the archive.
1629                /// </summary>
1630                /// <param name="fileName">The name of the file to add.</param>
1631                /// <param name="entryName">The name to use for the <see cref="ZipEntry"/> on the Zip file created.</param>
1632                /// <exception cref="ArgumentNullException">Argument supplied is null.</exception>
1633                public void Add(string fileName, string entryName)
1634                {
1635                        if (fileName == null) {
1636                                throw new ArgumentNullException("fileName");
1637                        }
1638
1639                        if ( entryName == null ) {
1640                                throw new ArgumentNullException("entryName");
1641                        }
1642                       
1643                        CheckUpdating();
1644                        AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(entryName)));
1645                }
1646
1647
1648                /// <summary>
1649                /// Add a file entry with data.
1650                /// </summary>
1651                /// <param name="dataSource">The source of the data for this entry.</param>
1652                /// <param name="entryName">The name to give to the entry.</param>
1653                public void Add(IStaticDataSource dataSource, string entryName)
1654                {
1655                        if ( dataSource == null ) {
1656                                throw new ArgumentNullException("dataSource");
1657                        }
1658
1659                        if ( entryName == null ) {
1660                                throw new ArgumentNullException("entryName");
1661                        }
1662
1663                        CheckUpdating();
1664                        AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false)));
1665                }
1666
1667                /// <summary>
1668                /// Add a file entry with data.
1669                /// </summary>
1670                /// <param name="dataSource">The source of the data for this entry.</param>
1671                /// <param name="entryName">The name to give to the entry.</param>
1672                /// <param name="compressionMethod">The compression method to use.</param>
1673                public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
1674                {
1675                        if ( dataSource == null ) {
1676                                throw new ArgumentNullException("dataSource");
1677                        }
1678
1679                        if ( entryName == null ) {
1680                                throw new ArgumentNullException("entryName");
1681                        }
1682
1683                        CheckUpdating();
1684
1685                        ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
1686                        entry.CompressionMethod = compressionMethod;
1687
1688                        AddUpdate(new ZipUpdate(dataSource, entry));
1689                }
1690
1691                /// <summary>
1692                /// Add a file entry with data.
1693                /// </summary>
1694                /// <param name="dataSource">The source of the data for this entry.</param>
1695                /// <param name="entryName">The name to give to the entry.</param>
1696                /// <param name="compressionMethod">The compression method to use.</param>
1697                /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param>
1698                public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText)
1699                {
1700                        if (dataSource == null) {
1701                                throw new ArgumentNullException("dataSource");
1702                        }
1703
1704                        if ( entryName == null ) {
1705                                throw new ArgumentNullException("entryName");
1706                        }
1707
1708                        CheckUpdating();
1709
1710                        ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
1711                        entry.IsUnicodeText = useUnicodeText;
1712                        entry.CompressionMethod = compressionMethod;
1713
1714                        AddUpdate(new ZipUpdate(dataSource, entry));
1715                }
1716
1717                /// <summary>
1718                /// Add a <see cref="ZipEntry"/> that contains no data.
1719                /// </summary>
1720                /// <param name="entry">The entry to add.</param>
1721                /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks>
1722                public void Add(ZipEntry entry)
1723                {
1724                        if ( entry == null ) {
1725                                throw new ArgumentNullException("entry");
1726                        }
1727
1728                        CheckUpdating();
1729
1730                        if ( (entry.Size != 0) || (entry.CompressedSize != 0) ) {
1731                                throw new ZipException("Entry cannot have any data");
1732                        }
1733
1734                        AddUpdate(new ZipUpdate(UpdateCommand.Add, entry));
1735                }
1736
1737                /// <summary>
1738                /// Add a directory entry to the archive.
1739                /// </summary>
1740                /// <param name="directoryName">The directory to add.</param>
1741                public void AddDirectory(string directoryName)
1742                {
1743                        if ( directoryName == null ) {
1744                                throw new ArgumentNullException("directoryName");
1745                        }
1746
1747                        CheckUpdating();
1748
1749                        ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName);
1750                        AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry));
1751                }
1752
1753                #endregion
1754               
1755                #region Modifying Entries
1756/* Modify not yet ready for public consumption.
1757   Direct modification of an entry should not overwrite original data before its read.
1758   Safe mode is trivial in this sense.
1759                public void Modify(ZipEntry original, ZipEntry updated)
1760                {
1761                        if ( original == null ) {
1762                                throw new ArgumentNullException("original");
1763                        }
1764
1765                        if ( updated == null ) {
1766                                throw new ArgumentNullException("updated");
1767                        }
1768
1769                        CheckUpdating();
1770                        contentsEdited_ = true;
1771                        updates_.Add(new ZipUpdate(original, updated));
1772                }
1773*/
1774                #endregion
1775               
1776                #region Deleting Entries
1777                /// <summary>
1778                /// Delete an entry by name
1779                /// </summary>
1780                /// <param name="fileName">The filename to delete</param>
1781                /// <returns>True if the entry was found and deleted; false otherwise.</returns>
1782                public bool Delete(string fileName)
1783                {
1784                        if ( fileName == null ) {
1785                                throw new ArgumentNullException("fileName");
1786                        }
1787                       
1788                        CheckUpdating();
1789
1790                        bool result = false;
1791                        int index = FindExistingUpdate(fileName);
1792                        if ( (index >= 0) && (updates_[index] != null) ) {
1793                                result = true;
1794                                contentsEdited_ = true;
1795                                updates_[index] = null;
1796                                updateCount_ -= 1;
1797                        }
1798                        else {
1799                                throw new ZipException("Cannot find entry to delete");
1800                        }
1801                        return result;
1802                }
1803
1804                /// <summary>
1805                /// Delete a <see cref="ZipEntry"/> from the archive.
1806                /// </summary>
1807                /// <param name="entry">The entry to delete.</param>
1808                public void Delete(ZipEntry entry)
1809                {
1810                        if ( entry == null ) {
1811                                throw new ArgumentNullException("entry");
1812                        }
1813                       
1814                        CheckUpdating();
1815
1816                        int index = FindExistingUpdate(entry);
1817                        if ( index >= 0 ) {
1818                                contentsEdited_ = true;
1819                                updates_[index] = null;
1820                                updateCount_ -= 1;
1821                        }
1822                        else {
1823                                throw new ZipException("Cannot find entry to delete");
1824                        }
1825                }
1826
1827                #endregion
1828               
1829                #region Update Support
1830                #region Writing Values/Headers
1831                void WriteLEShort(int value)
1832                {
1833                        baseStream_.WriteByte(( byte )(value & 0xff));
1834                        baseStream_.WriteByte(( byte )((value >> 8) & 0xff));
1835                }
1836
1837                /// <summary>
1838                /// Write an unsigned short in little endian byte order.
1839                /// </summary>
1840                void WriteLEUshort(ushort value)
1841                {
1842                        baseStream_.WriteByte(( byte )(value & 0xff));
1843                        baseStream_.WriteByte(( byte )(value >> 8));
1844                }
1845
1846                /// <summary>
1847                /// Write an int in little endian byte order.
1848                /// </summary>
1849                void WriteLEInt(int value)
1850                {
1851                        WriteLEShort(value & 0xffff);
1852                        WriteLEShort(value >> 16);
1853                }
1854
1855                /// <summary>
1856                /// Write an unsigned int in little endian byte order.
1857                /// </summary>
1858                void WriteLEUint(uint value)
1859                {
1860                        WriteLEUshort((ushort)(value & 0xffff));
1861                        WriteLEUshort((ushort)(value >> 16));
1862                }
1863
1864                /// <summary>
1865                /// Write a long in little endian byte order.
1866                /// </summary>
1867                void WriteLeLong(long value)
1868                {
1869                        WriteLEInt(( int )(value & 0xffffffff));
1870                        WriteLEInt(( int )(value >> 32));
1871                }
1872
1873                void WriteLEUlong(ulong value)
1874                {
1875                        WriteLEUint(( uint )(value & 0xffffffff));
1876                        WriteLEUint(( uint )(value >> 32));
1877                }
1878
1879                void WriteLocalEntryHeader(ZipUpdate update)
1880                {
1881                        ZipEntry entry = update.OutEntry;
1882
1883                        // TODO: Local offset will require adjusting for multi-disk zip files.
1884                        entry.Offset = baseStream_.Position;
1885
1886                        // TODO: Need to clear any entry flags that dont make sense or throw an exception here.
1887                        if (update.Command != UpdateCommand.Copy) {
1888                                if (entry.CompressionMethod == CompressionMethod.Deflated) {
1889                                        if (entry.Size == 0) {
1890                                                // No need to compress - no data.
1891                                                entry.CompressedSize = entry.Size;
1892                                                entry.Crc = 0;
1893                                                entry.CompressionMethod = CompressionMethod.Stored;
1894                                        }
1895                                }
1896                                else if (entry.CompressionMethod == CompressionMethod.Stored) {
1897                                        entry.Flags &= ~(int)GeneralBitFlags.Descriptor;
1898                                }
1899
1900                                if (HaveKeys) {
1901                                        entry.IsCrypted = true;
1902                                        if (entry.Crc < 0) {
1903                                                entry.Flags |= (int)GeneralBitFlags.Descriptor;
1904                                        }
1905                                }
1906                                else {
1907                                        entry.IsCrypted = false;
1908                                }
1909
1910                                switch (useZip64_) {
1911                                        case UseZip64.Dynamic:
1912                                                if (entry.Size < 0) {
1913                                                        entry.ForceZip64();
1914                                                }
1915                                                break;
1916
1917                                        case UseZip64.On:
1918                                                entry.ForceZip64();
1919                                                break;
1920
1921                                        case UseZip64.Off:
1922                                                // Do nothing.  The entry itself may be using Zip64 independantly.
1923                                                break;
1924                                }
1925                        }
1926
1927                        // Write the local file header
1928                        WriteLEInt(ZipConstants.LocalHeaderSignature);
1929
1930                        WriteLEShort(entry.Version);
1931                        WriteLEShort(entry.Flags);
1932
1933                        WriteLEShort((byte)entry.CompressionMethod);
1934                        WriteLEInt(( int )entry.DosTime);
1935
1936                        if ( !entry.HasCrc ) {
1937                                // Note patch address for updating CRC later.
1938                                update.CrcPatchOffset = baseStream_.Position;
1939                                WriteLEInt(( int )0);
1940                        }
1941                        else {
1942                                WriteLEInt(unchecked(( int )entry.Crc));
1943                        }
1944
1945                        if (entry.LocalHeaderRequiresZip64) {
1946                                WriteLEInt(-1);
1947                                WriteLEInt(-1);
1948                        }
1949                        else {
1950                                if ( (entry.CompressedSize < 0) || (entry.Size < 0) ) {
1951                                        update.SizePatchOffset = baseStream_.Position;
1952                                }
1953
1954                                WriteLEInt(( int )entry.CompressedSize);
1955                                WriteLEInt(( int )entry.Size);
1956                        }
1957
1958                        byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
1959
1960                        if ( name.Length > 0xFFFF ) {
1961                                throw new ZipException("Entry name too long.");
1962                        }
1963
1964                        ZipExtraData ed = new ZipExtraData(entry.ExtraData);
1965
1966                        if ( entry.LocalHeaderRequiresZip64 ) {
1967                                ed.StartNewEntry();
1968
1969                                // Local entry header always includes size and compressed size.
1970                                // NOTE the order of these fields is reversed when compared to the normal headers!
1971                                ed.AddLeLong(entry.Size);
1972                                ed.AddLeLong(entry.CompressedSize);
1973                                ed.AddNewEntry(1);
1974                        }
1975                        else {
1976                                ed.Delete(1);
1977                        }
1978
1979                        entry.ExtraData = ed.GetEntryData();
1980
1981                        WriteLEShort(name.Length);
1982                        WriteLEShort(entry.ExtraData.Length);
1983
1984                        if ( name.Length > 0 ) {
1985                                baseStream_.Write(name, 0, name.Length);
1986                        }
1987
1988                        if ( entry.LocalHeaderRequiresZip64 ) {
1989                                if ( !ed.Find(1) ) {
1990                                        throw new ZipException("Internal error cannot find extra data");
1991                                }
1992
1993                                update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex;
1994                        }
1995
1996                        if ( entry.ExtraData.Length > 0 ) {
1997                                baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length);
1998                        }
1999                }
2000
2001                int WriteCentralDirectoryHeader(ZipEntry entry)
2002                {
2003                        if ( entry.CompressedSize < 0 ) {
2004                                throw new ZipException("Attempt to write central directory entry with unknown csize");
2005                        }
2006
2007                        if ( entry.Size < 0 ) {
2008                                throw new ZipException("Attempt to write central directory entry with unknown size");
2009                        }
2010                       
2011                        if ( entry.Crc < 0 ) {
2012                                throw new ZipException("Attempt to write central directory entry with unknown crc");
2013                        }
2014                       
2015                        // Write the central file header
2016                        WriteLEInt(ZipConstants.CentralHeaderSignature);
2017
2018                        // Version made by
2019                        WriteLEShort(ZipConstants.VersionMadeBy);
2020
2021                        // Version required to extract
2022                        WriteLEShort(entry.Version);
2023
2024                        WriteLEShort(entry.Flags);
2025                       
2026                        unchecked {
2027                                WriteLEShort((byte)entry.CompressionMethod);
2028                                WriteLEInt((int)entry.DosTime);
2029                                WriteLEInt((int)entry.Crc);
2030                        }
2031
2032                        if ( (entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff) ) {
2033                                WriteLEInt(-1);
2034                        }
2035                        else {
2036                                WriteLEInt((int)(entry.CompressedSize & 0xffffffff));
2037                        }
2038
2039                        if ( (entry.IsZip64Forced()) || (entry.Size >= 0xffffffff) ) {
2040                                WriteLEInt(-1);
2041                        }
2042                        else {
2043                                WriteLEInt((int)entry.Size);
2044                        }
2045
2046                        byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
2047
2048                        if ( name.Length > 0xFFFF ) {
2049                                throw new ZipException("Entry name is too long.");
2050                        }
2051
2052                        WriteLEShort(name.Length);
2053
2054                        // Central header extra data is different to local header version so regenerate.
2055                        ZipExtraData ed = new ZipExtraData(entry.ExtraData);
2056
2057                        if ( entry.CentralHeaderRequiresZip64 ) {
2058                                ed.StartNewEntry();
2059
2060                                if ( (entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On) )
2061                                {
2062                                        ed.AddLeLong(entry.Size);
2063                                }
2064
2065                                if ( (entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On) )
2066                                {
2067                                        ed.AddLeLong(entry.CompressedSize);
2068                                }
2069
2070                                if ( entry.Offset >= 0xffffffff ) {
2071                                        ed.AddLeLong(entry.Offset);
2072                                }
2073
2074                                // Number of disk on which this file starts isnt supported and is never written here.
2075                                ed.AddNewEntry(1);
2076                        }
2077                        else {
2078                                // Should have already be done when local header was added.
2079                                ed.Delete(1);
2080                        }
2081
2082                        byte[] centralExtraData = ed.GetEntryData();
2083
2084                        WriteLEShort(centralExtraData.Length);
2085                        WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0);
2086
2087                        WriteLEShort(0);        // disk number
2088                        WriteLEShort(0);        // internal file attributes
2089
2090                        // External file attributes...
2091                        if ( entry.ExternalFileAttributes != -1 ) {
2092                                WriteLEInt(entry.ExternalFileAttributes);
2093                        }
2094                        else {
2095                                if ( entry.IsDirectory ) {
2096                                        WriteLEUint(16);
2097                                }
2098                                else {
2099                                        WriteLEUint(0);
2100                                }
2101                        }
2102
2103                        if ( entry.Offset >= 0xffffffff ) {
2104                                WriteLEUint(0xffffffff);
2105                        }
2106                        else {
2107                                WriteLEUint((uint)(int)entry.Offset);
2108                        }
2109
2110                        if ( name.Length > 0 ) {
2111                                baseStream_.Write(name, 0, name.Length);
2112                        }
2113
2114                        if ( centralExtraData.Length > 0 ) {
2115                                baseStream_.Write(centralExtraData, 0, centralExtraData.Length);
2116                        }
2117
2118                        byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0];
2119
2120                        if ( rawComment.Length > 0 ) {
2121                                baseStream_.Write(rawComment, 0, rawComment.Length);
2122                        }
2123
2124                        return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length;
2125                }
2126                #endregion
2127               
2128                void PostUpdateCleanup()
2129                {
2130                        if( archiveStorage_!=null ) {
2131                                archiveStorage_.Dispose();
2132                                archiveStorage_=null;
2133                        }
2134
2135                        updateDataSource_=null;
2136                        updates_ = null;
2137                        updateIndex_ = null;
2138                }
2139
2140                string GetTransformedFileName(string name)
2141                {
2142                        return (NameTransform != null) ?
2143                                NameTransform.TransformFile(name) :
2144                                name;
2145                }
2146
2147                string GetTransformedDirectoryName(string name)
2148                {
2149                        return (NameTransform != null) ?
2150                                NameTransform.TransformDirectory(name) :
2151                                name;
2152                }
2153
2154                /// <summary>
2155                /// Get a raw memory buffer.
2156                /// </summary>
2157                /// <returns>Returns a raw memory buffer.</returns>
2158                byte[] GetBuffer()
2159                {
2160                        if ( copyBuffer_ == null ) {
2161                                copyBuffer_ = new byte[bufferSize_];
2162                        }
2163                        return copyBuffer_;
2164                }
2165
2166                void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
2167                {
2168                        int bytesToCopy = GetDescriptorSize(update);
2169
2170                        if ( bytesToCopy > 0 ) {
2171                                byte[] buffer = GetBuffer();
2172
2173                                while ( bytesToCopy > 0 ) {
2174                                        int readSize = Math.Min(buffer.Length, bytesToCopy);
2175
2176                                        int bytesRead = source.Read(buffer, 0, readSize);
2177                                        if ( bytesRead > 0 ) {
2178                                                dest.Write(buffer, 0, bytesRead);
2179                                                bytesToCopy -= bytesRead;
2180                                        }
2181                                        else {
2182                                                throw new ZipException("Unxpected end of stream");
2183                                        }
2184                                }
2185                        }
2186                }
2187
2188                void CopyBytes(ZipUpdate update, Stream destination, Stream source,
2189                        long bytesToCopy, bool updateCrc)
2190                {
2191                        if ( destination == source ) {
2192                                throw new InvalidOperationException("Destination and source are the same");
2193                        }
2194
2195                        // NOTE: Compressed size is updated elsewhere.
2196                        Crc32 crc = new Crc32();
2197                        byte[] buffer = GetBuffer();
2198
2199                        long targetBytes = bytesToCopy;
2200                        long totalBytesRead = 0;
2201
2202                        int bytesRead;
2203                        do {
2204                                int readSize = buffer.Length;
2205
2206                                if ( bytesToCopy < readSize ) {
2207                                        readSize = (int)bytesToCopy;
2208                                }
2209
2210                                bytesRead = source.Read(buffer, 0, readSize);
2211                                if ( bytesRead > 0 ) {
2212                                        if ( updateCrc ) {
2213                                                crc.Update(buffer, 0, bytesRead);
2214                                        }
2215                                        destination.Write(buffer, 0, bytesRead);
2216                                        bytesToCopy -= bytesRead;
2217                                        totalBytesRead += bytesRead;
2218                                }
2219                        }
2220                        while ( (bytesRead > 0) && (bytesToCopy > 0) );
2221
2222                        if ( totalBytesRead != targetBytes ) {
2223                                throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead));
2224                        }
2225
2226                        if ( updateCrc ) {
2227                                update.OutEntry.Crc = crc.Value;
2228                        }
2229                }
2230
2231                /// <summary>
2232                /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>.
2233                /// </summary>
2234                /// <param name="update">The update to get the size for.</param>
2235                /// <returns>The descriptor size, zero if there isnt one.</returns>
2236                int GetDescriptorSize(ZipUpdate update)
2237                {
2238                        int result = 0;
2239                        if ( (update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) {
2240                                result = ZipConstants.DataDescriptorSize - 4;
2241                                if ( update.Entry.LocalHeaderRequiresZip64 ) {
2242                                        result = ZipConstants.Zip64DataDescriptorSize - 4;
2243                                }
2244                        }
2245                        return result;
2246                }
2247
2248                void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
2249                {
2250                        int bytesToCopy = GetDescriptorSize(update);
2251
2252                        while ( bytesToCopy > 0 ) {
2253                                int readSize = (int)bytesToCopy;
2254                                byte[] buffer = GetBuffer();
2255
2256                                stream.Position = sourcePosition;
2257                                int bytesRead = stream.Read(buffer, 0, readSize);
2258                                if ( bytesRead > 0 ) {
2259                                        stream.Position = destinationPosition;
2260                                        stream.Write(buffer, 0, bytesRead);
2261                                        bytesToCopy -= bytesRead;
2262                                        destinationPosition += bytesRead;
2263                                        sourcePosition += bytesRead;
2264                                }
2265                                else {
2266                                        throw new ZipException("Unxpected end of stream");
2267                                }
2268                        }
2269                }
2270
2271                void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition)
2272                {
2273                        long bytesToCopy = update.Entry.CompressedSize;
2274                       
2275                        // NOTE: Compressed size is updated elsewhere.
2276                        Crc32 crc = new Crc32();
2277                        byte[] buffer = GetBuffer();
2278
2279                        long targetBytes = bytesToCopy;
2280                        long totalBytesRead = 0;
2281
2282                        int bytesRead;
2283                        do
2284                        {
2285                                int readSize = buffer.Length;
2286
2287                                if ( bytesToCopy < readSize ) {
2288                                        readSize = (int)bytesToCopy;
2289                                }
2290
2291                                stream.Position = sourcePosition;
2292                                bytesRead = stream.Read(buffer, 0, readSize);
2293                                if ( bytesRead > 0 ) {
2294                                        if ( updateCrc ) {
2295                                                crc.Update(buffer, 0, bytesRead);
2296                                        }
2297                                        stream.Position = destinationPosition;
2298                                        stream.Write(buffer, 0, bytesRead);
2299
2300                                        destinationPosition += bytesRead;
2301                                        sourcePosition += bytesRead;
2302                                        bytesToCopy -= bytesRead;
2303                                        totalBytesRead += bytesRead;
2304                                }
2305                        }
2306                        while ( (bytesRead > 0) && (bytesToCopy > 0) );
2307
2308                        if ( totalBytesRead != targetBytes ) {
2309                                throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead));
2310                        }
2311
2312                        if ( updateCrc ) {
2313                                update.OutEntry.Crc = crc.Value;
2314                        }
2315                }
2316
2317                int FindExistingUpdate(ZipEntry entry)
2318                {
2319                        int result = -1;
2320                        string convertedName = GetTransformedFileName(entry.Name);
2321
2322                        if (updateIndex_.ContainsKey(convertedName)) {
2323                                result = (int)updateIndex_[convertedName];
2324                        }
2325/*
2326                        // This is slow like the coming of the next ice age but takes less storage and may be useful
2327                        // for CF?
2328                        for (int index = 0; index < updates_.Count; ++index)
2329                        {
2330                                ZipUpdate zu = ( ZipUpdate )updates_[index];
2331                                if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) &&
2332                                        (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) {
2333                                        result = index;
2334                                        break;
2335                                }
2336                        }
2337 */
2338                        return result;
2339                }
2340
2341                int FindExistingUpdate(string fileName)
2342                {
2343                        int result = -1;
2344
2345                        string convertedName = GetTransformedFileName(fileName);
2346
2347                        if (updateIndex_.ContainsKey(convertedName)) {
2348                                result = (int)updateIndex_[convertedName];
2349                        }
2350
2351/*
2352                        // This is slow like the coming of the next ice age but takes less storage and may be useful
2353                        // for CF?
2354                        for ( int index = 0; index < updates_.Count; ++index ) {
2355                                if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name,
2356                                        true, CultureInfo.InvariantCulture) == 0 ) {
2357                                        result = index;
2358                                        break;
2359                                }
2360                        }
2361 */
2362
2363                        return result;
2364                }
2365
2366                /// <summary>
2367                /// Get an output stream for the specified <see cref="ZipEntry"/>
2368                /// </summary>
2369                /// <param name="entry">The entry to get an output stream for.</param>
2370                /// <returns>The output stream obtained for the entry.</returns>
2371                Stream GetOutputStream(ZipEntry entry)
2372                {
2373                        Stream result = baseStream_;
2374
2375                        if ( entry.IsCrypted == true ) {
2376#if NETCF_1_0
2377                                throw new ZipException("Encryption not supported for Compact Framework 1.0");
2378#else
2379                                result = CreateAndInitEncryptionStream(result, entry);
2380#endif
2381                        }
2382
2383                        switch ( entry.CompressionMethod ) {
2384                                case CompressionMethod.Stored:
2385                                        result = new UncompressedStream(result);
2386                                        break;
2387
2388                                case CompressionMethod.Deflated:
2389                                        DeflaterOutputStream dos = new DeflaterOutputStream(result, new Deflater(9, true));
2390                                        dos.IsStreamOwner = false;
2391                                        result = dos;
2392                                        break;
2393
2394                                default:
2395                                        throw new ZipException("Unknown compression method " + entry.CompressionMethod);
2396                        }
2397                        return result;
2398                }
2399
2400                void AddEntry(ZipFile workFile, ZipUpdate update)
2401                {
2402                        Stream source = null;
2403
2404                        if ( update.Entry.IsFile ) {
2405                                source = update.GetSource();
2406                               
2407                                if ( source == null ) {
2408                                        source = updateDataSource_.GetSource(update.Entry, update.Filename);
2409                                }
2410                        }
2411
2412                        if ( source != null ) {
2413                                using ( source ) {
2414                                        long sourceStreamLength = source.Length;
2415                                        if ( update.OutEntry.Size < 0 ) {
2416                                                update.OutEntry.Size = sourceStreamLength;
2417                                        }
2418                                        else {
2419                                                // Check for errant entries.
2420                                                if ( update.OutEntry.Size != sourceStreamLength ) {
2421                                                        throw new ZipException("Entry size/stream size mismatch");
2422                                                }
2423                                        }
2424
2425                                        workFile.WriteLocalEntryHeader(update);
2426
2427                                        long dataStart = workFile.baseStream_.Position;
2428
2429                                        using ( Stream output = workFile.GetOutputStream(update.OutEntry) ) {
2430                                                CopyBytes(update, output, source, sourceStreamLength, true);
2431                                        }
2432
2433                                        long dataEnd = workFile.baseStream_.Position;
2434                                        update.OutEntry.CompressedSize = dataEnd - dataStart;
2435
2436                                        if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor)
2437                                        {
2438                                                ZipHelperStream helper = new ZipHelperStream(workFile.baseStream_);
2439                                                helper.WriteDataDescriptor(update.OutEntry);
2440                                        }
2441                                }
2442                        }
2443                        else {
2444                                workFile.WriteLocalEntryHeader(update);
2445                                update.OutEntry.CompressedSize = 0;
2446                        }
2447
2448                }
2449
2450                void ModifyEntry(ZipFile workFile, ZipUpdate update)
2451                {
2452                        workFile.WriteLocalEntryHeader(update);
2453                        long dataStart = workFile.baseStream_.Position;
2454
2455                        // TODO: This is slow if the changes don't effect the data!!
2456                        if ( update.Entry.IsFile && (update.Filename != null) ) {
2457                                using ( Stream output = workFile.GetOutputStream(update.OutEntry) ) {
2458                                        using ( Stream source = this.GetInputStream(update.Entry) ) {
2459                                                CopyBytes(update, output, source, source.Length, true);
2460                                        }
2461                                }
2462                        }
2463
2464                        long dataEnd = workFile.baseStream_.Position;
2465                        update.Entry.CompressedSize = dataEnd - dataStart;
2466                }
2467
2468                void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition)
2469                {
2470                        bool skipOver = false;
2471                        if ( update.Entry.Offset == destinationPosition ) {
2472                                skipOver = true;
2473                        }
2474
2475                        if ( !skipOver ) {
2476                                baseStream_.Position = destinationPosition;
2477                                workFile.WriteLocalEntryHeader(update);
2478                                destinationPosition = baseStream_.Position;
2479                        }
2480
2481                        long sourcePosition = 0;
2482
2483                        const int NameLengthOffset = 26;
2484
2485                        // TODO: Add base for SFX friendly handling
2486                        long entryDataOffset = update.Entry.Offset + NameLengthOffset;
2487
2488                        baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
2489
2490                        // Clumsy way of handling retrieving the original name and extra data length for now.
2491                        // TODO: Stop re-reading name and data length in CopyEntryDirect.
2492                        uint nameLength = ReadLEUshort();
2493                        uint extraLength = ReadLEUshort();
2494
2495                        sourcePosition = baseStream_.Position + nameLength + extraLength;
2496
2497                        if ( skipOver ) {
2498                                destinationPosition +=
2499                                        (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
2500                                        update.Entry.CompressedSize + GetDescriptorSize(update);
2501                        }
2502                        else {
2503                                if ( update.Entry.CompressedSize > 0 ) {
2504                                        CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition );
2505                                }
2506                                CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition);
2507                        }
2508                }
2509
2510                void CopyEntry(ZipFile workFile, ZipUpdate update)
2511                {
2512                        workFile.WriteLocalEntryHeader(update);
2513
2514                        if ( update.Entry.CompressedSize > 0 ) {
2515                                const int NameLengthOffset = 26;
2516
2517                                long entryDataOffset = update.Entry.Offset + NameLengthOffset;
2518
2519                                // TODO: This wont work for SFX files!
2520                                baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
2521
2522                                uint nameLength = ReadLEUshort();
2523                                uint extraLength = ReadLEUshort();
2524
2525                                baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current);
2526
2527                                CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false);
2528                        }
2529                        CopyDescriptorBytes(update, workFile.baseStream_, baseStream_);
2530                }
2531
2532                void Reopen(Stream source)
2533                {
2534                        if ( source == null ) {
2535                                throw new ZipException("Failed to reopen archive - no source");
2536                        }
2537
2538                        isNewArchive_ = false;
2539                        baseStream_ = source;
2540                        ReadEntries();
2541                }
2542
2543                void Reopen()
2544                {
2545                        if (Name == null) {
2546                                throw new InvalidOperationException("Name is not known cannot Reopen");
2547                        }
2548
2549                        Reopen(File.OpenRead(Name));
2550                }
2551
2552                void UpdateCommentOnly()
2553                {
2554                        long baseLength = baseStream_.Length;
2555
2556                        ZipHelperStream updateFile = null;
2557
2558                        if ( archiveStorage_.UpdateMode == FileUpdateMode.Safe ) {
2559                                Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_);
2560                                updateFile = new ZipHelperStream(copyStream);
2561                                updateFile.IsStreamOwner = true;
2562
2563                                baseStream_.Close();
2564                                baseStream_ = null;
2565                        }
2566                        else {
2567                                if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) {
2568                                        // TODO: archiveStorage wasnt originally intended for this use.
2569                                        // Need to revisit this to tidy up handling as archive storage currently doesnt
2570                                        // handle the original stream well.
2571                                        // The problem is when using an existing zip archive with an in memory archive storage.
2572                                        // The open stream wont support writing but the memory storage should open the same file not an in memory one.
2573
2574                                        // Need to tidy up the archive storage interface and contract basically.
2575                                        baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_);
2576                                        updateFile = new ZipHelperStream(baseStream_);
2577                                }
2578                                else {
2579                                        baseStream_.Close();
2580                                        baseStream_ = null;
2581                                        updateFile = new ZipHelperStream(Name);
2582                                }
2583                        }
2584
2585                        using ( updateFile ) {
2586                                long locatedCentralDirOffset =
2587                                        updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
2588                                                                                                                baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
2589                                if ( locatedCentralDirOffset < 0 ) {
2590                                        throw new ZipException("Cannot find central directory");
2591                                }
2592
2593                                const int CentralHeaderCommentSizeOffset = 16;
2594                                updateFile.Position += CentralHeaderCommentSizeOffset;
2595
2596                                byte[] rawComment = newComment_.RawComment;
2597
2598                                updateFile.WriteLEShort(rawComment.Length);
2599                                updateFile.Write(rawComment, 0, rawComment.Length);
2600                                updateFile.SetLength(updateFile.Position);
2601                        }
2602
2603                        if ( archiveStorage_.UpdateMode == FileUpdateMode.Safe ) {
2604                                Reopen(archiveStorage_.ConvertTemporaryToFinal());
2605                        }
2606                        else {
2607                                ReadEntries();
2608                        }
2609                }
2610
2611                /// <summary>
2612                /// Class used to sort updates.
2613                /// </summary>
2614                class UpdateComparer : IComparer
2615                {
2616                        /// <summary>
2617                        /// Compares two objects and returns a value indicating whether one is
2618                        /// less than, equal to or greater than the other.
2619                        /// </summary>
2620                        /// <param name="x">First object to compare</param>
2621                        /// <param name="y">Second object to compare.</param>
2622                        /// <returns>Compare result.</returns>
2623                        public int Compare(
2624                                object x,
2625                                object y)
2626                        {
2627                                ZipUpdate zx = x as ZipUpdate;
2628                                ZipUpdate zy = y as ZipUpdate;
2629
2630                                int result;
2631
2632                                if (zx == null) {
2633                                        if (zy == null) {
2634                                                result = 0;
2635                                        }
2636                                        else {
2637                                                result = -1;
2638                                        }
2639                                }
2640                                else if (zy == null) {
2641                                        result = 1;
2642                                }
2643                                else {
2644                                        int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1;
2645                                        int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1;
2646
2647                                        result = xCmdValue - yCmdValue;
2648                                        if (result == 0) {
2649                                                long offsetDiff = zx.Entry.Offset - zy.Entry.Offset;
2650                                                if (offsetDiff < 0) {
2651                                                        result = -1;
2652                                                }
2653                                                else if (offsetDiff == 0) {
2654                                                        result = 0;
2655                                                }
2656                                                else {
2657                                                        result = 1;
2658                                                }
2659                                        }
2660                                }
2661                                return result;
2662                        }
2663                }
2664
2665                void RunUpdates()
2666                {
2667                        long sizeEntries = 0;
2668                        long endOfStream = 0;
2669                        bool allOk = true;
2670                        bool directUpdate = false;
2671                        long destinationPosition = 0; // NOT SFX friendly
2672
2673                        ZipFile workFile;
2674
2675                        if ( IsNewArchive ) {
2676                                workFile = this;
2677                                workFile.baseStream_.Position = 0;
2678                                directUpdate = true;
2679                        }
2680                        else if ( archiveStorage_.UpdateMode == FileUpdateMode.Direct ) {
2681                                workFile = this;
2682                                workFile.baseStream_.Position = 0;
2683                                directUpdate = true;
2684
2685                                // Sort the updates by offset within copies/modifies, then adds.
2686                                // This ensures that data required by copies will not be overwritten.
2687                                updates_.Sort(new UpdateComparer());
2688                        }
2689                        else {
2690                                workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput());
2691                                workFile.UseZip64 = UseZip64;
2692                               
2693                                if (key != null) {
2694                                        workFile.key = (byte[])key.Clone();
2695                                }
2696                        }
2697
2698                        try {
2699                                foreach ( ZipUpdate update in updates_ ) {
2700                                        if (update != null) {
2701                                                switch (update.Command) {
2702                                                        case UpdateCommand.Copy:
2703                                                                if (directUpdate) {
2704                                                                        CopyEntryDirect(workFile, update, ref destinationPosition);
2705                                                                }
2706                                                                else {
2707                                                                        CopyEntry(workFile, update);
2708                                                                }
2709                                                                break;
2710
2711                                                        case UpdateCommand.Modify:
2712                                                                // TODO: Direct modifying of an entry will take some legwork.
2713                                                                ModifyEntry(workFile, update);
2714                                                                break;
2715
2716                                                        case UpdateCommand.Add:
2717                                                                if (!IsNewArchive && directUpdate) {
2718                                                                        workFile.baseStream_.Position = destinationPosition;
2719                                                                }
2720
2721                                                                AddEntry(workFile, update);
2722
2723                                                                if (directUpdate) {
2724                                                                        destinationPosition = workFile.baseStream_.Position;
2725                                                                }
2726                                                                break;
2727                                                }
2728                                        }
2729                                }
2730
2731                                if ( !IsNewArchive && directUpdate ) {
2732                                        workFile.baseStream_.Position = destinationPosition;
2733                                }
2734
2735                                long centralDirOffset = workFile.baseStream_.Position;
2736
2737                                foreach ( ZipUpdate update in updates_ ) {
2738                                        if (update != null) {
2739                                                sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry);
2740                                        }
2741                                }
2742
2743                                byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_);
2744                                using ( ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_) ) {
2745                                        zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment);
2746                                }
2747
2748                                endOfStream = workFile.baseStream_.Position;
2749
2750                                // And now patch entries...
2751                                foreach ( ZipUpdate update in updates_ ) {
2752                                        if (update != null)
2753                                        {
2754
2755                                                // If the size of the entry is zero leave the crc as 0 as well.
2756                                                // The calculated crc will be all bits on...
2757                                                if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) {
2758                                                        workFile.baseStream_.Position = update.CrcPatchOffset;
2759                                                        workFile.WriteLEInt((int)update.OutEntry.Crc);
2760                                                }
2761
2762                                                if (update.SizePatchOffset > 0) {
2763                                                        workFile.baseStream_.Position = update.SizePatchOffset;
2764                                                        if (update.OutEntry.LocalHeaderRequiresZip64) {
2765                                                                workFile.WriteLeLong(update.OutEntry.Size);
2766                                                                workFile.WriteLeLong(update.OutEntry.CompressedSize);
2767                                                        }
2768                                                        else {
2769                                                                workFile.WriteLEInt((int)update.OutEntry.CompressedSize);
2770                                                                workFile.WriteLEInt((int)update.OutEntry.Size);
2771                                                        }
2772                                                }
2773                                        }
2774                                }
2775                        }
2776                        catch(Exception) {
2777                                allOk = false;
2778                        }
2779                        finally {
2780                                if ( directUpdate ) {
2781                                        if ( allOk ) {
2782                                                workFile.baseStream_.Flush();
2783                                                workFile.baseStream_.SetLength(endOfStream);
2784                                        }
2785                                }
2786                                else {
2787                                        workFile.Close();
2788                                }
2789                        }
2790
2791                        if ( allOk ) {
2792                                if ( directUpdate ) {
2793                                        isNewArchive_ = false;
2794                                        workFile.baseStream_.Flush();
2795                                        ReadEntries();
2796                                }
2797                                else {
2798                                        baseStream_.Close();
2799                                        Reopen(archiveStorage_.ConvertTemporaryToFinal());
2800                                }
2801                        }
2802                        else {
2803                                workFile.Close();
2804                                if ( !directUpdate && (workFile.Name != null) ) {
2805                                        File.Delete(workFile.Name);
2806                                }
2807                        }
2808                }
2809
2810                void CheckUpdating()
2811                {
2812                        if ( updates_ == null ) {
2813                                throw new InvalidOperationException("BeginUpdate has not been called");
2814                        }
2815                }
2816
2817                #endregion
2818               
2819                #region ZipUpdate class
2820                /// <summary>
2821                /// Represents a pending update to a Zip file.
2822                /// </summary>
2823                class ZipUpdate
2824                {
2825                        #region Constructors
2826                        public ZipUpdate(string fileName, ZipEntry entry)
2827                        {
2828                                command_ = UpdateCommand.Add;
2829                                entry_ = entry;
2830                                filename_ = fileName;
2831                        }
2832
2833                        [Obsolete]
2834                        public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod)
2835                        {
2836                                command_ = UpdateCommand.Add;
2837                                entry_ = new ZipEntry(entryName);
2838                                entry_.CompressionMethod = compressionMethod;
2839                                filename_ = fileName;
2840                        }
2841
2842                        [Obsolete]
2843                        public ZipUpdate(string fileName, string entryName)
2844                                : this(fileName, entryName, CompressionMethod.Deflated)
2845                        {
2846                                // Do nothing.
2847                        }
2848
2849                        [Obsolete]
2850                        public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
2851                        {
2852                                command_ = UpdateCommand.Add;
2853                                entry_ = new ZipEntry(entryName);
2854                                entry_.CompressionMethod = compressionMethod;
2855                                dataSource_ = dataSource;
2856                        }
2857
2858                        public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry)
2859                        {
2860                                command_ = UpdateCommand.Add;
2861                                entry_ = entry;
2862                                dataSource_ = dataSource;
2863                        }
2864
2865                        public ZipUpdate(ZipEntry original, ZipEntry updated)
2866                        {
2867                                throw new ZipException("Modify not currently supported");
2868                        /*
2869                                command_ = UpdateCommand.Modify;
2870                                entry_ = ( ZipEntry )original.Clone();
2871                                outEntry_ = ( ZipEntry )updated.Clone();
2872                        */
2873                        }
2874
2875                        public ZipUpdate(UpdateCommand command, ZipEntry entry)
2876                        {
2877                                command_ = command;
2878                                entry_ = ( ZipEntry )entry.Clone();
2879                        }
2880
2881
2882                        /// <summary>
2883                        /// Copy an existing entry.
2884                        /// </summary>
2885                        /// <param name="entry">The existing entry to copy.</param>
2886                        public ZipUpdate(ZipEntry entry)
2887                                : this(UpdateCommand.Copy, entry)
2888                        {
2889                                // Do nothing.
2890                        }
2891                        #endregion
2892
2893                        /// <summary>
2894                        /// Get the <see cref="ZipEntry"/> for this update.
2895                        /// </summary>
2896                        /// <remarks>This is the source or original entry.</remarks>
2897                        public ZipEntry Entry
2898                        {
2899                                get { return entry_; }
2900                        }
2901
2902                        /// <summary>
2903                        /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file.
2904                        /// </summary>
2905                        public ZipEntry OutEntry
2906                        {
2907                                get {
2908                                        if ( outEntry_ == null ) {
2909                                                outEntry_ = (ZipEntry)entry_.Clone();
2910                                        }
2911
2912                                        return outEntry_;
2913                                }
2914                        }
2915
2916                        /// <summary>
2917                        /// Get the command for this update.
2918                        /// </summary>
2919                        public UpdateCommand Command
2920                        {
2921                                get { return command_; }
2922                        }
2923
2924                        /// <summary>
2925                        /// Get the filename if any for this update.  Null if none exists.
2926                        /// </summary>
2927                        public string Filename
2928                        {
2929                                get { return filename_; }
2930                        }
2931
2932                        /// <summary>
2933                        /// Get/set the location of the size patch for this update.
2934                        /// </summary>
2935                        public long SizePatchOffset
2936                        {
2937                                get { return sizePatchOffset_; }
2938                                set { sizePatchOffset_ = value; }
2939                        }
2940
2941                        /// <summary>
2942                        /// Get /set the location of the crc patch for this update.
2943                        /// </summary>
2944                        public long CrcPatchOffset
2945                        {
2946                                get { return crcPatchOffset_; }
2947                                set { crcPatchOffset_ = value; }
2948                        }
2949
2950                        public Stream GetSource()
2951                        {
2952                                Stream result = null;
2953                                if ( dataSource_ != null ) {
2954                                        result = dataSource_.GetSource();
2955                                }
2956
2957                                return result;
2958                        }
2959
2960                        #region Instance Fields
2961                        ZipEntry entry_;
2962                        ZipEntry outEntry_;
2963                        UpdateCommand command_;
2964                        IStaticDataSource dataSource_;
2965                        string filename_;
2966                        long sizePatchOffset_ = -1;
2967                        long crcPatchOffset_ = -1;
2968                        #endregion
2969                }
2970
2971                #endregion
2972                #endregion
2973               
2974                #region Disposing
2975
2976                #region IDisposable Members
2977                void IDisposable.Dispose()
2978                {
2979                        Close();
2980                }
2981                #endregion
2982
2983                void DisposeInternal(bool disposing)
2984                {
2985                        if ( !isDisposed_ ) {
2986                                isDisposed_ = true;
2987                                entries_ = new ZipEntry[0];
2988                                               
2989                                if ( IsStreamOwner && (baseStream_ != null) ) {
2990                                        lock(baseStream_) {
2991                                                baseStream_.Close();
2992                                        }
2993                                }
2994                               
2995                                PostUpdateCleanup();
2996                        }
2997                }
2998
2999                /// <summary>
3000                /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources.
3001                /// </summary>
3002                /// <param name="disposing">true to release both managed and unmanaged resources;
3003                /// false to release only unmanaged resources.</param>
3004                protected virtual void Dispose(bool disposing)
3005                {
3006                        DisposeInternal(disposing);
3007                }
3008
3009                #endregion
3010               
3011                #region Internal routines
3012                #region Reading
3013                /// <summary>
3014                /// Read an unsigned short in little endian byte order.
3015                /// </summary>
3016                /// <returns>Returns the value read.</returns>
3017                /// <exception cref="EndOfStreamException">
3018                /// The stream ends prematurely
3019                /// </exception>
3020                ushort ReadLEUshort()
3021                {
3022                        int data1 = baseStream_.ReadByte();
3023
3024                        if ( data1 < 0 ) {
3025                                throw new EndOfStreamException("End of stream");
3026                        }
3027
3028                        int data2 = baseStream_.ReadByte();
3029
3030                        if ( data2 < 0 ) {
3031                                throw new EndOfStreamException("End of stream");
3032                        }
3033
3034
3035                        return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8)));
3036                }
3037
3038                /// <summary>
3039                /// Read a uint in little endian byte order.
3040                /// </summary>
3041                /// <returns>Returns the value read.</returns>
3042                /// <exception cref="IOException">
3043                /// An i/o error occurs.
3044                /// </exception>
3045                /// <exception cref="System.IO.EndOfStreamException">
3046                /// The file ends prematurely
3047                /// </exception>
3048                uint ReadLEUint()
3049                {
3050                        return (uint)(ReadLEUshort() | (ReadLEUshort() << 16));
3051                }
3052
3053                ulong ReadLEUlong()
3054                {
3055                        return ReadLEUint() | ((ulong)ReadLEUint() << 32);
3056                }
3057
3058                #endregion
3059                // NOTE this returns the offset of the first byte after the signature.
3060                long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
3061                {
3062                        using ( ZipHelperStream les = new ZipHelperStream(baseStream_) ) {
3063                                return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData);
3064                        }
3065                }
3066               
3067                /// <summary>
3068                /// Search for and read the central directory of a zip file filling the entries array.
3069                /// </summary>
3070                /// <exception cref="System.IO.IOException">
3071                /// An i/o error occurs.
3072                /// </exception>
3073                /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
3074                /// The central directory is malformed or cannot be found
3075                /// </exception>
3076                void ReadEntries()
3077                {
3078                        // Search for the End Of Central Directory.  When a zip comment is
3079                        // present the directory will start earlier
3080                        //
3081                        // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
3082                        // This should be compatible with both SFX and ZIP files but has only been tested for Zip files
3083                        // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then
3084                        // this could be invalid.
3085                        // Could also speed this up by reading memory in larger blocks.                 
3086
3087                        if (baseStream_.CanSeek == false) {
3088                                throw new ZipException("ZipFile stream must be seekable");
3089                        }
3090                       
3091                        long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
3092                                baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
3093                       
3094                        if (locatedEndOfCentralDir < 0) {
3095                                throw new ZipException("Cannot find central directory");
3096                        }
3097
3098                        // Read end of central directory record
3099                        ushort thisDiskNumber           = ReadLEUshort();
3100                        ushort startCentralDirDisk      = ReadLEUshort();
3101                        ulong entriesForThisDisk        = ReadLEUshort();
3102                        ulong entriesForWholeCentralDir = ReadLEUshort();
3103                        ulong centralDirSize            = ReadLEUint();
3104                        long offsetOfCentralDir         = ReadLEUint();
3105                        uint commentSize                = ReadLEUshort();
3106                       
3107                        if ( commentSize > 0 ) {
3108                                byte[] comment = new byte[commentSize];
3109
3110                                StreamUtils.ReadFully(baseStream_, comment);
3111                                comment_ = ZipConstants.ConvertToString(comment);
3112                        }
3113                        else {
3114                                comment_ = string.Empty;
3115                        }
3116                       
3117                        bool isZip64 = false;
3118
3119                        // Check if zip64 header information is required.
3120                        if ( (thisDiskNumber == 0xffff) ||
3121                                (startCentralDirDisk == 0xffff) ||
3122                                (entriesForThisDisk == 0xffff) ||
3123                                (entriesForWholeCentralDir == 0xffff) ||
3124                                (centralDirSize == 0xffffffff) ||
3125                                (offsetOfCentralDir == 0xffffffff) ) {
3126                                isZip64 = true;
3127
3128                                long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, 0x1000);
3129                                if ( offset < 0 ) {
3130                                        throw new ZipException("Cannot find Zip64 locator");
3131                                }
3132
3133                                // number of the disk with the start of the zip64 end of central directory 4 bytes
3134                                // relative offset of the zip64 end of central directory record 8 bytes
3135                                // total number of disks 4 bytes
3136                                ReadLEUint(); // startDisk64 is not currently used
3137                                ulong offset64 = ReadLEUlong();
3138                                uint totalDisks = ReadLEUint();
3139
3140                                baseStream_.Position = (long)offset64;
3141                                long sig64 = ReadLEUint();
3142
3143                                if ( sig64 != ZipConstants.Zip64CentralFileHeaderSignature ) {
3144                                        throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
3145                                }
3146
3147                                // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
3148                                ulong recordSize = ( ulong )ReadLEUlong();
3149                                int versionMadeBy = ReadLEUshort();
3150                                int versionToExtract = ReadLEUshort();
3151                                uint thisDisk = ReadLEUint();
3152                                uint centralDirDisk = ReadLEUint();
3153                                entriesForThisDisk = ReadLEUlong();
3154                                entriesForWholeCentralDir = ReadLEUlong();
3155                                centralDirSize = ReadLEUlong();
3156                                offsetOfCentralDir = (long)ReadLEUlong();
3157
3158                                // NOTE: zip64 extensible data sector (variable size) is ignored.
3159                        }
3160                       
3161                        entries_ = new ZipEntry[entriesForThisDisk];
3162                       
3163                        // SFX/embedded support, find the offset of the first entry vis the start of the stream
3164                        // This applies to Zip files that are appended to the end of an SFX stub.
3165                        // Or are appended as a resource to an executable.
3166                        // Zip files created by some archivers have the offsets altered to reflect the true offsets
3167                        // and so dont require any adjustment here...
3168                        // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths?
3169                        if ( !isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize)) ) {
3170                                offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir);
3171                                if (offsetOfFirstEntry <= 0) {
3172                                        throw new ZipException("Invalid embedded zip archive");
3173                                }
3174                        }
3175
3176                        baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
3177                       
3178                        for (ulong i = 0; i < entriesForThisDisk; i++) {
3179                                if (ReadLEUint() != ZipConstants.CentralHeaderSignature) {
3180                                        throw new ZipException("Wrong Central Directory signature");
3181                                }
3182                               
3183                                int versionMadeBy      = ReadLEUshort();
3184                                int versionToExtract   = ReadLEUshort();
3185                                int bitFlags           = ReadLEUshort();
3186                                int method             = ReadLEUshort();
3187                                uint dostime           = ReadLEUint();
3188                                uint crc               = ReadLEUint();
3189                                long csize             = (long)ReadLEUint();
3190                                long size              = (long)ReadLEUint();
3191                                int nameLen            = ReadLEUshort();
3192                                int extraLen           = ReadLEUshort();
3193                                int commentLen         = ReadLEUshort();
3194                               
3195                                int diskStartNo        = ReadLEUshort();  // Not currently used
3196                                int internalAttributes = ReadLEUshort();  // Not currently used
3197
3198                                uint externalAttributes = ReadLEUint();
3199                                long offset             = ReadLEUint();
3200                               
3201                                byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
3202                               
3203                                StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen);
3204                                string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen);
3205                               
3206                                ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method);
3207                                entry.Crc = crc & 0xffffffffL;
3208                                entry.Size = size & 0xffffffffL;
3209                                entry.CompressedSize = csize & 0xffffffffL;
3210                                entry.Flags = bitFlags;
3211                                entry.DosTime = (uint)dostime;
3212                                entry.ZipFileIndex = (long)i;
3213                                entry.Offset = offset;
3214                                entry.ExternalFileAttributes = (int)externalAttributes;
3215
3216                                if ((bitFlags & 8) == 0) {
3217                                        entry.CryptoCheckValue = (byte)(crc >> 24);
3218                                }
3219                                else {
3220                                        entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
3221                                }
3222
3223                                if (extraLen > 0) {
3224                                        byte[] extra = new byte[extraLen];
3225                                        StreamUtils.ReadFully(baseStream_, extra);
3226                                        entry.ExtraData = extra;
3227                                }
3228
3229                                entry.ProcessExtraData(false);
3230                               
3231                                if (commentLen > 0) {
3232                                        StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen);
3233                                        entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen);
3234                                }
3235                               
3236                                entries_[i] = entry;
3237                        }
3238                }
3239
3240                /// <summary>
3241                /// Locate the data for a given entry.
3242                /// </summary>
3243                /// <returns>
3244                /// The start offset of the data.
3245                /// </returns>
3246                /// <exception cref="System.IO.EndOfStreamException">
3247                /// The stream ends prematurely
3248                /// </exception>
3249                /// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
3250                /// The local header signature is invalid, the entry and central header file name lengths are different
3251                /// or the local and entry compression methods dont match
3252                /// </exception>
3253                long LocateEntry(ZipEntry entry)
3254                {
3255                        return TestLocalHeader(entry, HeaderTest.Extract);
3256                }
3257               
3258#if !NETCF_1_0         
3259                Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
3260                {
3261                        CryptoStream result = null;
3262
3263                        if ( (entry.Version < ZipConstants.VersionStrongEncryption)
3264                                || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
3265                                PkzipClassicManaged classicManaged = new PkzipClassicManaged();
3266
3267                                OnKeysRequired(entry.Name);
3268                                if (HaveKeys == false) {
3269                                        throw new ZipException("No password available for encrypted stream");
3270                                }
3271
3272                                result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read);
3273                                CheckClassicPassword(result, entry);
3274                        }
3275                        else {
3276                                throw new ZipException("Decryption method not supported");
3277                        }
3278
3279                        return result;
3280                }
3281
3282                Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
3283                {
3284                        CryptoStream result = null;
3285                        if ( (entry.Version < ZipConstants.VersionStrongEncryption)
3286                                || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
3287                                PkzipClassicManaged classicManaged = new PkzipClassicManaged();
3288
3289                                OnKeysRequired(entry.Name);
3290                                if (HaveKeys == false) {
3291                                        throw new ZipException("No password available for encrypted stream");
3292                                }
3293
3294                                // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
3295                                // which doesnt do this.
3296                                result = new CryptoStream(new UncompressedStream(baseStream),
3297                                        classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
3298
3299                                if ( (entry.Crc < 0) || (entry.Flags & 8) != 0) {
3300                                        WriteEncryptionHeader(result, entry.DosTime << 16);
3301                                }
3302                                else {
3303                                        WriteEncryptionHeader(result, entry.Crc);
3304                                }
3305                        }
3306                        return result;
3307                }
3308               
3309                static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry)
3310                {
3311                        byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
3312                        StreamUtils.ReadFully(classicCryptoStream, cryptbuffer);
3313                        if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) {
3314                                throw new ZipException("Invalid password");
3315                        }
3316                }
3317#endif
3318               
3319                static void WriteEncryptionHeader(Stream stream, long crcValue)
3320                {
3321                        byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
3322                        Random rnd = new Random();
3323                        rnd.NextBytes(cryptBuffer);
3324                        cryptBuffer[11] = (byte)(crcValue >> 24);
3325                        stream.Write(cryptBuffer, 0, cryptBuffer.Length);
3326                }
3327
3328                #endregion
3329               
3330                #region Instance Fields
3331                bool       isDisposed_;
3332                string     name_;
3333                string     comment_;
3334                Stream     baseStream_;
3335                bool       isStreamOwner;
3336                long       offsetOfFirstEntry;
3337                ZipEntry[] entries_;
3338                byte[] key;
3339                bool isNewArchive_;
3340               
3341                // Default is dynamic which is not backwards compatible and can cause problems
3342                // with XP's built in compression which cant read Zip64 archives.
3343                // However it does avoid the situation were a large file is added and cannot be completed correctly.
3344                // Hint: Set always ZipEntry size before they are added to an archive and this setting isnt needed.
3345                UseZip64 useZip64_ = UseZip64.Dynamic ;
3346               
3347                #region Zip Update Instance Fields
3348                ArrayList updates_;
3349                long updateCount_; // Count is managed manually as updates_ can contain nulls!
3350                Hashtable updateIndex_;
3351                IArchiveStorage archiveStorage_;
3352                IDynamicDataSource updateDataSource_;
3353                bool contentsEdited_;
3354                int bufferSize_ = DefaultBufferSize;
3355                byte[] copyBuffer_;
3356                ZipString newComment_;
3357                bool commentEdited_;
3358                IEntryFactory updateEntryFactory_ = new ZipEntryFactory();
3359                #endregion
3360                #endregion
3361               
3362                #region Support Classes
3363                /// <summary>
3364                /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes.
3365                /// </summary>
3366                class ZipString
3367                {
3368                        #region Constructors
3369                        /// <summary>
3370                        /// Initialise a <see cref="ZipString"/> with a string.
3371                        /// </summary>
3372                        /// <param name="comment">The textual string form.</param>
3373                        public ZipString(string comment)
3374                        {
3375                                comment_ = comment;
3376                                isSourceString_ = true;
3377                        }
3378
3379                        /// <summary>
3380                        /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form.
3381                        /// </summary>
3382                        /// <param name="rawString"></param>
3383                        public ZipString(byte[] rawString)
3384                        {
3385                                rawComment_ = rawString;
3386                        }
3387                        #endregion
3388
3389                        /// <summary>
3390                        /// Get a value indicating the original source of data for this instance.
3391                        /// True if the source was a string; false if the source was binary data.
3392                        /// </summary>
3393                        public bool IsSourceString
3394                        {
3395                                get { return isSourceString_; }
3396                        }
3397                       
3398                        /// <summary>
3399                        /// Get the length of the comment when represented as raw bytes.
3400                        /// </summary>
3401                        public int RawLength
3402                        {
3403                                get {
3404                                        MakeBytesAvailable();
3405                                        return rawComment_.Length;
3406                                }
3407                        }
3408
3409                        /// <summary>
3410                        /// Get the comment in its 'raw' form as plain bytes.
3411                        /// </summary>
3412                        public byte[] RawComment
3413                        {
3414                                get {
3415                                        MakeBytesAvailable();
3416                                        return (byte[])rawComment_.Clone();
3417                                }
3418                        }
3419
3420                        /// <summary>
3421                        /// Reset the comment to its initial state.
3422                        /// </summary>
3423                        public void Reset()
3424                        {
3425                                if ( isSourceString_ ) {
3426                                        rawComment_ = null;
3427                                }
3428                                else {
3429                                        comment_ = null;
3430                                }
3431                        }
3432
3433                        void MakeTextAvailable()
3434                        {
3435                                if ( comment_ == null ) {
3436                                        comment_ = ZipConstants.ConvertToString(rawComment_);
3437                                }
3438                        }
3439
3440                        void MakeBytesAvailable()
3441                        {
3442                                if ( rawComment_ == null ) {
3443                                        rawComment_ = ZipConstants.ConvertToArray(comment_);
3444                                }
3445                        }
3446
3447                        /// <summary>
3448                        /// Implicit conversion of comment to a string.
3449                        /// </summary>
3450                        /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param>
3451                        /// <returns>The textual equivalent for the input value.</returns>
3452                        static public implicit operator string(ZipString zipString)
3453                        {
3454                                zipString.MakeTextAvailable();
3455                                return zipString.comment_;
3456                        }
3457
3458                        #region Instance Fields
3459                        string comment_;
3460                        byte[] rawComment_;
3461                        bool isSourceString_;
3462                        #endregion
3463                }
3464               
3465                /// <summary>
3466                /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see>
3467                /// </summary>
3468                class ZipEntryEnumerator : IEnumerator
3469                {
3470                        #region Constructors
3471                        public ZipEntryEnumerator(ZipEntry[] entries)
3472                        {
3473                                array = entries;
3474                        }
3475                       
3476                        #endregion
3477                        #region IEnumerator Members
3478                        public object Current
3479                        {
3480                                get {
3481                                        return array[index];
3482                                }
3483                        }
3484                       
3485                        public void Reset()
3486                        {
3487                                index = -1;
3488                        }
3489                       
3490                        public bool MoveNext()
3491                        {
3492                                return (++index < array.Length);
3493                        }
3494                        #endregion
3495                        #region Instance Fields
3496                        ZipEntry[] array;
3497                        int index = -1;
3498                        #endregion
3499                }
3500
3501                /// <summary>
3502                /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data
3503                /// to and flush, but cannot read, seek or do anything else to.
3504                /// </summary>
3505                class UncompressedStream : Stream
3506                {
3507                        #region Constructors
3508                        public UncompressedStream(Stream baseStream)
3509                        {
3510                                baseStream_ = baseStream;
3511                        }
3512
3513                        #endregion
3514
3515                        /// <summary>
3516                        /// Close this stream instance.
3517                        /// </summary>
3518                        public override void Close()
3519                        {
3520                                // Do nothing
3521                        }
3522
3523                        /// <summary>
3524                        /// Gets a value indicating whether the current stream supports reading.
3525                        /// </summary>
3526                        public override bool CanRead
3527                        {
3528                                get {
3529                                        return false;
3530                                }
3531                        }
3532
3533                        /// <summary>
3534                        /// Write any buffered data to underlying storage.
3535                        /// </summary>
3536                        public override void Flush()
3537                        {
3538                                baseStream_.Flush();
3539                        }
3540
3541                        /// <summary>
3542                        /// Gets a value indicating whether the current stream supports writing.
3543                        /// </summary>
3544                        public override bool CanWrite
3545                        {
3546                                get {
3547                                        return baseStream_.CanWrite;
3548                                }
3549                        }
3550
3551                        /// <summary>
3552                        /// Gets a value indicating whether the current stream supports seeking.
3553                        /// </summary>
3554                        public override bool CanSeek
3555                        {
3556                                get {
3557                                        return false;
3558                                }
3559                        }
3560
3561                        /// <summary>
3562                        /// Get the length in bytes of the stream.
3563                        /// </summary>
3564                        public override long Length
3565                        {
3566                                get {
3567                                        return 0;
3568                                }
3569                        }
3570
3571                        /// <summary>
3572                        /// Gets or sets the position within the current stream.
3573                        /// </summary>
3574                        public override long Position
3575                        {
3576                                get     {
3577                                        return baseStream_.Position;
3578                                }
3579                               
3580                                set
3581                                {
3582                                }
3583                        }
3584
3585                        /// <summary>
3586                        /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
3587                        /// </summary>
3588                        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
3589                        /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
3590                        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
3591                        /// <returns>
3592                        /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
3593                        /// </returns>
3594                        /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception>
3595                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3596                        /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
3597                        /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
3598                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3599                        /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
3600                        public override int Read(byte[] buffer, int offset, int count)
3601                        {
3602                                return 0;
3603                        }
3604
3605                        /// <summary>
3606                        /// Sets the position within the current stream.
3607                        /// </summary>
3608                        /// <param name="offset">A byte offset relative to the origin parameter.</param>
3609                        /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point used to obtain the new position.</param>
3610                        /// <returns>
3611                        /// The new position within the current stream.
3612                        /// </returns>
3613                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3614                        /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
3615                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3616                        public override long Seek(long offset, SeekOrigin origin)
3617                        {
3618                                return 0;
3619                        }
3620
3621                        /// <summary>
3622                        /// Sets the length of the current stream.
3623                        /// </summary>
3624                        /// <param name="value">The desired length of the current stream in bytes.</param>
3625                        /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
3626                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3627                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3628                        public override void SetLength(long value)
3629                        {
3630                        }
3631
3632                        /// <summary>
3633                        /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
3634                        /// </summary>
3635                        /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
3636                        /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
3637                        /// <param name="count">The number of bytes to be written to the current stream.</param>
3638                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3639                        /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception>
3640                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3641                        /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
3642                        /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </exception>
3643                        /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
3644                        public override void Write(byte[] buffer, int offset, int count)
3645                        {
3646                                baseStream_.Write(buffer, offset, count);
3647                        }
3648
3649                        #region Instance Fields
3650                        Stream baseStream_;
3651                        #endregion
3652                }
3653               
3654                /// <summary>
3655                /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/>
3656                /// whose data is only a part or subsection of a file.
3657                /// </summary>
3658                class PartialInputStream : Stream
3659                {
3660                        #region Constructors
3661                        /// <summary>
3662                        /// Initialise a new instance of the <see cref="PartialInputStream"/> class.
3663                        /// </summary>
3664                        /// <param name="zipFile">The <see cref="ZipFile"/> containing the underlying stream to use for IO.</param>
3665                        /// <param name="start">The start of the partial data.</param>
3666                        /// <param name="length">The length of the partial data.</param>
3667                        public PartialInputStream(ZipFile zipFile, long start, long length)
3668                        {
3669                                start_ = start;
3670                                length_ = length;
3671
3672                                // Although this is the only time the zipfile is used
3673                                // keeping a reference here prevents premature closure of
3674                                // this zip file and thus the baseStream_.
3675
3676                                // Code like this will cause apparently random failures depending
3677                                // on the size of the files and when garbage is collected.
3678                                //
3679                                // ZipFile z = new ZipFile (stream);
3680                                // Stream reader = z.GetInputStream(0);
3681                                // uses reader here....
3682                                zipFile_ = zipFile;
3683                                baseStream_ = zipFile_.baseStream_;
3684                                readPos_ = start;
3685                                end_ = start + length;
3686                        }
3687                        #endregion
3688
3689                        /// <summary>
3690                        /// Read a byte from this stream.
3691                        /// </summary>
3692                        /// <returns>Returns the byte read or -1 on end of stream.</returns>
3693                        public override int ReadByte()
3694                        {
3695                                if (readPos_ >= end_) {
3696                                         // -1 is the correct value at end of stream.
3697                                        return -1;
3698                                }
3699                               
3700                                lock( baseStream_ ) {
3701                                        baseStream_.Seek(readPos_++, SeekOrigin.Begin);
3702                                        return baseStream_.ReadByte();
3703                                }
3704                        }
3705                       
3706                        /// <summary>
3707                        /// Close this <see cref="PartialInputStream">partial input stream</see>.
3708                        /// </summary>
3709                        /// <remarks>
3710                        /// The underlying stream is not closed.  Close the parent ZipFile class to do that.
3711                        /// </remarks>
3712                        public override void Close()
3713                        {
3714                                // Do nothing at all!
3715                        }
3716
3717                        /// <summary>
3718                        /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
3719                        /// </summary>
3720                        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param>
3721                        /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param>
3722                        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
3723                        /// <returns>
3724                        /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
3725                        /// </returns>
3726                        /// <exception cref="T:System.ArgumentException">The sum of offset and count is larger than the buffer length. </exception>
3727                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3728                        /// <exception cref="T:System.NotSupportedException">The stream does not support reading. </exception>
3729                        /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
3730                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3731                        /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
3732                        public override int Read(byte[] buffer, int offset, int count)
3733                        {
3734                                lock(baseStream_) {
3735                                        if (count > end_ - readPos_) {
3736                                                count = (int) (end_ - readPos_);
3737                                                if (count == 0) {
3738                                                        return 0;
3739                                                }
3740                                        }
3741                                       
3742                                        baseStream_.Seek(readPos_, SeekOrigin.Begin);
3743                                        int readCount = baseStream_.Read(buffer, offset, count);
3744                                        if (readCount > 0) {
3745                                                readPos_ += readCount;
3746                                        }
3747                                        return readCount;
3748                                }
3749                        }
3750
3751                        /// <summary>
3752                        /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
3753                        /// </summary>
3754                        /// <param name="buffer">An array of bytes. This method copies count bytes from buffer to the current stream.</param>
3755                        /// <param name="offset">The zero-based byte offset in buffer at which to begin copying bytes to the current stream.</param>
3756                        /// <param name="count">The number of bytes to be written to the current stream.</param>
3757                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3758                        /// <exception cref="T:System.NotSupportedException">The stream does not support writing. </exception>
3759                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3760                        /// <exception cref="T:System.ArgumentNullException">buffer is null. </exception>
3761                        /// <exception cref="T:System.ArgumentException">The sum of offset and count is greater than the buffer length. </exception>
3762                        /// <exception cref="T:System.ArgumentOutOfRangeException">offset or count is negative. </exception>
3763                        public override void Write(byte[] buffer, int offset, int count)
3764                        {
3765                                throw new NotSupportedException();
3766                        }
3767
3768                        /// <summary>
3769                        /// When overridden in a derived class, sets the length of the current stream.
3770                        /// </summary>
3771                        /// <param name="value">The desired length of the current stream in bytes.</param>
3772                        /// <exception cref="T:System.NotSupportedException">The stream does not support both writing and seeking, such as if the stream is constructed from a pipe or console output. </exception>
3773                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3774                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3775                        public override void SetLength(long value)
3776                        {
3777                                throw new NotSupportedException();
3778                        }
3779
3780                        /// <summary>
3781                        /// When overridden in a derived class, sets the position within the current stream.
3782                        /// </summary>
3783                        /// <param name="offset">A byte offset relative to the origin parameter.</param>
3784                        /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin"></see> indicating the reference point used to obtain the new position.</param>
3785                        /// <returns>
3786                        /// The new position within the current stream.
3787                        /// </returns>
3788                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3789                        /// <exception cref="T:System.NotSupportedException">The stream does not support seeking, such as if the stream is constructed from a pipe or console output. </exception>
3790                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3791                        public override long Seek(long offset, SeekOrigin origin)
3792                        {
3793                                long newPos = readPos_;
3794                               
3795                                switch ( origin )
3796                                {
3797                                        case SeekOrigin.Begin:
3798                                                newPos = start_ + offset;
3799                                                break;
3800                                               
3801                                        case SeekOrigin.Current:
3802                                                newPos = readPos_ + offset;
3803                                                break;
3804                                               
3805                                        case SeekOrigin.End:
3806                                                newPos = end_ + offset;
3807                                                break;
3808                                }
3809                               
3810                                if ( newPos < start_ ) {
3811                                        throw new ArgumentException("Negative position is invalid");
3812                                }
3813                               
3814                                if ( newPos >= end_ ) {
3815                                        throw new IOException("Cannot seek past end");
3816                                }
3817                                readPos_ = newPos;
3818                                return readPos_;
3819                        }
3820
3821                        /// <summary>
3822                        /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device.
3823                        /// </summary>
3824                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3825                        public override void Flush()
3826                        {
3827                                // Nothing to do.
3828                        }
3829
3830                        /// <summary>
3831                        /// Gets or sets the position within the current stream.
3832                        /// </summary>
3833                        /// <value></value>
3834                        /// <returns>The current position within the stream.</returns>
3835                        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
3836                        /// <exception cref="T:System.NotSupportedException">The stream does not support seeking. </exception>
3837                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3838                        public override long Position {
3839                                get { return readPos_ - start_; }
3840                                set {
3841                                        long newPos = start_ + value;
3842                                       
3843                                        if ( newPos < start_ ) {
3844                                                throw new ArgumentException("Negative position is invalid");
3845                                        }
3846                                       
3847                                        if ( newPos >= end_ ) {
3848                                                throw new InvalidOperationException("Cannot seek past end");
3849                                        }
3850                                        readPos_ = newPos;
3851                                }
3852                        }
3853
3854                        /// <summary>
3855                        /// Gets the length in bytes of the stream.
3856                        /// </summary>
3857                        /// <value></value>
3858                        /// <returns>A long value representing the length of the stream in bytes.</returns>
3859                        /// <exception cref="T:System.NotSupportedException">A class derived from Stream does not support seeking. </exception>
3860                        /// <exception cref="T:System.ObjectDisposedException">Methods were called after the stream was closed. </exception>
3861                        public override long Length {
3862                                get { return length_; }
3863                        }
3864
3865                        /// <summary>
3866                        /// Gets a value indicating whether the current stream supports writing.
3867                        /// </summary>
3868                        /// <value>false</value>
3869                        /// <returns>true if the stream supports writing; otherwise, false.</returns>
3870                        public override bool CanWrite {
3871                                get { return false; }
3872                        }
3873
3874                        /// <summary>
3875                        /// Gets a value indicating whether the current stream supports seeking.
3876                        /// </summary>
3877                        /// <value>true</value>
3878                        /// <returns>true if the stream supports seeking; otherwise, false.</returns>
3879                        public override bool CanSeek {
3880                                get { return true; }
3881                        }
3882
3883                        /// <summary>
3884                        /// Gets a value indicating whether the current stream supports reading.
3885                        /// </summary>
3886                        /// <value>true.</value>
3887                        /// <returns>true if the stream supports reading; otherwise, false.</returns>
3888                        public override bool CanRead {
3889                                get { return true; }
3890                        }
3891                       
3892#if !NET_1_0 && !NET_1_1 && !NETCF_1_0
3893                        /// <summary>
3894                        /// Gets a value that determines whether the current stream can time out.
3895                        /// </summary>
3896                        /// <value></value>
3897                        /// <returns>A value that determines whether the current stream can time out.</returns>
3898                        public override bool CanTimeout {
3899                                get { return baseStream_.CanTimeout; }
3900                        }
3901#endif                 
3902                        #region Instance Fields
3903                        ZipFile zipFile_;
3904                        Stream baseStream_;
3905                        long start_;
3906                        long length_;
3907                        long readPos_;
3908                        long end_;
3909                        #endregion     
3910                }
3911                #endregion
3912        }
3913
3914        #endregion
3915       
3916        #region DataSources
3917        /// <summary>
3918        /// Provides a static way to obtain a source of data for an entry.
3919        /// </summary>
3920        public interface IStaticDataSource
3921        {
3922                /// <summary>
3923                /// Get a source of data by creating a new stream.
3924                /// </summary>
3925                /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns>
3926                /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks>
3927                Stream GetSource();
3928        }
3929
3930        /// <summary>
3931        /// Represents a source of data that can dynamically provide
3932        /// multiple <see cref="Stream">data sources</see> based on the parameters passed.
3933        /// </summary>
3934        public interface IDynamicDataSource
3935        {
3936                /// <summary>
3937                /// Get a data source.
3938                /// </summary>
3939                /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param>
3940                /// <param name="name">The name for data if known.</param>
3941                /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns>
3942                /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks>
3943                Stream GetSource(ZipEntry entry, string name);
3944        }
3945
3946        /// <summary>
3947        /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk.
3948        /// </summary>
3949        public class StaticDiskDataSource : IStaticDataSource
3950        {
3951                /// <summary>
3952                /// Initialise a new instnace of <see cref="StaticDiskDataSource"/>
3953                /// </summary>
3954                /// <param name="fileName">The name of the file to obtain data from.</param>
3955                public StaticDiskDataSource(string fileName)
3956                {
3957                        fileName_ = fileName;
3958                }
3959
3960                #region IDataSource Members
3961
3962                /// <summary>
3963                /// Get a <see cref="Stream"/> providing data.
3964                /// </summary>
3965                /// <returns>Returns a <see cref="Stream"/> provising data.</returns>
3966                public Stream GetSource()
3967                {
3968                        return File.OpenRead(fileName_);
3969                }
3970
3971                #endregion
3972                #region Instance Fields
3973                string fileName_;
3974                #endregion
3975        }
3976
3977
3978        /// <summary>
3979        /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk.
3980        /// </summary>
3981        public class DynamicDiskDataSource : IDynamicDataSource
3982        {
3983                /// <summary>
3984                /// Initialise a default instance of <see cref="DynamicDiskDataSource"/>.
3985                /// </summary>
3986                public DynamicDiskDataSource()
3987                {
3988                }
3989
3990                #region IDataSource Members
3991                /// <summary>
3992                /// Get a <see cref="Stream"/> providing data for an entry.
3993                /// </summary>
3994                /// <param name="entry">The entry to provide data for.</param>
3995                /// <param name="name">The file name for data if known.</param>
3996                /// <returns>Returns a stream providing data; or null if not available</returns>
3997                public Stream GetSource(ZipEntry entry, string name)
3998                {
3999                        Stream result = null;
4000
4001                        if ( name != null ) {
4002                                result = File.OpenRead(name);
4003                        }
4004
4005                        return result;
4006                }
4007
4008                #endregion
4009        }
4010
4011        #endregion
4012       
4013        #region Archive Storage
4014        /// <summary>
4015        /// Defines facilities for data storage when updating Zip Archives.
4016        /// </summary>
4017        public interface IArchiveStorage
4018        {
4019                /// <summary>
4020                /// Get the <see cref="FileUpdateMode"/> to apply during updates.
4021                /// </summary>
4022                FileUpdateMode UpdateMode { get; }
4023
4024                /// <summary>
4025                /// Get an empty <see cref="Stream"/> that can be used for temporary output.
4026                /// </summary>
4027                /// <returns>Returns a temporary output <see cref="Stream"/></returns>
4028                /// <seealso cref="ConvertTemporaryToFinal"></seealso>
4029                Stream GetTemporaryOutput();
4030
4031                /// <summary>
4032                /// Convert a temporary output stream to a final stream.
4033                /// </summary>
4034                /// <returns>The resulting final <see cref="Stream"/></returns>
4035                /// <seealso cref="GetTemporaryOutput"/>
4036                Stream ConvertTemporaryToFinal();
4037
4038                /// <summary>
4039                /// Make a temporary copy of the original stream.
4040                /// </summary>
4041                /// <param name="stream">The <see cref="Stream"/> to copy.</param>
4042                /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
4043                Stream MakeTemporaryCopy(Stream stream);
4044
4045                /// <summary>
4046                /// Return a stream suitable for performing direct updates on the original source.
4047                /// </summary>
4048                /// <param name="stream">The current stream.</param>
4049                /// <returns>Returns a stream suitable for direct updating.</returns>
4050                /// <remarks>This may be the current stream passed.</remarks>
4051                Stream OpenForDirectUpdate(Stream stream);
4052
4053                /// <summary>
4054                /// Dispose of this instance.
4055                /// </summary>
4056                void Dispose();
4057        }
4058
4059        /// <summary>
4060        /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance.
4061        /// </summary>
4062        abstract public class BaseArchiveStorage : IArchiveStorage
4063        {
4064                #region Constructors
4065                /// <summary>
4066                /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class.
4067                /// </summary>
4068                /// <param name="updateMode">The update mode.</param>
4069                protected BaseArchiveStorage(FileUpdateMode updateMode)
4070                {
4071                        updateMode_ = updateMode;
4072                }
4073                #endregion
4074               
4075                #region IArchiveStorage Members
4076
4077                /// <summary>
4078                /// Gets a temporary output <see cref="Stream"/>
4079                /// </summary>
4080                /// <returns>Returns the temporary output stream.</returns>
4081                /// <seealso cref="ConvertTemporaryToFinal"></seealso>
4082                public abstract Stream GetTemporaryOutput();
4083
4084                /// <summary>
4085                /// Converts the temporary <see cref="Stream"/> to its final form.
4086                /// </summary>
4087                /// <returns>Returns a <see cref="Stream"/> that can be used to read
4088                /// the final storage for the archive.</returns>
4089                /// <seealso cref="GetTemporaryOutput"/>
4090                public abstract Stream ConvertTemporaryToFinal();
4091
4092                /// <summary>
4093                /// Make a temporary copy of a <see cref="Stream"/>.
4094                /// </summary>
4095                /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param>
4096                /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
4097                public abstract Stream MakeTemporaryCopy(Stream stream);
4098
4099                /// <summary>
4100                /// Return a stream suitable for performing direct updates on the original source.
4101                /// </summary>
4102                /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param>
4103                /// <returns>Returns a stream suitable for direct updating.</returns>
4104                public abstract Stream OpenForDirectUpdate(Stream stream);
4105
4106                /// <summary>
4107                /// Disposes this instance.
4108                /// </summary>
4109                public abstract void Dispose();
4110
4111                /// <summary>
4112                /// Gets the update mode applicable.
4113                /// </summary>
4114                /// <value>The update mode.</value>
4115                public FileUpdateMode UpdateMode
4116                {
4117                        get {
4118                                return updateMode_;
4119                        }
4120                }
4121
4122                #endregion
4123
4124                #region Instance Fields
4125                FileUpdateMode updateMode_;
4126                #endregion
4127        }
4128
4129        /// <summary>
4130        /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks.
4131        /// </summary>
4132        public class DiskArchiveStorage : BaseArchiveStorage
4133        {
4134                #region Constructors
4135                /// <summary>
4136                /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class.
4137                /// </summary>
4138                /// <param name="file">The file.</param>
4139                /// <param name="updateMode">The update mode.</param>
4140                public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode)
4141                        : base(updateMode)
4142                {
4143                        if ( file.Name == null ) {
4144                                throw new ZipException("Cant handle non file archives");
4145                        }
4146
4147                        fileName_ = file.Name;
4148                }
4149
4150                /// <summary>
4151                /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class.
4152                /// </summary>
4153                /// <param name="file">The file.</param>
4154                public DiskArchiveStorage(ZipFile file)
4155                        : this(file, FileUpdateMode.Safe)
4156                {
4157                }
4158                #endregion
4159
4160                #region IArchiveStorage Members
4161
4162                /// <summary>
4163                /// Gets a temporary output <see cref="Stream"/> for performing updates on.
4164                /// </summary>
4165                /// <returns>Returns the temporary output stream.</returns>
4166                public override Stream GetTemporaryOutput()
4167                {
4168                        if ( temporaryName_ != null ) {
4169                                temporaryName_ = GetTempFileName(temporaryName_, true);
4170                                temporaryStream_ = File.OpenWrite(temporaryName_);
4171                        }
4172                        else {
4173                                // Determine where to place files based on internal strategy.
4174                                // Currently this is always done in system temp directory.
4175                                temporaryName_ = Path.GetTempFileName();
4176                                temporaryStream_ = File.OpenWrite(temporaryName_);
4177                        }
4178
4179                        return temporaryStream_;
4180                }
4181
4182                /// <summary>
4183                /// Converts a temporary <see cref="Stream"/> to its final form.
4184                /// </summary>
4185                /// <returns>Returns a <see cref="Stream"/> that can be used to read
4186                /// the final storage for the archive.</returns>
4187                public override Stream ConvertTemporaryToFinal()
4188                {
4189                        if ( temporaryStream_ == null ) {
4190                                throw new ZipException("No temporary stream has been created");
4191                        }
4192
4193                        Stream result = null;
4194
4195                        string moveTempName = GetTempFileName(fileName_, false);
4196                        bool newFileCreated = false;
4197
4198                        try     {
4199                                temporaryStream_.Close();
4200                                File.Move(fileName_, moveTempName);
4201                                File.Move(temporaryName_, fileName_);
4202                                newFileCreated = true;
4203                                File.Delete(moveTempName);
4204
4205                                result = File.OpenRead(fileName_);
4206                        }
4207                        catch(Exception) {
4208                                result  = null;
4209
4210                                // Try to roll back changes...
4211                                if ( !newFileCreated ) {
4212                                        File.Move(moveTempName, fileName_);
4213                                        File.Delete(temporaryName_);
4214                                }
4215
4216                                throw;
4217                        }
4218
4219                        return result;
4220                }
4221
4222                /// <summary>
4223                /// Make a temporary copy of a stream.
4224                /// </summary>
4225                /// <param name="stream">The <see cref="Stream"/> to copy.</param>
4226                /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
4227                public override Stream MakeTemporaryCopy(Stream stream)
4228                {
4229                        stream.Close();
4230
4231                        temporaryName_ = GetTempFileName(fileName_, true);
4232                        File.Copy(fileName_, temporaryName_, true);
4233                       
4234                        temporaryStream_ = new FileStream(temporaryName_,
4235                                FileMode.Open,
4236                                FileAccess.ReadWrite);
4237                        return temporaryStream_;
4238                }
4239
4240                /// <summary>
4241                /// Return a stream suitable for performing direct updates on the original source.
4242                /// </summary>
4243                /// <param name="stream">The current stream.</param>
4244                /// <returns>Returns a stream suitable for direct updating.</returns>
4245                /// <remarks>If the <paramref name="current"/> stream is not null this is used as is.</remarks>
4246                public override Stream OpenForDirectUpdate(Stream stream)
4247                {
4248                        Stream result;
4249                        if ((stream == null) || !stream.CanWrite)
4250                        {
4251                                if (stream != null) {
4252                                        stream.Close();
4253                                }
4254
4255                                result = new FileStream(fileName_,
4256                                                FileMode.Open,
4257                                                FileAccess.ReadWrite);
4258                        }
4259                        else
4260                        {
4261                                result = stream;
4262                        }
4263
4264                        return result;
4265                }
4266
4267                /// <summary>
4268                /// Disposes this instance.
4269                /// </summary>
4270                public override void Dispose()
4271                {
4272                        if ( temporaryStream_ != null ) {
4273                                temporaryStream_.Close();
4274                        }
4275                }
4276
4277                #endregion
4278
4279                #region Internal routines
4280                static string GetTempFileName(string original, bool makeTempFile)
4281                {
4282                        string result = null;
4283                               
4284                        if ( original == null ) {
4285                                result = Path.GetTempFileName();
4286                        }
4287                        else {
4288                                int counter = 0;
4289                                int suffixSeed = DateTime.Now.Second;
4290
4291                                while ( result == null ) {
4292                                        counter += 1;
4293                                        string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter);
4294                                        if ( !File.Exists(newName) ) {
4295                                                if ( makeTempFile) {
4296                                                        try     {
4297                                                                // Try and create the file.
4298                                                                using ( FileStream stream = File.Create(newName) ) {
4299                                                                }
4300                                                                result = newName;
4301                                                        }
4302                                                        catch {
4303                                                                suffixSeed = DateTime.Now.Second;
4304                                                        }
4305                                                }
4306                                                else {
4307                                                        result = newName;
4308                                                }
4309                                        }
4310                                }
4311                        }
4312                        return result;
4313                }
4314                #endregion
4315
4316                #region Instance Fields
4317                Stream temporaryStream_;
4318                string fileName_;
4319                string temporaryName_;
4320                #endregion
4321        }
4322
4323        /// <summary>
4324        /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams.
4325        /// </summary>
4326        public class MemoryArchiveStorage : BaseArchiveStorage
4327        {
4328                #region Constructors
4329                /// <summary>
4330                /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class.
4331                /// </summary>
4332                public MemoryArchiveStorage()
4333                        : base(FileUpdateMode.Direct)
4334                {
4335                }
4336
4337                /// <summary>
4338                /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class.
4339                /// </summary>
4340                /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param>
4341                /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks>
4342                public MemoryArchiveStorage(FileUpdateMode updateMode)
4343                        : base(updateMode)
4344                {
4345                }
4346
4347                #endregion
4348
4349                #region Properties
4350                /// <summary>
4351                /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called.
4352                /// </summary>
4353                public MemoryStream FinalStream
4354                {
4355                        get { return finalStream_; }
4356                }
4357
4358                #endregion
4359
4360                #region IArchiveStorage Members
4361
4362                /// <summary>
4363                /// Gets the temporary output <see cref="Stream"/>
4364                /// </summary>
4365                /// <returns>Returns the temporary output stream.</returns>
4366                public override Stream GetTemporaryOutput()
4367                {
4368                        temporaryStream_ = new MemoryStream();
4369                        return temporaryStream_;
4370                }
4371
4372                /// <summary>
4373                /// Converts the temporary <see cref="Stream"/> to its final form.
4374                /// </summary>
4375                /// <returns>Returns a <see cref="Stream"/> that can be used to read
4376                /// the final storage for the archive.</returns>
4377                public override Stream ConvertTemporaryToFinal()
4378                {
4379                        if ( temporaryStream_ == null ) {
4380                                throw new ZipException("No temporary stream has been created");
4381                        }
4382
4383                        finalStream_ = new MemoryStream(temporaryStream_.ToArray());
4384                        return finalStream_;
4385                }
4386
4387                /// <summary>
4388                /// Make a temporary copy of the original stream.
4389                /// </summary>
4390                /// <param name="stream">The <see cref="Stream"/> to copy.</param>
4391                /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
4392                public override Stream MakeTemporaryCopy(Stream stream)
4393                {
4394                        temporaryStream_ = new MemoryStream();
4395                        stream.Position = 0;
4396                        StreamUtils.Copy(stream, temporaryStream_, new byte[4096]);
4397                        return temporaryStream_;
4398                }
4399
4400                /// <summary>
4401                /// Return a stream suitable for performing direct updates on the original source.
4402                /// </summary>
4403                /// <param name="stream">The original source stream</param>
4404                /// <returns>Returns a stream suitable for direct updating.</returns>
4405                /// <remarks>If the <paramref name="stream"/> passed is not null this is used;
4406                /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks>
4407                public override Stream OpenForDirectUpdate(Stream stream)
4408                {
4409                        Stream result;
4410                        if ((stream == null) || !stream.CanWrite) {
4411
4412                                result = new MemoryStream();
4413
4414                                if (stream != null) {
4415                                        stream.Position = 0;
4416                                        StreamUtils.Copy(stream, result, new byte[4096]);
4417
4418                                        stream.Close();
4419                                }
4420                        }
4421                        else {
4422                                result = stream;
4423                        }
4424
4425                        return result;
4426                }
4427
4428                /// <summary>
4429                /// Disposes this instance.
4430                /// </summary>
4431                public override void Dispose()
4432                {
4433                        if ( temporaryStream_ != null ) {
4434                                temporaryStream_.Close();
4435                        }
4436                }
4437
4438                #endregion
4439
4440                #region Instance Fields
4441                MemoryStream temporaryStream_;
4442                MemoryStream finalStream_;
4443                #endregion
4444        }
4445
4446        #endregion
4447}
Notatka: Zobacz TracBrowser aby uzyskać więcej informacji.