Summary

Class:ICSharpCode.SharpZipLib.Zip.BaseArchiveStorage
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs
Covered lines:4
Uncovered lines:0
Coverable lines:4
Total lines:4263
Line coverage:100%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
.ctor(...)1100100

File(s)

C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs

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