Statistics
| Branch: | Revision:

root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipFile.cs @ 0eea575a

History | View | Annotate | Download (143.4 kB)

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
}