Summary

Class:ICSharpCode.SharpZipLib.Zip.ZipFile
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipFile.cs
Covered lines:953
Uncovered lines:356
Coverable lines:1309
Total lines:4263
Line coverage:72.8%
Branch coverage:58.6%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
OnKeysRequired(...)24066.67
.ctor(...)293.3366.67
.ctor(...)335.2940
.ctor(...)473.6871.43
.ctor()1100100
Finalize()1100100
Close()1100100
Create(...)287.566.67
Create(...)466.6757.14
GetEnumerator()266.6766.67
FindEntry(...)487.585.71
GetEntry(...)300
GetInputStream(...)75046.15
GetInputStream(...)678.5772.73
TestArchive(...)1100100
TestArchive(...)2553.6260
TestLocalHeader(...)7465.9655.24
BeginUpdate(...)987.1063.64
BeginUpdate(...)1100100
BeginUpdate()2100100
CommitUpdate()79077.78
AbortUpdate()100
SetComment(...)366.6760
AddUpdate(...)363.6440
Add(...)400
Add(...)300
Add(...)28066.67
Add(...)300
Add(...)371.4360
Add(...)377.7860
Add(...)38060
Add(...)471.4357.14
AddDirectory(...)200
Delete(...)484.6257.14
Delete(...)38060
WriteLEShort(...)1100100
WriteLEUshort(...)1100100
WriteLEInt(...)1100100
WriteLEUint(...)1100100
WriteLeLong(...)1100100
WriteLEUlong(...)100
WriteLocalEntryHeader(...)2093.6582.05
WriteCentralDirectoryHeader(...)2483.6166.67
PostUpdateCleanup()2100100
GetTransformedFileName(...)210066.67
GetTransformedDirectoryName(...)200
GetBuffer()2100100
CopyDescriptorBytes(...)42528.57
CopyBytes(...)990.9164.71
GetDescriptorSize(...)35040
CopyDescriptorBytesDirect(...)32040
CopyEntryDataDirect(...)88853.33
FindExistingUpdate(...)210066.67
FindExistingUpdate(...)2100100
GetOutputStream(...)491.6785.71
AddEntry(...)996.3084.62
ModifyEntry(...)500
CopyEntryDirect(...)510088.89
CopyEntry(...)210066.67
Reopen(...)283.3366.67
Reopen()200
UpdateCommentOnly()687.1077.78
RunUpdates()2991.1484.62
CheckUpdating()266.6766.67
System.IDisposable.Dispose()1100100
DisposeInternal(...)5100100
Dispose(...)1100100
ReadLEUshort()371.4360
ReadLEUint()1100100
ReadLEUlong()1100100
LocateBlockWithSignature(...)2100100
ReadEntries()2092.3974.36
LocateEntry(...)1100100
CreateAndInitDecryptionStream(...)931.0323.53
CreateAndInitEncryptionStream(...)683.3354.55
CheckClassicPassword(...)28066.67
WriteEncryptionHeader(...)1100100
Compare(...)1310088.24
.ctor(...)1100100
.ctor(...)100
.ctor(...)100
.ctor(...)100
.ctor(...)1100100
.ctor(...)100
.ctor(...)1100100
.ctor(...)1100100
GetSource()2100100
.ctor(...)1100100
.ctor(...)100
Reset()200
MakeTextAvailable()200
MakeBytesAvailable()2100100
op_Implicit(...)100
.ctor(...)1100100
Reset()100
MoveNext()1100100
.ctor(...)1100100
Close()1100100
Flush()100
Read(...)100
Seek(...)100
SetLength(...)100
Write(...)1100100
.ctor(...)1100100
ReadByte()383.3366.67
Close()1100100
Read(...)691.6777.78
Write(...)100
SetLength(...)100
Seek(...)661.5444.44
Flush()100

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    {
 15309       if (KeysRequired != null) {
 0310        var krea = new KeysRequiredEventArgs(fileName, key);
 0311        KeysRequired(this, krea);
 0312        key = krea.Key;
 313      }
 15314    }
 315
 316    /// <summary>
 317    /// Get/set the encryption key value.
 318    /// </summary>
 319    byte[] Key {
 0320      get { return key; }
 0321      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 {
 16330         if (string.IsNullOrEmpty(value)) {
 5331          key = null;
 5332        } else {
 11333          rawPassword_ = value;
 11334          key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value));
 335        }
 11336      }
 337    }
 338
 339    /// <summary>
 340    /// Get a value indicating wether encryption keys are currently available.
 341    /// </summary>
 342    bool HaveKeys {
 65720343      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>
 15359    public ZipFile(string name)
 360    {
 15361       if (name == null) {
 0362        throw new ArgumentNullException(nameof(name));
 363      }
 364
 15365      name_ = name;
 366
 15367      baseStream_ = File.Open(name, FileMode.Open, FileAccess.Read, FileShare.Read);
 15368      isStreamOwner = true;
 369
 370      try {
 15371        ReadEntries();
 15372      } catch {
 1373        DisposeInternal(true);
 1374        throw;
 375      }
 14376    }
 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>
 1389    public ZipFile(FileStream file)
 390    {
 1391       if (file == null) {
 1392        throw new ArgumentNullException(nameof(file));
 393      }
 394
 0395       if (!file.CanSeek) {
 0396        throw new ArgumentException("Stream is not seekable", nameof(file));
 397      }
 398
 0399      baseStream_ = file;
 0400      name_ = file.Name;
 0401      isStreamOwner = true;
 402
 403      try {
 0404        ReadEntries();
 0405      } catch {
 0406        DisposeInternal(true);
 0407        throw;
 408      }
 0409    }
 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>
 62427    public ZipFile(Stream stream)
 428    {
 62429       if (stream == null) {
 0430        throw new ArgumentNullException(nameof(stream));
 431      }
 432
 62433       if (!stream.CanSeek) {
 0434        throw new ArgumentException("Stream is not seekable", nameof(stream));
 435      }
 436
 62437      baseStream_ = stream;
 62438      isStreamOwner = true;
 439
 62440       if (baseStream_.Length > 0) {
 441        try {
 52442          ReadEntries();
 52443        } catch {
 0444          DisposeInternal(true);
 0445          throw;
 446        }
 447      } else {
 10448        entries_ = new ZipEntry[0];
 10449        isNewArchive_ = true;
 450      }
 62451    }
 452
 453    /// <summary>
 454    /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage.
 455    /// </summary>
 10456    internal ZipFile()
 457    {
 10458      entries_ = new ZipEntry[0];
 10459      isNewArchive_ = true;
 10460    }
 461
 462    #endregion
 463
 464    #region Destructors and Closing
 465    /// <summary>
 466    /// Finalize this instance.
 467    /// </summary>
 468    ~ZipFile()
 469    {
 6470      Dispose(false);
 12471    }
 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    {
 91482      DisposeInternal(true);
 91483      GC.SuppressFinalize(this);
 91484    }
 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    {
 7497       if (fileName == null) {
 0498        throw new ArgumentNullException(nameof(fileName));
 499      }
 500
 7501      FileStream fs = File.Create(fileName);
 502
 7503      var result = new ZipFile();
 7504      result.name_ = fileName;
 7505      result.baseStream_ = fs;
 7506      result.isStreamOwner = true;
 7507      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    {
 3519       if (outStream == null) {
 0520        throw new ArgumentNullException(nameof(outStream));
 521      }
 522
 3523       if (!outStream.CanWrite) {
 0524        throw new ArgumentException("Stream is not writeable", nameof(outStream));
 525      }
 526
 3527       if (!outStream.CanSeek) {
 0528        throw new ArgumentException("Stream is not seekable", nameof(outStream));
 529      }
 530
 3531      var result = new ZipFile();
 3532      result.baseStream_ = outStream;
 3533      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 {
 88547      get { return isStreamOwner; }
 74548      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
 48557      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 {
 65793564      get { return isNewArchive_; }
 565    }
 566
 567    /// <summary>
 568    /// Gets the comment for the zip file.
 569    /// </summary>
 570    public string ZipFileComment {
 6571      get { return comment_; }
 572    }
 573
 574    /// <summary>
 575    /// Gets the name of this zip file.
 576    /// </summary>
 577    public string Name {
 38578      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 {
 0590        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 {
 66100599        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 {
 329670609        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    {
 4625       if (isDisposed_) {
 0626        throw new ObjectDisposedException("ZipFile");
 627      }
 628
 4629      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    {
 12643       if (isDisposed_) {
 0644        throw new ObjectDisposedException("ZipFile");
 645      }
 646
 647      // TODO: This will be slow as the next ice age for huge archives!
 62648       for (int i = 0; i < entries_.Length; i++) {
 28649         if (string.Compare(name, entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0) {
 9650          return i;
 651        }
 652      }
 3653      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    {
 0671       if (isDisposed_) {
 0672        throw new ObjectDisposedException("ZipFile");
 673      }
 674
 0675      int index = FindEntry(name, true);
 0676       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    {
 65933696       if (entry == null) {
 0697        throw new ArgumentNullException(nameof(entry));
 698      }
 699
 65933700       if (isDisposed_) {
 0701        throw new ObjectDisposedException("ZipFile");
 702      }
 703
 65933704      long index = entry.ZipFileIndex;
 65933705       if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) {
 0706        index = FindEntry(entry.Name, true);
 0707         if (index < 0) {
 0708          throw new ZipException("Entry cannot be found");
 709        }
 710      }
 65933711      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    {
 65946732       if (isDisposed_) {
 0733        throw new ObjectDisposedException("ZipFile");
 734      }
 735
 65946736      long start = LocateEntry(entries_[entryIndex]);
 65946737      CompressionMethod method = entries_[entryIndex].CompressionMethod;
 65946738      Stream result = new PartialInputStream(this, start, entries_[entryIndex].CompressedSize);
 739
 65946740       if (entries_[entryIndex].IsCrypted == true) {
 10741        result = CreateAndInitDecryptionStream(result, entries_[entryIndex]);
 10742         if (result == null) {
 0743          throw new ZipException("Unable to decrypt this entry");
 744        }
 745      }
 746
 65946747       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.
 353754          result = new InflaterInputStream(result, new Inflater(true));
 353755          break;
 756
 757        default:
 0758          throw new ZipException("Unsupported compression method " + method);
 759      }
 760
 65946761      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    {
 96775      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    {
 96788       if (isDisposed_) {
 0789        throw new ObjectDisposedException("ZipFile");
 790      }
 791
 96792      var status = new TestStatus(this);
 793
 96794       if (resultHandler != null) {
 0795        resultHandler(status, null);
 796      }
 797
 96798       HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header;
 799
 96800      bool testing = true;
 801
 802      try {
 96803        int entryIndex = 0;
 804
 66012805         while (testing && (entryIndex < Count)) {
 65916806           if (resultHandler != null) {
 0807            status.SetEntry(this[entryIndex]);
 0808            status.SetOperation(TestOperation.EntryHeader);
 0809            resultHandler(status, null);
 810          }
 811
 812          try {
 65916813            TestLocalHeader(this[entryIndex], test);
 65916814          } catch (ZipException ex) {
 0815            status.AddError();
 816
 0817             if (resultHandler != null) {
 0818              resultHandler(status,
 0819                string.Format("Exception during test - '{0}'", ex.Message));
 820            }
 821
 0822            testing &= strategy != TestStrategy.FindFirstError;
 0823          }
 824
 65916825           if (testing && testData && this[entryIndex].IsFile) {
 65912826             if (resultHandler != null) {
 0827              status.SetOperation(TestOperation.EntryData);
 0828              resultHandler(status, null);
 829            }
 830
 65912831            var crc = new Crc32();
 832
 65912833            using (Stream entryStream = this.GetInputStream(this[entryIndex])) {
 834
 65912835              byte[] buffer = new byte[4096];
 65912836              long totalBytes = 0;
 837              int bytesRead;
 66297838               while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) {
 385839                crc.Update(buffer, 0, bytesRead);
 840
 385841                 if (resultHandler != null) {
 0842                  totalBytes += bytesRead;
 0843                  status.SetBytesTested(totalBytes);
 0844                  resultHandler(status, null);
 845                }
 846              }
 65912847            }
 848
 65912849             if (this[entryIndex].Crc != crc.Value) {
 1850              status.AddError();
 851
 1852               if (resultHandler != null) {
 0853                resultHandler(status, "CRC mismatch");
 854              }
 855
 1856              testing &= strategy != TestStrategy.FindFirstError;
 857            }
 858
 65912859             if ((this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0) {
 13860              var helper = new ZipHelperStream(baseStream_);
 13861              var data = new DescriptorData();
 13862              helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data);
 13863               if (this[entryIndex].Crc != data.Crc) {
 0864                status.AddError();
 865              }
 866
 13867               if (this[entryIndex].CompressedSize != data.CompressedSize) {
 0868                status.AddError();
 869              }
 870
 13871               if (this[entryIndex].Size != data.Size) {
 0872                status.AddError();
 873              }
 874            }
 875          }
 876
 65916877           if (resultHandler != null) {
 0878            status.SetOperation(TestOperation.EntryComplete);
 0879            resultHandler(status, null);
 880          }
 881
 65916882          entryIndex += 1;
 883        }
 884
 96885         if (resultHandler != null) {
 0886          status.SetOperation(TestOperation.MiscellaneousTests);
 0887          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.
 96892      } catch (Exception ex) {
 0893        status.AddError();
 894
 0895         if (resultHandler != null) {
 0896          resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message));
 897        }
 0898      }
 899
 96900       if (resultHandler != null) {
 0901        status.SetOperation(TestOperation.Complete);
 0902        status.SetEntry(null);
 0903        resultHandler(status, null);
 904      }
 905
 96906      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    {
 131862926      lock (baseStream_) {
 131862927        bool testHeader = (tests & HeaderTest.Header) != 0;
 131862928        bool testData = (tests & HeaderTest.Extract) != 0;
 929
 131862930        baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
 131862931         if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) {
 0932          throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset)
 933        }
 934
 131862935        var extractVersion = (short)(ReadLEUshort() & 0x00ff);
 131862936        var localFlags = (short)ReadLEUshort();
 131862937        var compressionMethod = (short)ReadLEUshort();
 131862938        var fileTime = (short)ReadLEUshort();
 131862939        var fileDate = (short)ReadLEUshort();
 131862940        uint crcValue = ReadLEUint();
 131862941        long compressedSize = ReadLEUint();
 131862942        long size = ReadLEUint();
 131862943        int storedNameLength = ReadLEUshort();
 131862944        int extraDataLength = ReadLEUshort();
 945
 131862946        byte[] nameData = new byte[storedNameLength];
 131862947        StreamUtils.ReadFully(baseStream_, nameData);
 948
 131862949        byte[] extraData = new byte[extraDataLength];
 131862950        StreamUtils.ReadFully(baseStream_, extraData);
 951
 131862952        var localExtraData = new ZipExtraData(extraData);
 953
 954        // Extra data / zip64 checks
 131862955         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
 50959          size = localExtraData.ReadLong();
 50960          compressedSize = localExtraData.ReadLong();
 961
 50962           if ((localFlags & (int)GeneralBitFlags.Descriptor) != 0) {
 963            // These may be valid if patched later
 9964             if ((size != -1) && (size != entry.Size)) {
 0965              throw new ZipException("Size invalid for descriptor");
 966            }
 967
 9968             if ((compressedSize != -1) && (compressedSize != entry.CompressedSize)) {
 0969              throw new ZipException("Compressed size invalid for descriptor");
 970            }
 971          }
 972        } else {
 973          // No zip64 extra data but entry requires it.
 131812974           if ((extractVersion >= ZipConstants.VersionZip64) &&
 131812975            (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue))) {
 0976            throw new ZipException("Required Zip64 extended information missing");
 977          }
 978        }
 979
 131862980         if (testData) {
 131862981           if (entry.IsFile) {
 131858982             if (!entry.IsCompressionMethodSupported()) {
 0983              throw new ZipException("Compression method not supported");
 984            }
 985
 131858986             if ((extractVersion > ZipConstants.VersionMadeBy)
 131858987              || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) {
 0988              throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extract
 989            }
 990
 131858991             if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.Enhance
 0992              throw new ZipException("The library does not support the zip version required to extract this entry");
 993            }
 994          }
 995        }
 996
 131862997         if (testHeader) {
 65916998           if ((extractVersion <= 63) &&   // Ignore later versions as we dont know about them..
 65916999            (extractVersion != 10) &&
 659161000            (extractVersion != 11) &&
 659161001            (extractVersion != 20) &&
 659161002            (extractVersion != 21) &&
 659161003            (extractVersion != 25) &&
 659161004            (extractVersion != 27) &&
 659161005            (extractVersion != 45) &&
 659161006            (extractVersion != 46) &&
 659161007            (extractVersion != 50) &&
 659161008            (extractVersion != 51) &&
 659161009            (extractVersion != 52) &&
 659161010            (extractVersion != 61) &&
 659161011            (extractVersion != 62) &&
 659161012            (extractVersion != 63)
 659161013            ) {
 01014            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.
 659161018           if ((localFlags & (int)(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.R
 01019            throw new ZipException("Reserved bit flags cannot be set.");
 1020          }
 1021
 1022          // Encryption requires extract version >= 20
 659161023           if (((localFlags & (int)GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) {
 01024            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.
 659161028           if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) {
 01029             if ((localFlags & (int)GeneralBitFlags.Encrypted) == 0) {
 01030              throw new ZipException("Strong encryption flag set but encryption flag is not set");
 1031            }
 1032
 01033             if (extractVersion < 50) {
 01034              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
 659161039           if (((localFlags & (int)GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) {
 01040            throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
 1041          }
 1042
 1043          // Central header flags match local entry flags.
 659161044           if (localFlags != entry.Flags) {
 01045            throw new ZipException("Central header/local header flags mismatch");
 1046          }
 1047
 1048          // Central header compression method matches local entry
 659161049           if (entry.CompressionMethod != (CompressionMethod)compressionMethod) {
 01050            throw new ZipException("Central header/local header compression method mismatch");
 1051          }
 1052
 659161053           if (entry.Version != extractVersion) {
 01054            throw new ZipException("Extract version mismatch");
 1055          }
 1056
 1057          // Strong encryption and extract version match
 659161058           if ((localFlags & (int)GeneralBitFlags.StrongEncryption) != 0) {
 01059             if (extractVersion < 62) {
 01060              throw new ZipException("Strong encryption flag set but version not high enough");
 1061            }
 1062          }
 1063
 659161064           if ((localFlags & (int)GeneralBitFlags.HeaderMasked) != 0) {
 01065             if ((fileTime != 0) || (fileDate != 0)) {
 01066              throw new ZipException("Header masked set but date/time values non-zero");
 1067            }
 1068          }
 1069
 659161070           if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) {
 659001071             if (crcValue != (uint)entry.Crc) {
 01072              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
 659161078           if ((size == 0) && (compressedSize == 0)) {
 655411079             if (crcValue != 0) {
 01080              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
 659161086           if (entry.Name.Length > storedNameLength) {
 01087            throw new ZipException("File name length mismatch");
 1088          }
 1089
 1090          // Name data has already been read convert it and compare.
 659161091          string localName = ZipConstants.ConvertToStringExt(localFlags, nameData);
 1092
 1093          // Central directory and local entry name match
 659161094           if (localName != entry.Name) {
 01095            throw new ZipException("Central header and local header file name mismatch");
 1096          }
 1097
 1098          // Directories have zero actual size but can have compressed size
 659161099           if (entry.IsDirectory) {
 41100             if (size > 0) {
 01101              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.
 41106             if (entry.IsCrypted) {
 21107               if (compressedSize > ZipConstants.CryptoHeaderSize + 2) {
 01108                throw new ZipException("Directory compressed size invalid");
 1109              }
 21110             } 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
 01114              throw new ZipException("Directory compressed size invalid");
 1115            }
 1116          }
 1117
 659161118           if (!ZipNameTransform.IsValidName(localName, true)) {
 01119            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.
 1318621127         if (((localFlags & (int)GeneralBitFlags.Descriptor) == 0) ||
 1318621128          ((size > 0 || compressedSize > 0) && entry.Size > 0)) {
 1129
 1318471130           if ((size != 0)
 1318471131            && (size != entry.Size)) {
 01132            throw new ZipException(
 01133              string.Format("Size mismatch between central header({0}) and local header({1})",
 01134                entry.Size, size));
 1135          }
 1136
 1318471137           if ((compressedSize != 0)
 1318471138            && (compressedSize != entry.CompressedSize && compressedSize != 0xFFFFFFFF && compressedSize != -1)) {
 01139            throw new ZipException(
 01140              string.Format("Compressed size mismatch between central header({0}) and local header({1})",
 01141              entry.CompressedSize, compressedSize));
 1142          }
 1143        }
 1144
 1318621145        int extraLength = storedNameLength + extraDataLength;
 1318621146        return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength;
 1147      }
 1318621148    }
 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 {
 657401172        return updateEntryFactory_.NameTransform;
 1173      }
 1174
 1175      set {
 01176        updateEntryFactory_.NameTransform = value;
 01177      }
 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 {
 1721186        return updateEntryFactory_;
 1187      }
 1188
 1189      set {
 01190         if (value == null) {
 01191          updateEntryFactory_ = new ZipEntryFactory();
 01192        } else {
 01193          updateEntryFactory_ = value;
 1194        }
 01195      }
 1196    }
 1197
 1198    /// <summary>
 1199    /// Get /set the buffer size to be used when updating this zip file.
 1200    /// </summary>
 1201    public int BufferSize {
 01202      get { return bufferSize_; }
 1203      set {
 01204         if (value < 1024) {
 01205          throw new ArgumentOutOfRangeException(nameof(value), "cannot be below 1024");
 1206        }
 1207
 01208         if (bufferSize_ != value) {
 01209          bufferSize_ = value;
 01210          copyBuffer_ = null;
 1211        }
 01212      }
 1213    }
 1214
 1215    /// <summary>
 1216    /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>.
 1217    /// </summary>
 1218    public bool IsUpdating {
 01219      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 {
 31226      get { return useZip64_; }
 121227      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    {
 481255       if (archiveStorage == null) {
 01256        throw new ArgumentNullException(nameof(archiveStorage));
 1257      }
 1258
 481259       if (dataSource == null) {
 01260        throw new ArgumentNullException(nameof(dataSource));
 1261      }
 1262
 481263       if (isDisposed_) {
 01264        throw new ObjectDisposedException("ZipFile");
 1265      }
 1266
 481267       if (IsEmbeddedArchive) {
 01268        throw new ZipException("Cannot update embedded/SFX archives");
 1269      }
 1270
 481271      archiveStorage_ = archiveStorage;
 481272      updateDataSource_ = dataSource;
 1273
 1274      // NOTE: the baseStream_ may not currently support writing or seeking.
 1275
 481276      updateIndex_ = new Hashtable();
 1277
 481278      updates_ = new ArrayList(entries_.Length);
 3181279      foreach (ZipEntry entry in entries_) {
 1111280        int index = updates_.Add(new ZipUpdate(entry));
 1111281        updateIndex_.Add(entry.Name, index);
 1282      }
 1283
 1284      // We must sort by offset before using offset's calculated sizes
 481285      updates_.Sort(new UpdateComparer());
 1286
 481287      int idx = 0;
 2871288      foreach (ZipUpdate update in updates_) {
 1289        //If last entry, there is no next entry offset to use
 1111290         if (idx == updates_.Count - 1)
 311291          break;
 1292
 801293        update.OffsetBasedSize = ((ZipUpdate)updates_[idx + 1]).Entry.Offset - update.Entry.Offset;
 801294        idx++;
 1295      }
 481296      updateCount_ = updates_.Count;
 1297
 481298      contentsEdited_ = false;
 481299      commentEdited_ = false;
 481300      newComment_ = null;
 481301    }
 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    {
 321309      BeginUpdate(archiveStorage, new DynamicDiskDataSource());
 321310    }
 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    {
 161320       if (Name == null) {
 61321        BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource());
 61322      } else {
 101323        BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource());
 1324      }
 101325    }
 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    {
 481335       if (isDisposed_) {
 01336        throw new ObjectDisposedException("ZipFile");
 1337      }
 1338
 481339      CheckUpdating();
 1340
 1341      try {
 481342        updateIndex_.Clear();
 481343        updateIndex_ = null;
 1344
 481345         if (contentsEdited_) {
 441346          RunUpdates();
 481347         } else if (commentEdited_) {
 31348          UpdateCommentOnly();
 31349        } else {
 1350          // Create an empty archive if none existed originally.
 11351           if (entries_.Length == 0) {
 11352            byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_);
 11353            using (ZipHelperStream zhs = new ZipHelperStream(baseStream_)) {
 11354              zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment);
 11355            }
 1356          }
 1357        }
 1358
 01359      } finally {
 481360        PostUpdateCleanup();
 481361      }
 481362    }
 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    {
 01371      PostUpdateCleanup();
 01372    }
 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    {
 31381       if (isDisposed_) {
 01382        throw new ObjectDisposedException("ZipFile");
 1383      }
 1384
 31385      CheckUpdating();
 1386
 31387      newComment_ = new ZipString(comment);
 1388
 31389       if (newComment_.RawLength > 0xffff) {
 01390        newComment_ = null;
 01391        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.
 31396      commentEdited_ = true;
 31397    }
 1398
 1399    #endregion
 1400
 1401    #region Adding Entries
 1402
 1403    void AddUpdate(ZipUpdate update)
 1404    {
 657051405      contentsEdited_ = true;
 1406
 657051407      int index = FindExistingUpdate(update.Entry.Name);
 1408
 657051409       if (index >= 0) {
 01410         if (updates_[index] == null) {
 01411          updateCount_ += 1;
 1412        }
 1413
 1414        // Direct replacement is faster than delete and add.
 01415        updates_[index] = update;
 01416      } else {
 657051417        index = updates_.Add(update);
 657051418        updateCount_ += 1;
 657051419        updateIndex_.Add(update.Entry.Name, index);
 1420      }
 657051421    }
 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    {
 01434       if (fileName == null) {
 01435        throw new ArgumentNullException(nameof(fileName));
 1436      }
 1437
 01438       if (isDisposed_) {
 01439        throw new ObjectDisposedException("ZipFile");
 1440      }
 1441
 01442       if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) {
 01443        throw new ArgumentOutOfRangeException(nameof(compressionMethod));
 1444      }
 1445
 01446      CheckUpdating();
 01447      contentsEdited_ = true;
 1448
 01449      ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
 01450      entry.IsUnicodeText = useUnicodeText;
 01451      entry.CompressionMethod = compressionMethod;
 1452
 01453      AddUpdate(new ZipUpdate(fileName, entry));
 01454    }
 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    {
 01465       if (fileName == null) {
 01466        throw new ArgumentNullException(nameof(fileName));
 1467      }
 1468
 01469       if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) {
 01470        throw new ArgumentOutOfRangeException(nameof(compressionMethod));
 1471      }
 1472
 01473      CheckUpdating();
 01474      contentsEdited_ = true;
 1475
 01476      ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
 01477      entry.CompressionMethod = compressionMethod;
 01478      AddUpdate(new ZipUpdate(fileName, entry));
 01479    }
 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    {
 31488       if (fileName == null) {
 01489        throw new ArgumentNullException(nameof(fileName));
 1490      }
 1491
 31492      CheckUpdating();
 31493      AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName)));
 31494    }
 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    {
 01504       if (fileName == null) {
 01505        throw new ArgumentNullException(nameof(fileName));
 1506      }
 1507
 01508       if (entryName == null) {
 01509        throw new ArgumentNullException(nameof(entryName));
 1510      }
 1511
 01512      CheckUpdating();
 01513      AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName, entryName, true)));
 01514    }
 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    {
 1461524       if (dataSource == null) {
 01525        throw new ArgumentNullException(nameof(dataSource));
 1526      }
 1527
 1461528       if (entryName == null) {
 01529        throw new ArgumentNullException(nameof(entryName));
 1530      }
 1531
 1461532      CheckUpdating();
 1461533      AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName, false)));
 1461534    }
 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    {
 151544       if (dataSource == null) {
 01545        throw new ArgumentNullException(nameof(dataSource));
 1546      }
 1547
 151548       if (entryName == null) {
 01549        throw new ArgumentNullException(nameof(entryName));
 1550      }
 1551
 151552      CheckUpdating();
 1553
 151554      ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
 151555      entry.CompressionMethod = compressionMethod;
 1556
 151557      AddUpdate(new ZipUpdate(dataSource, entry));
 151558    }
 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    {
 41569       if (dataSource == null) {
 01570        throw new ArgumentNullException(nameof(dataSource));
 1571      }
 1572
 41573       if (entryName == null) {
 01574        throw new ArgumentNullException(nameof(entryName));
 1575      }
 1576
 41577      CheckUpdating();
 1578
 41579      ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
 41580      entry.IsUnicodeText = useUnicodeText;
 41581      entry.CompressionMethod = compressionMethod;
 1582
 41583      AddUpdate(new ZipUpdate(dataSource, entry));
 41584    }
 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    {
 655371593       if (entry == null) {
 01594        throw new ArgumentNullException(nameof(entry));
 1595      }
 1596
 655371597      CheckUpdating();
 1598
 655371599       if ((entry.Size != 0) || (entry.CompressedSize != 0)) {
 01600        throw new ZipException("Entry cannot have any data");
 1601      }
 1602
 655371603      AddUpdate(new ZipUpdate(UpdateCommand.Add, entry));
 655371604    }
 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    {
 01612       if (directoryName == null) {
 01613        throw new ArgumentNullException(nameof(directoryName));
 1614      }
 1615
 01616      CheckUpdating();
 1617
 01618      ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName);
 01619      AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry));
 01620    }
 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    {
 31653       if (fileName == null) {
 01654        throw new ArgumentNullException(nameof(fileName));
 1655      }
 1656
 31657      CheckUpdating();
 1658
 31659      bool result = false;
 31660      int index = FindExistingUpdate(fileName);
 31661       if ((index >= 0) && (updates_[index] != null)) {
 31662        result = true;
 31663        contentsEdited_ = true;
 31664        updates_[index] = null;
 31665        updateCount_ -= 1;
 31666      } else {
 01667        throw new ZipException("Cannot find entry to delete");
 1668      }
 31669      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    {
 321678       if (entry == null) {
 01679        throw new ArgumentNullException(nameof(entry));
 1680      }
 1681
 321682      CheckUpdating();
 1683
 321684      int index = FindExistingUpdate(entry);
 321685       if (index >= 0) {
 321686        contentsEdited_ = true;
 321687        updates_[index] = null;
 321688        updateCount_ -= 1;
 321689      } else {
 01690        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    {
 22370481701      baseStream_.WriteByte((byte)(value & 0xff));
 22370481702      baseStream_.WriteByte((byte)((value >> 8) & 0xff));
 22370481703    }
 1704
 1705    /// <summary>
 1706    /// Write an unsigned short in little endian byte order.
 1707    /// </summary>
 1708    void WriteLEUshort(ushort value)
 1709    {
 2629441710      baseStream_.WriteByte((byte)(value & 0xff));
 2629441711      baseStream_.WriteByte((byte)(value >> 8));
 2629441712    }
 1713
 1714    /// <summary>
 1715    /// Write an int in little endian byte order.
 1716    /// </summary>
 1717    void WriteLEInt(int value)
 1718    {
 6581801719      WriteLEShort(value & 0xffff);
 6581801720      WriteLEShort(value >> 16);
 6581801721    }
 1722
 1723    /// <summary>
 1724    /// Write an unsigned int in little endian byte order.
 1725    /// </summary>
 1726    void WriteLEUint(uint value)
 1727    {
 1314721728      WriteLEUshort((ushort)(value & 0xffff));
 1314721729      WriteLEUshort((ushort)(value >> 16));
 1314721730    }
 1731
 1732    /// <summary>
 1733    /// Write a long in little endian byte order.
 1734    /// </summary>
 1735    void WriteLeLong(long value)
 1736    {
 41737      WriteLEInt((int)(value & 0xffffffff));
 41738      WriteLEInt((int)(value >> 32));
 41739    }
 1740
 1741    void WriteLEUlong(ulong value)
 1742    {
 01743      WriteLEUint((uint)(value & 0xffffffff));
 01744      WriteLEUint((uint)(value >> 32));
 01745    }
 1746
 1747    void WriteLocalEntryHeader(ZipUpdate update)
 1748    {
 657481749      ZipEntry entry = update.OutEntry;
 1750
 1751      // TODO: Local offset will require adjusting for multi-disk zip files.
 657481752      entry.Offset = baseStream_.Position;
 1753
 1754      // TODO: Need to clear any entry flags that dont make sense or throw an exception here.
 657481755       if (update.Command != UpdateCommand.Copy) {
 657051756         if (entry.CompressionMethod == CompressionMethod.Deflated) {
 656901757           if (entry.Size == 0) {
 1758            // No need to compress - no data.
 655371759            entry.CompressedSize = entry.Size;
 655371760            entry.Crc = 0;
 655371761            entry.CompressionMethod = CompressionMethod.Stored;
 1762          }
 655521763         } else if (entry.CompressionMethod == CompressionMethod.Stored) {
 151764          entry.Flags &= ~(int)GeneralBitFlags.Descriptor;
 1765        }
 1766
 657051767         if (HaveKeys) {
 51768          entry.IsCrypted = true;
 51769           if (entry.Crc < 0) {
 51770            entry.Flags |= (int)GeneralBitFlags.Descriptor;
 1771          }
 51772        } else {
 657001773          entry.IsCrypted = false;
 1774        }
 1775
 657051776         switch (useZip64_) {
 1777          case UseZip64.Dynamic:
 657011778             if (entry.Size < 0) {
 01779              entry.ForceZip64();
 1780            }
 01781            break;
 1782
 1783          case UseZip64.On:
 21784            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
 657481794      WriteLEInt(ZipConstants.LocalHeaderSignature);
 1795
 657481796      WriteLEShort(entry.Version);
 657481797      WriteLEShort(entry.Flags);
 1798
 657481799      WriteLEShort((byte)entry.CompressionMethod);
 657481800      WriteLEInt((int)entry.DosTime);
 1801
 657481802       if (!entry.HasCrc) {
 1803        // Note patch address for updating CRC later.
 1681804        update.CrcPatchOffset = baseStream_.Position;
 1681805        WriteLEInt((int)0);
 1681806      } else {
 655801807        WriteLEInt(unchecked((int)entry.Crc));
 1808      }
 1809
 657481810       if (entry.LocalHeaderRequiresZip64) {
 21811        WriteLEInt(-1);
 21812        WriteLEInt(-1);
 21813      } else {
 657461814         if ((entry.CompressedSize < 0) || (entry.Size < 0)) {
 1661815          update.SizePatchOffset = baseStream_.Position;
 1816        }
 1817
 657461818        WriteLEInt((int)entry.CompressedSize);
 657461819        WriteLEInt((int)entry.Size);
 1820      }
 1821
 657481822      byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
 1823
 657481824       if (name.Length > 0xFFFF) {
 01825        throw new ZipException("Entry name too long.");
 1826      }
 1827
 657481828      var ed = new ZipExtraData(entry.ExtraData);
 1829
 657481830       if (entry.LocalHeaderRequiresZip64) {
 21831        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!
 21835        ed.AddLeLong(entry.Size);
 21836        ed.AddLeLong(entry.CompressedSize);
 21837        ed.AddNewEntry(1);
 21838      } else {
 657461839        ed.Delete(1);
 1840      }
 1841
 657481842      entry.ExtraData = ed.GetEntryData();
 1843
 657481844      WriteLEShort(name.Length);
 657481845      WriteLEShort(entry.ExtraData.Length);
 1846
 657481847       if (name.Length > 0) {
 657481848        baseStream_.Write(name, 0, name.Length);
 1849      }
 1850
 657481851       if (entry.LocalHeaderRequiresZip64) {
 21852         if (!ed.Find(1)) {
 01853          throw new ZipException("Internal error cannot find extra data");
 1854        }
 1855
 21856        update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex;
 1857      }
 1858
 657481859       if (entry.ExtraData.Length > 0) {
 21860        baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length);
 1861      }
 657481862    }
 1863
 1864    int WriteCentralDirectoryHeader(ZipEntry entry)
 1865    {
 657721866       if (entry.CompressedSize < 0) {
 01867        throw new ZipException("Attempt to write central directory entry with unknown csize");
 1868      }
 1869
 657721870       if (entry.Size < 0) {
 01871        throw new ZipException("Attempt to write central directory entry with unknown size");
 1872      }
 1873
 657721874       if (entry.Crc < 0) {
 01875        throw new ZipException("Attempt to write central directory entry with unknown crc");
 1876      }
 1877
 1878      // Write the central file header
 657721879      WriteLEInt(ZipConstants.CentralHeaderSignature);
 1880
 1881      // Version made by
 657721882      WriteLEShort(ZipConstants.VersionMadeBy);
 1883
 1884      // Version required to extract
 657721885      WriteLEShort(entry.Version);
 1886
 657721887      WriteLEShort(entry.Flags);
 1888
 1889      unchecked {
 657721890        WriteLEShort((byte)entry.CompressionMethod);
 657721891        WriteLEInt((int)entry.DosTime);
 657721892        WriteLEInt((int)entry.Crc);
 1893      }
 1894
 657721895       if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) {
 21896        WriteLEInt(-1);
 21897      } else {
 657701898        WriteLEInt((int)(entry.CompressedSize & 0xffffffff));
 1899      }
 1900
 657721901       if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) {
 21902        WriteLEInt(-1);
 21903      } else {
 657701904        WriteLEInt((int)entry.Size);
 1905      }
 1906
 657721907      byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
 1908
 657721909       if (name.Length > 0xFFFF) {
 01910        throw new ZipException("Entry name is too long.");
 1911      }
 1912
 657721913      WriteLEShort(name.Length);
 1914
 1915      // Central header extra data is different to local header version so regenerate.
 657721916      var ed = new ZipExtraData(entry.ExtraData);
 1917
 657721918       if (entry.CentralHeaderRequiresZip64) {
 21919        ed.StartNewEntry();
 1920
 21921         if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) {
 21922          ed.AddLeLong(entry.Size);
 1923        }
 1924
 21925         if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) {
 21926          ed.AddLeLong(entry.CompressedSize);
 1927        }
 1928
 21929         if (entry.Offset >= 0xffffffff) {
 01930          ed.AddLeLong(entry.Offset);
 1931        }
 1932
 1933        // Number of disk on which this file starts isnt supported and is never written here.
 21934        ed.AddNewEntry(1);
 21935      } else {
 1936        // Should have already be done when local header was added.
 657701937        ed.Delete(1);
 1938      }
 1939
 657721940      byte[] centralExtraData = ed.GetEntryData();
 1941
 657721942      WriteLEShort(centralExtraData.Length);
 657721943       WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0);
 1944
 657721945      WriteLEShort(0);    // disk number
 657721946      WriteLEShort(0);    // internal file attributes
 1947
 1948      // External file attributes...
 657721949       if (entry.ExternalFileAttributes != -1) {
 721950        WriteLEInt(entry.ExternalFileAttributes);
 721951      } else {
 657001952         if (entry.IsDirectory) {
 01953          WriteLEUint(16);
 01954        } else {
 657001955          WriteLEUint(0);
 1956        }
 1957      }
 1958
 657721959       if (entry.Offset >= 0xffffffff) {
 01960        WriteLEUint(0xffffffff);
 01961      } else {
 657721962        WriteLEUint((uint)(int)entry.Offset);
 1963      }
 1964
 657721965       if (name.Length > 0) {
 657721966        baseStream_.Write(name, 0, name.Length);
 1967      }
 1968
 657721969       if (centralExtraData.Length > 0) {
 21970        baseStream_.Write(centralExtraData, 0, centralExtraData.Length);
 1971      }
 1972
 657721973       byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0];
 1974
 657721975       if (rawComment.Length > 0) {
 01976        baseStream_.Write(rawComment, 0, rawComment.Length);
 1977      }
 1978
 657721979      return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length;
 1980    }
 1981    #endregion
 1982
 1983    void PostUpdateCleanup()
 1984    {
 1361985      updateDataSource_ = null;
 1361986      updates_ = null;
 1361987      updateIndex_ = null;
 1988
 1361989       if (archiveStorage_ != null) {
 481990        archiveStorage_.Dispose();
 481991        archiveStorage_ = null;
 1992      }
 1361993    }
 1994
 1995    string GetTransformedFileName(string name)
 1996    {
 657401997      INameTransform transform = NameTransform;
 657401998       return (transform != null) ?
 657401999        transform.TransformFile(name) :
 657402000        name;
 2001    }
 2002
 2003    string GetTransformedDirectoryName(string name)
 2004    {
 02005      INameTransform transform = NameTransform;
 02006       return (transform != null) ?
 02007        transform.TransformDirectory(name) :
 02008        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    {
 2112017       if (copyBuffer_ == null) {
 412018        copyBuffer_ = new byte[bufferSize_];
 2019      }
 2112020      return copyBuffer_;
 2021    }
 2022
 2023    void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
 2024    {
 42025      int bytesToCopy = GetDescriptorSize(update);
 2026
 42027       if (bytesToCopy > 0) {
 02028        byte[] buffer = GetBuffer();
 2029
 02030         while (bytesToCopy > 0) {
 02031          int readSize = Math.Min(buffer.Length, bytesToCopy);
 2032
 02033          int bytesRead = source.Read(buffer, 0, readSize);
 02034           if (bytesRead > 0) {
 02035            dest.Write(buffer, 0, bytesRead);
 02036            bytesToCopy -= bytesRead;
 02037          } else {
 02038            throw new ZipException("Unxpected end of stream");
 2039          }
 2040        }
 2041      }
 42042    }
 2043
 2044    void CopyBytes(ZipUpdate update, Stream destination, Stream source,
 2045      long bytesToCopy, bool updateCrc)
 2046    {
 1722047       if (destination == source) {
 02048        throw new InvalidOperationException("Destination and source are the same");
 2049      }
 2050
 2051      // NOTE: Compressed size is updated elsewhere.
 1722052      var crc = new Crc32();
 1722053      byte[] buffer = GetBuffer();
 2054
 1722055      long targetBytes = bytesToCopy;
 1722056      long totalBytesRead = 0;
 2057
 2058      int bytesRead;
 2059      do {
 1722060        int readSize = buffer.Length;
 2061
 1722062         if (bytesToCopy < readSize) {
 1722063          readSize = (int)bytesToCopy;
 2064        }
 2065
 1722066        bytesRead = source.Read(buffer, 0, readSize);
 1722067         if (bytesRead > 0) {
 1722068           if (updateCrc) {
 1682069            crc.Update(buffer, 0, bytesRead);
 2070          }
 1722071          destination.Write(buffer, 0, bytesRead);
 1722072          bytesToCopy -= bytesRead;
 1722073          totalBytesRead += bytesRead;
 2074        }
 2075      }
 1722076       while ((bytesRead > 0) && (bytesToCopy > 0));
 2077
 1722078       if (totalBytesRead != targetBytes) {
 02079        throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead))
 2080      }
 2081
 1722082       if (updateCrc) {
 1682083        update.OutEntry.Crc = crc.Value;
 2084      }
 1722085    }
 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    {
 452094      int result = 0;
 452095       if ((update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) {
 02096        result = ZipConstants.DataDescriptorSize - 4;
 02097         if (update.Entry.LocalHeaderRequiresZip64) {
 02098          result = ZipConstants.Zip64DataDescriptorSize - 4;
 2099        }
 2100      }
 452101      return result;
 2102    }
 2103
 2104    void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
 2105    {
 392106      int bytesToCopy = GetDescriptorSize(update);
 2107
 392108       while (bytesToCopy > 0) {
 02109        var readSize = (int)bytesToCopy;
 02110        byte[] buffer = GetBuffer();
 2111
 02112        stream.Position = sourcePosition;
 02113        int bytesRead = stream.Read(buffer, 0, readSize);
 02114         if (bytesRead > 0) {
 02115          stream.Position = destinationPosition;
 02116          stream.Write(buffer, 0, bytesRead);
 02117          bytesToCopy -= bytesRead;
 02118          destinationPosition += bytesRead;
 02119          sourcePosition += bytesRead;
 02120        } else {
 02121          throw new ZipException("Unxpected end of stream");
 2122        }
 2123      }
 392124    }
 2125
 2126    void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sou
 2127    {
 392128      long bytesToCopy = update.Entry.CompressedSize;
 2129
 2130      // NOTE: Compressed size is updated elsewhere.
 392131      var crc = new Crc32();
 392132      byte[] buffer = GetBuffer();
 2133
 392134      long targetBytes = bytesToCopy;
 392135      long totalBytesRead = 0;
 2136
 2137      int bytesRead;
 2138      do {
 392139        int readSize = buffer.Length;
 2140
 392141         if (bytesToCopy < readSize) {
 392142          readSize = (int)bytesToCopy;
 2143        }
 2144
 392145        stream.Position = sourcePosition;
 392146        bytesRead = stream.Read(buffer, 0, readSize);
 392147         if (bytesRead > 0) {
 392148           if (updateCrc) {
 02149            crc.Update(buffer, 0, bytesRead);
 2150          }
 392151          stream.Position = destinationPosition;
 392152          stream.Write(buffer, 0, bytesRead);
 2153
 392154          destinationPosition += bytesRead;
 392155          sourcePosition += bytesRead;
 392156          bytesToCopy -= bytesRead;
 392157          totalBytesRead += bytesRead;
 2158        }
 2159      }
 392160       while ((bytesRead > 0) && (bytesToCopy > 0));
 2161
 392162       if (totalBytesRead != targetBytes) {
 02163        throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead))
 2164      }
 2165
 392166       if (updateCrc) {
 02167        update.OutEntry.Crc = crc.Value;
 2168      }
 392169    }
 2170
 2171    int FindExistingUpdate(ZipEntry entry)
 2172    {
 322173      int result = -1;
 322174      string convertedName = GetTransformedFileName(entry.Name);
 2175
 322176       if (updateIndex_.ContainsKey(convertedName)) {
 322177        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       */
 322192      return result;
 2193    }
 2194
 2195    int FindExistingUpdate(string fileName)
 2196    {
 657082197      int result = -1;
 2198
 657082199      string convertedName = GetTransformedFileName(fileName);
 2200
 657082201       if (updateIndex_.ContainsKey(convertedName)) {
 32202        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
 657082217      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    {
 1682227      Stream result = baseStream_;
 2228
 1682229       if (entry.IsCrypted == true) {
 52230        result = CreateAndInitEncryptionStream(result, entry);
 2231      }
 2232
 1682233       switch (entry.CompressionMethod) {
 2234        case CompressionMethod.Stored:
 152235          result = new UncompressedStream(result);
 152236          break;
 2237
 2238        case CompressionMethod.Deflated:
 1532239          var dos = new DeflaterOutputStream(result, new Deflater(9, true));
 1532240          dos.IsStreamOwner = false;
 1532241          result = dos;
 1532242          break;
 2243
 2244        default:
 02245          throw new ZipException("Unknown compression method " + entry.CompressionMethod);
 2246      }
 1682247      return result;
 2248    }
 2249
 2250    void AddEntry(ZipFile workFile, ZipUpdate update)
 2251    {
 657052252      Stream source = null;
 2253
 657052254       if (update.Entry.IsFile) {
 657052255        source = update.GetSource();
 2256
 657052257         if (source == null) {
 655402258          source = updateDataSource_.GetSource(update.Entry, update.Filename);
 2259        }
 2260      }
 2261
 657052262       if (source != null) {
 1682263        using (source) {
 1682264          long sourceStreamLength = source.Length;
 1682265           if (update.OutEntry.Size < 0) {
 1652266            update.OutEntry.Size = sourceStreamLength;
 1652267          } else {
 2268            // Check for errant entries.
 32269             if (update.OutEntry.Size != sourceStreamLength) {
 02270              throw new ZipException("Entry size/stream size mismatch");
 2271            }
 2272          }
 2273
 1682274          workFile.WriteLocalEntryHeader(update);
 2275
 1682276          long dataStart = workFile.baseStream_.Position;
 2277
 1682278          using (Stream output = workFile.GetOutputStream(update.OutEntry)) {
 1682279            CopyBytes(update, output, source, sourceStreamLength, true);
 1682280          }
 2281
 1682282          long dataEnd = workFile.baseStream_.Position;
 1682283          update.OutEntry.CompressedSize = dataEnd - dataStart;
 2284
 1682285           if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor) {
 52286            var helper = new ZipHelperStream(workFile.baseStream_);
 52287            helper.WriteDataDescriptor(update.OutEntry);
 2288          }
 1682289        }
 2290      } else {
 655372291        workFile.WriteLocalEntryHeader(update);
 655372292        update.OutEntry.CompressedSize = 0;
 2293      }
 2294
 657052295    }
 2296
 2297    void ModifyEntry(ZipFile workFile, ZipUpdate update)
 2298    {
 02299      workFile.WriteLocalEntryHeader(update);
 02300      long dataStart = workFile.baseStream_.Position;
 2301
 2302      // TODO: This is slow if the changes don't effect the data!!
 02303       if (update.Entry.IsFile && (update.Filename != null)) {
 02304        using (Stream output = workFile.GetOutputStream(update.OutEntry)) {
 02305          using (Stream source = this.GetInputStream(update.Entry)) {
 02306            CopyBytes(update, output, source, source.Length, true);
 02307          }
 2308        }
 2309      }
 2310
 02311      long dataEnd = workFile.baseStream_.Position;
 02312      update.Entry.CompressedSize = dataEnd - dataStart;
 02313    }
 2314
 2315    void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition)
 2316    {
 632317      bool skipOver = false || update.Entry.Offset == destinationPosition;
 2318
 632319       if (!skipOver) {
 392320        baseStream_.Position = destinationPosition;
 392321        workFile.WriteLocalEntryHeader(update);
 392322        destinationPosition = baseStream_.Position;
 2323      }
 2324
 632325      long sourcePosition = 0;
 2326
 2327      const int NameLengthOffset = 26;
 2328
 2329      // TODO: Add base for SFX friendly handling
 632330      long entryDataOffset = update.Entry.Offset + NameLengthOffset;
 2331
 632332      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.
 632336      uint nameLength = ReadLEUshort();
 632337      uint extraLength = ReadLEUshort();
 2338
 632339      sourcePosition = baseStream_.Position + nameLength + extraLength;
 2340
 632341       if (skipOver) {
 242342         if (update.OffsetBasedSize != -1)
 222343          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 ..."
 22348          destinationPosition +=
 22349            (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
 22350            update.Entry.CompressedSize + GetDescriptorSize(update);
 22351      } else {
 392352         if (update.Entry.CompressedSize > 0) {
 392353          CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition);
 2354        }
 392355        CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition);
 2356      }
 392357    }
 2358
 2359    void CopyEntry(ZipFile workFile, ZipUpdate update)
 2360    {
 42361      workFile.WriteLocalEntryHeader(update);
 2362
 42363       if (update.Entry.CompressedSize > 0) {
 2364        const int NameLengthOffset = 26;
 2365
 42366        long entryDataOffset = update.Entry.Offset + NameLengthOffset;
 2367
 2368        // TODO: This wont work for SFX files!
 42369        baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
 2370
 42371        uint nameLength = ReadLEUshort();
 42372        uint extraLength = ReadLEUshort();
 2373
 42374        baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current);
 2375
 42376        CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false);
 2377      }
 42378      CopyDescriptorBytes(update, workFile.baseStream_, baseStream_);
 42379    }
 2380
 2381    void Reopen(Stream source)
 2382    {
 42383       if (source == null) {
 02384        throw new ZipException("Failed to reopen archive - no source");
 2385      }
 2386
 42387      isNewArchive_ = false;
 42388      baseStream_ = source;
 42389      ReadEntries();
 42390    }
 2391
 2392    void Reopen()
 2393    {
 02394       if (Name == null) {
 02395        throw new InvalidOperationException("Name is not known cannot Reopen");
 2396      }
 2397
 02398      Reopen(File.Open(Name, FileMode.Open, FileAccess.Read, FileShare.Read));
 02399    }
 2400
 2401    void UpdateCommentOnly()
 2402    {
 32403      long baseLength = baseStream_.Length;
 2404
 32405      ZipHelperStream updateFile = null;
 2406
 32407       if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) {
 12408        Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_);
 12409        updateFile = new ZipHelperStream(copyStream);
 12410        updateFile.IsStreamOwner = true;
 2411
 12412        baseStream_.Close();
 12413        baseStream_ = null;
 12414      } else {
 22415         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.
 22423          baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_);
 22424          updateFile = new ZipHelperStream(baseStream_);
 22425        } else {
 02426          baseStream_.Close();
 02427          baseStream_ = null;
 02428          updateFile = new ZipHelperStream(Name);
 2429        }
 2430      }
 2431
 32432      using (updateFile) {
 32433        long locatedCentralDirOffset =
 32434          updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
 32435                            baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
 32436         if (locatedCentralDirOffset < 0) {
 02437          throw new ZipException("Cannot find central directory");
 2438        }
 2439
 2440        const int CentralHeaderCommentSizeOffset = 16;
 32441        updateFile.Position += CentralHeaderCommentSizeOffset;
 2442
 32443        byte[] rawComment = newComment_.RawComment;
 2444
 32445        updateFile.WriteLEShort(rawComment.Length);
 32446        updateFile.Write(rawComment, 0, rawComment.Length);
 32447        updateFile.SetLength(updateFile.Position);
 32448      }
 2449
 32450       if (archiveStorage_.UpdateMode == FileUpdateMode.Safe) {
 12451        Reopen(archiveStorage_.ConvertTemporaryToFinal());
 12452      } else {
 22453        ReadEntries();
 2454      }
 22455    }
 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      {
 4712473        var zx = x as ZipUpdate;
 4712474        var zy = y as ZipUpdate;
 2475
 2476        int result;
 2477
 4712478         if (zx == null) {
 502479           if (zy == null) {
 102480            result = 0;
 102481          } else {
 402482            result = -1;
 2483          }
 4612484         } else if (zy == null) {
 112485          result = 1;
 112486        } else {
 4102487           int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1;
 4102488           int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1;
 2489
 4102490          result = xCmdValue - yCmdValue;
 4102491           if (result == 0) {
 3782492            long offsetDiff = zx.Entry.Offset - zy.Entry.Offset;
 3782493             if (offsetDiff < 0) {
 162494              result = -1;
 3782495             } else if (offsetDiff == 0) {
 2552496              result = 0;
 2552497            } else {
 1072498              result = 1;
 2499            }
 2500          }
 2501        }
 4712502        return result;
 2503      }
 2504    }
 2505
 2506    void RunUpdates()
 2507    {
 442508      long sizeEntries = 0;
 442509      long endOfStream = 0;
 442510      bool directUpdate = false;
 442511      long destinationPosition = 0; // NOT SFX friendly
 2512
 2513      ZipFile workFile;
 2514
 442515       if (IsNewArchive) {
 162516        workFile = this;
 162517        workFile.baseStream_.Position = 0;
 162518        directUpdate = true;
 442519       } else if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) {
 252520        workFile = this;
 252521        workFile.baseStream_.Position = 0;
 252522        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.
 252526        updates_.Sort(new UpdateComparer());
 252527      } else {
 32528        workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput());
 32529        workFile.UseZip64 = UseZip64;
 2530
 32531         if (key != null) {
 12532          workFile.key = (byte[])key.Clone();
 2533        }
 2534      }
 2535
 2536      try {
 1317022537        foreach (ZipUpdate update in updates_) {
 658072538           if (update != null) {
 657722539             switch (update.Command) {
 2540              case UpdateCommand.Copy:
 672541                 if (directUpdate) {
 632542                  CopyEntryDirect(workFile, update, ref destinationPosition);
 632543                } else {
 42544                  CopyEntry(workFile, update);
 2545                }
 42546                break;
 2547
 2548              case UpdateCommand.Modify:
 2549                // TODO: Direct modifying of an entry will take some legwork.
 02550                ModifyEntry(workFile, update);
 02551                break;
 2552
 2553              case UpdateCommand.Add:
 657052554                 if (!IsNewArchive && directUpdate) {
 1342555                  workFile.baseStream_.Position = destinationPosition;
 2556                }
 2557
 657052558                AddEntry(workFile, update);
 2559
 657052560                 if (directUpdate) {
 657042561                  destinationPosition = workFile.baseStream_.Position;
 2562                }
 2563                break;
 2564            }
 2565          }
 2566        }
 2567
 442568         if (!IsNewArchive && directUpdate) {
 252569          workFile.baseStream_.Position = destinationPosition;
 2570        }
 2571
 442572        long centralDirOffset = workFile.baseStream_.Position;
 2573
 1317022574        foreach (ZipUpdate update in updates_) {
 658072575           if (update != null) {
 657722576            sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry);
 2577          }
 2578        }
 2579
 442580        byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_);
 442581        using (ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_)) {
 442582          zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment);
 442583        }
 2584
 442585        endOfStream = workFile.baseStream_.Position;
 2586
 2587        // And now patch entries...
 1317022588        foreach (ZipUpdate update in updates_) {
 658072589           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...
 657722592             if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) {
 1682593              workFile.baseStream_.Position = update.CrcPatchOffset;
 1682594              workFile.WriteLEInt((int)update.OutEntry.Crc);
 2595            }
 2596
 657722597             if (update.SizePatchOffset > 0) {
 1682598              workFile.baseStream_.Position = update.SizePatchOffset;
 1682599               if (update.OutEntry.LocalHeaderRequiresZip64) {
 22600                workFile.WriteLeLong(update.OutEntry.Size);
 22601                workFile.WriteLeLong(update.OutEntry.CompressedSize);
 22602              } else {
 1662603                workFile.WriteLEInt((int)update.OutEntry.CompressedSize);
 1662604                workFile.WriteLEInt((int)update.OutEntry.Size);
 2605              }
 2606            }
 2607          }
 2608        }
 442609      } catch {
 02610        workFile.Close();
 02611         if (!directUpdate && (workFile.Name != null)) {
 02612          File.Delete(workFile.Name);
 2613        }
 02614        throw;
 2615      }
 2616
 442617       if (directUpdate) {
 412618        workFile.baseStream_.SetLength(endOfStream);
 412619        workFile.baseStream_.Flush();
 412620        isNewArchive_ = false;
 412621        ReadEntries();
 412622      } else {
 32623        baseStream_.Close();
 32624        Reopen(archiveStorage_.ConvertTemporaryToFinal());
 2625      }
 32626    }
 2627
 2628    void CheckUpdating()
 2629    {
 657912630       if (updates_ == null) {
 02631        throw new InvalidOperationException("BeginUpdate has not been called");
 2632      }
 657912633    }
 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
 32644      public ZipUpdate(string fileName, ZipEntry entry)
 2645      {
 32646        command_ = UpdateCommand.Add;
 32647        entry_ = entry;
 32648        filename_ = fileName;
 32649      }
 2650
 2651      [Obsolete]
 02652      public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod)
 2653      {
 02654        command_ = UpdateCommand.Add;
 02655        entry_ = new ZipEntry(entryName);
 02656        entry_.CompressionMethod = compressionMethod;
 02657        filename_ = fileName;
 02658      }
 2659
 2660      [Obsolete]
 2661      public ZipUpdate(string fileName, string entryName)
 02662        : this(fileName, entryName, CompressionMethod.Deflated)
 2663      {
 2664        // Do nothing.
 02665      }
 2666
 2667      [Obsolete]
 02668      public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
 2669      {
 02670        command_ = UpdateCommand.Add;
 02671        entry_ = new ZipEntry(entryName);
 02672        entry_.CompressionMethod = compressionMethod;
 02673        dataSource_ = dataSource;
 02674      }
 2675
 1652676      public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry)
 2677      {
 1652678        command_ = UpdateCommand.Add;
 1652679        entry_ = entry;
 1652680        dataSource_ = dataSource;
 1652681      }
 2682
 02683      public ZipUpdate(ZipEntry original, ZipEntry updated)
 2684      {
 02685        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
 656482693      public ZipUpdate(UpdateCommand command, ZipEntry entry)
 2694      {
 656482695        command_ = command;
 656482696        entry_ = (ZipEntry)entry.Clone();
 656482697      }
 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)
 1112705        : this(UpdateCommand.Copy, entry)
 2706      {
 2707        // Do nothing.
 1112708      }
 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 {
 2638342716        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 {
 1989102724           if (outEntry_ == null) {
 657722725            outEntry_ = (ZipEntry)entry_.Clone();
 2726          }
 2727
 1989102728          return outEntry_;
 2729        }
 2730      }
 2731
 2732      /// <summary>
 2733      /// Get the command for this update.
 2734      /// </summary>
 2735      public UpdateCommand Command {
 1328822736        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 {
 655402743        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 {
 659402750        get { return sizePatchOffset_; }
 3362751        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 {
 659402758        get { return crcPatchOffset_; }
 3362759        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 {
 462767        get { return _offsetBasedSize; }
 1602768        set { _offsetBasedSize = value; }
 2769      }
 2770
 2771      public Stream GetSource()
 2772      {
 657052773        Stream result = null;
 657052774         if (dataSource_ != null) {
 1652775          result = dataSource_.GetSource();
 2776        }
 2777
 657052778        return result;
 2779      }
 2780
 2781      #region Instance Fields
 2782      ZipEntry entry_;
 2783      ZipEntry outEntry_;
 2784      UpdateCommand command_;
 2785      IStaticDataSource dataSource_;
 2786      string filename_;
 658162787      long sizePatchOffset_ = -1;
 658162788      long crcPatchOffset_ = -1;
 658162789      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    {
 822801      Close();
 822802    }
 2803    #endregion
 2804
 2805    void DisposeInternal(bool disposing)
 2806    {
 982807       if (!isDisposed_) {
 882808        isDisposed_ = true;
 882809        entries_ = new ZipEntry[0];
 2810
 882811         if (IsStreamOwner && (baseStream_ != null)) {
 492812          lock (baseStream_) {
 492813            baseStream_.Close();
 492814          }
 2815        }
 2816
 882817        PostUpdateCleanup();
 2818      }
 982819    }
 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    {
 62828      DisposeInternal(disposing);
 62829    }
 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    {
 34961052844      int data1 = baseStream_.ReadByte();
 2845
 34961052846       if (data1 < 0) {
 02847        throw new EndOfStreamException("End of stream");
 2848      }
 2849
 34961052850      int data2 = baseStream_.ReadByte();
 2851
 34961052852       if (data2 < 0) {
 02853        throw new EndOfStreamException("End of stream");
 2854      }
 2855
 2856
 34961052857      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    {
 9893832872      return (uint)(ReadLEUshort() | (ReadLEUshort() << 16));
 2873    }
 2874
 2875    ulong ReadLEUlong()
 2876    {
 62877      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    {
 1152884      using (ZipHelperStream les = new ZipHelperStream(baseStream_)) {
 1152885        return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData);
 2886      }
 1152887    }
 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
 1142909       if (baseStream_.CanSeek == false) {
 02910        throw new ZipException("ZipFile stream must be seekable");
 2911      }
 2912
 1142913      long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
 1142914        baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
 2915
 1142916       if (locatedEndOfCentralDir < 0) {
 12917        throw new ZipException("Cannot find central directory");
 2918      }
 2919
 2920      // Read end of central directory record
 1132921      ushort thisDiskNumber = ReadLEUshort();
 1132922      ushort startCentralDirDisk = ReadLEUshort();
 1132923      ulong entriesForThisDisk = ReadLEUshort();
 1132924      ulong entriesForWholeCentralDir = ReadLEUshort();
 1132925      ulong centralDirSize = ReadLEUint();
 1132926      long offsetOfCentralDir = ReadLEUint();
 1132927      uint commentSize = ReadLEUshort();
 2928
 1132929       if (commentSize > 0) {
 102930        byte[] comment = new byte[commentSize];
 2931
 102932        StreamUtils.ReadFully(baseStream_, comment);
 102933        comment_ = ZipConstants.ConvertToString(comment);
 102934      } else {
 1032935        comment_ = string.Empty;
 2936      }
 2937
 1132938      bool isZip64 = false;
 2939
 2940      // Check if zip64 header information is required.
 1132941       if ((thisDiskNumber == 0xffff) ||
 1132942        (startCentralDirDisk == 0xffff) ||
 1132943        (entriesForThisDisk == 0xffff) ||
 1132944        (entriesForWholeCentralDir == 0xffff) ||
 1132945        (centralDirSize == 0xffffffff) ||
 1132946        (offsetOfCentralDir == 0xffffffff)) {
 12947        isZip64 = true;
 2948
 12949        long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, 
 12950         if (offset < 0) {
 02951          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
 12957        ReadLEUint(); // startDisk64 is not currently used
 12958        ulong offset64 = ReadLEUlong();
 12959        uint totalDisks = ReadLEUint();
 2960
 12961        baseStream_.Position = (long)offset64;
 12962        long sig64 = ReadLEUint();
 2963
 12964         if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) {
 02965          throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
 2966        }
 2967
 2968        // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
 12969        ulong recordSize = ReadLEUlong();
 12970        int versionMadeBy = ReadLEUshort();
 12971        int versionToExtract = ReadLEUshort();
 12972        uint thisDisk = ReadLEUint();
 12973        uint centralDirDisk = ReadLEUint();
 12974        entriesForThisDisk = ReadLEUlong();
 12975        entriesForWholeCentralDir = ReadLEUlong();
 12976        centralDirSize = ReadLEUlong();
 12977        offsetOfCentralDir = (long)ReadLEUlong();
 2978
 2979        // NOTE: zip64 extensible data sector (variable size) is ignored.
 2980      }
 2981
 1132982      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?
 1132990       if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize))) {
 22991        offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir);
 22992         if (offsetOfFirstEntry <= 0) {
 02993          throw new ZipException("Invalid embedded zip archive");
 2994        }
 2995      }
 2996
 1132997      baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
 2998
 1321382999       for (ulong i = 0; i < entriesForThisDisk; i++) {
 659563000         if (ReadLEUint() != ZipConstants.CentralHeaderSignature) {
 03001          throw new ZipException("Wrong Central Directory signature");
 3002        }
 3003
 659563004        int versionMadeBy = ReadLEUshort();
 659563005        int versionToExtract = ReadLEUshort();
 659563006        int bitFlags = ReadLEUshort();
 659563007        int method = ReadLEUshort();
 659563008        uint dostime = ReadLEUint();
 659563009        uint crc = ReadLEUint();
 659563010        var csize = (long)ReadLEUint();
 659563011        var size = (long)ReadLEUint();
 659563012        int nameLen = ReadLEUshort();
 659563013        int extraLen = ReadLEUshort();
 659563014        int commentLen = ReadLEUshort();
 3015
 659563016        int diskStartNo = ReadLEUshort();  // Not currently used
 659563017        int internalAttributes = ReadLEUshort();  // Not currently used
 3018
 659563019        uint externalAttributes = ReadLEUint();
 659563020        long offset = ReadLEUint();
 3021
 659563022        byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
 3023
 659563024        StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen);
 659563025        string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen);
 3026
 659563027        var entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method);
 659563028        entry.Crc = crc & 0xffffffffL;
 659563029        entry.Size = size & 0xffffffffL;
 659563030        entry.CompressedSize = csize & 0xffffffffL;
 659563031        entry.Flags = bitFlags;
 659563032        entry.DosTime = (uint)dostime;
 659563033        entry.ZipFileIndex = (long)i;
 659563034        entry.Offset = offset;
 659563035        entry.ExternalFileAttributes = (int)externalAttributes;
 3036
 659563037         if ((bitFlags & 8) == 0) {
 659373038          entry.CryptoCheckValue = (byte)(crc >> 24);
 659373039        } else {
 193040          entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
 3041        }
 3042
 659563043         if (extraLen > 0) {
 453044          byte[] extra = new byte[extraLen];
 453045          StreamUtils.ReadFully(baseStream_, extra);
 453046          entry.ExtraData = extra;
 3047        }
 3048
 659563049        entry.ProcessExtraData(false);
 3050
 659563051         if (commentLen > 0) {
 03052          StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen);
 03053          entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen);
 3054        }
 3055
 659563056        entries_[i] = entry;
 3057      }
 1133058    }
 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    {
 659463075      return TestLocalHeader(entry, HeaderTest.Extract);
 3076    }
 3077
 3078    Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
 3079    {
 103080      CryptoStream result = null;
 3081
 103082       if ((entry.Version < ZipConstants.VersionStrongEncryption)
 103083        || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
 103084        var classicManaged = new PkzipClassicManaged();
 3085
 103086        OnKeysRequired(entry.Name);
 103087         if (HaveKeys == false) {
 03088          throw new ZipException("No password available for encrypted stream");
 3089        }
 3090
 103091        result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read);
 103092        CheckClassicPassword(result, entry);
 103093      } else {
 03094         if (entry.Version == ZipConstants.VERSION_AES) {
 3095          //
 03096          OnKeysRequired(entry.Name);
 03097           if (HaveKeys == false) {
 03098            throw new ZipException("No password available for AES encrypted stream");
 3099          }
 03100          int saltLen = entry.AESSaltLen;
 03101          byte[] saltBytes = new byte[saltLen];
 03102          int saltIn = baseStream.Read(saltBytes, 0, saltLen);
 03103           if (saltIn != saltLen)
 03104            throw new ZipException("AES Salt expected " + saltLen + " got " + saltIn);
 3105          //
 03106          byte[] pwdVerifyRead = new byte[2];
 03107          baseStream.Read(pwdVerifyRead, 0, 2);
 03108          int blockSize = entry.AESKeySize / 8;   // bits to bytes
 3109
 03110          var decryptor = new ZipAESTransform(rawPassword_, saltBytes, blockSize, false);
 03111          byte[] pwdVerifyCalc = decryptor.PwdVerifier;
 03112           if (pwdVerifyCalc[0] != pwdVerifyRead[0] || pwdVerifyCalc[1] != pwdVerifyRead[1])
 03113            throw new ZipException("Invalid password for AES");
 03114          result = new ZipAESStream(baseStream, decryptor, CryptoStreamMode.Read);
 03115        } else {
 03116          throw new ZipException("Decryption method not supported");
 3117        }
 3118      }
 3119
 103120      return result;
 3121    }
 3122
 3123    Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
 3124    {
 53125      CryptoStream result = null;
 53126       if ((entry.Version < ZipConstants.VersionStrongEncryption)
 53127        || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
 53128        var classicManaged = new PkzipClassicManaged();
 3129
 53130        OnKeysRequired(entry.Name);
 53131         if (HaveKeys == false) {
 03132          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.
 53137        result = new CryptoStream(new UncompressedStream(baseStream),
 53138          classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
 3139
 53140         if ((entry.Crc < 0) || (entry.Flags & 8) != 0) {
 53141          WriteEncryptionHeader(result, entry.DosTime << 16);
 53142        } else {
 03143          WriteEncryptionHeader(result, entry.Crc);
 3144        }
 3145      }
 53146      return result;
 3147    }
 3148
 3149    static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry)
 3150    {
 103151      byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
 103152      StreamUtils.ReadFully(classicCryptoStream, cryptbuffer);
 103153       if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) {
 03154        throw new ZipException("Invalid password");
 3155      }
 103156    }
 3157
 3158    static void WriteEncryptionHeader(Stream stream, long crcValue)
 3159    {
 53160      byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
 53161      var rnd = new Random();
 53162      rnd.NextBytes(cryptBuffer);
 53163      cryptBuffer[11] = (byte)(crcValue >> 24);
 53164      stream.Write(cryptBuffer, 0, cryptBuffer.Length);
 53165    }
 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.
 883185    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_;
 883194    int bufferSize_ = DefaultBufferSize;
 3195    byte[] copyBuffer_;
 3196    ZipString newComment_;
 3197    bool commentEdited_;
 883198    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>
 33213      public ZipString(string comment)
 3214      {
 33215        comment_ = comment;
 33216        isSourceString_ = true;
 33217      }
 3218
 3219      /// <summary>
 3220      /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form.
 3221      /// </summary>
 3222      /// <param name="rawString"></param>
 03223      public ZipString(byte[] rawString)
 3224      {
 03225        rawComment_ = rawString;
 03226      }
 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 {
 03234        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 {
 33242          MakeBytesAvailable();
 33243          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 {
 33252          MakeBytesAvailable();
 33253          return (byte[])rawComment_.Clone();
 3254        }
 3255      }
 3256
 3257      /// <summary>
 3258      /// Reset the comment to its initial state.
 3259      /// </summary>
 3260      public void Reset()
 3261      {
 03262         if (isSourceString_) {
 03263          rawComment_ = null;
 03264        } else {
 03265          comment_ = null;
 3266        }
 03267      }
 3268
 3269      void MakeTextAvailable()
 3270      {
 03271         if (comment_ == null) {
 03272          comment_ = ZipConstants.ConvertToString(rawComment_);
 3273        }
 03274      }
 3275
 3276      void MakeBytesAvailable()
 3277      {
 63278         if (rawComment_ == null) {
 33279          rawComment_ = ZipConstants.ConvertToArray(comment_);
 3280        }
 63281      }
 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      {
 03290        zipString.MakeTextAvailable();
 03291        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
 43307      public ZipEntryEnumerator(ZipEntry[] entries)
 3308      {
 43309        array = entries;
 43310      }
 3311
 3312      #endregion
 3313      #region IEnumerator Members
 3314      public object Current {
 3315        get {
 223316          return array[index];
 3317        }
 3318      }
 3319
 3320      public void Reset()
 3321      {
 03322        index = -1;
 03323      }
 3324
 3325      public bool MoveNext()
 3326      {
 263327        return (++index < array.Length);
 3328      }
 3329      #endregion
 3330      #region Instance Fields
 3331      ZipEntry[] array;
 43332      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
 203343      public UncompressedStream(Stream baseStream)
 3344      {
 203345        baseStream_ = baseStream;
 203346      }
 3347
 3348      #endregion
 3349
 3350      /// <summary>
 3351      /// Close this stream instance.
 3352      /// </summary>
 3353      public override void Close()
 3354      {
 3355        // Do nothing
 153356      }
 3357
 3358      /// <summary>
 3359      /// Gets a value indicating whether the current stream supports reading.
 3360      /// </summary>
 3361      public override bool CanRead {
 3362        get {
 03363          return false;
 3364        }
 3365      }
 3366
 3367      /// <summary>
 3368      /// Write any buffered data to underlying storage.
 3369      /// </summary>
 3370      public override void Flush()
 3371      {
 03372        baseStream_.Flush();
 03373      }
 3374
 3375      /// <summary>
 3376      /// Gets a value indicating whether the current stream supports writing.
 3377      /// </summary>
 3378      public override bool CanWrite {
 3379        get {
 53380          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 {
 03389          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 {
 03398          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 {
 03407          return baseStream_.Position;
 3408        }
 3409        set {
 03410          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      {
 03431        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      {
 03447        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      {
 03459      }
 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      {
 253475        baseStream_.Write(buffer, offset, count);
 253476      }
 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>
 659463498      public PartialInputStream(ZipFile zipFile, long start, long length)
 3499      {
 659463500        start_ = start;
 659463501        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....
 659463513        zipFile_ = zipFile;
 659463514        baseStream_ = zipFile_.baseStream_;
 659463515        readPos_ = start;
 659463516        end_ = start + length;
 659463517      }
 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      {
 1583526         if (readPos_ >= end_) {
 3527          // -1 is the correct value at end of stream.
 03528          return -1;
 3529        }
 3530
 1583531        lock (baseStream_) {
 1583532          baseStream_.Seek(readPos_++, SeekOrigin.Begin);
 1583533          return baseStream_.ReadByte();
 3534        }
 1583535      }
 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!
 659183546      }
 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      {
 663793565        lock (baseStream_) {
 663793566           if (count > end_ - readPos_) {
 663483567            count = (int)(end_ - readPos_);
 663483568             if (count == 0) {
 659433569              return 0;
 3570            }
 3571          }
 3572          // Protect against Stream implementations that throw away their buffer on every Seek
 3573          // (for example, Mono FileStream)
 4363574           if (baseStream_.Position != readPos_) {
 03575            baseStream_.Seek(readPos_, SeekOrigin.Begin);
 3576          }
 4363577          int readCount = baseStream_.Read(buffer, offset, count);
 4363578           if (readCount > 0) {
 4363579            readPos_ += readCount;
 3580          }
 4363581          return readCount;
 3582        }
 663793583      }
 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      {
 03599        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      {
 03611        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      {
 53627        long newPos = readPos_;
 3628
 53629         switch (origin) {
 3630          case SeekOrigin.Begin:
 53631            newPos = start_ + offset;
 53632            break;
 3633
 3634          case SeekOrigin.Current:
 03635            newPos = readPos_ + offset;
 03636            break;
 3637
 3638          case SeekOrigin.End:
 03639            newPos = end_ + offset;
 3640            break;
 3641        }
 3642
 53643         if (newPos < start_) {
 03644          throw new ArgumentException("Negative position is invalid");
 3645        }
 3646
 53647         if (newPos >= end_) {
 03648          throw new IOException("Cannot seek past end");
 3649        }
 53650        readPos_ = newPos;
 53651        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.
 03661      }
 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 {
 33672        get { return readPos_ - start_; }
 3673        set {
 03674          long newPos = start_ + value;
 3675
 03676           if (newPos < start_) {
 03677            throw new ArgumentException("Negative position is invalid");
 3678          }
 3679
 03680           if (newPos >= end_) {
 03681            throw new InvalidOperationException("Cannot seek past end");
 3682          }
 03683          readPos_ = newPos;
 03684        }
 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 {
 23695        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 {
 03704        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 {
 23713        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 {
 133722        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 {
 03731        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>
 3896    protected BaseArchiveStorage(FileUpdateMode updateMode)
 3897    {
 3898      updateMode_ = updateMode;
 3899    }
 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 {
 3944        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}