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