Summary

Class:ICSharpCode.SharpZipLib.Zip.ZipOutputStream
Assembly:ICSharpCode.SharpZipLib
File(s):C:\Users\Neil\Documents\Visual Studio 2015\Projects\icsharpcode\SZL_master\ICSharpCode.SharpZipLib\Zip\ZipOutputStream.cs
Covered lines:285
Uncovered lines:48
Coverable lines:333
Total lines:816
Line coverage:85.5%
Branch coverage:78.4%

Metrics

MethodCyclomatic ComplexitySequence CoverageBranch Coverage
.ctor(...)1100100
.ctor(...)100
SetComment(...)2100100
SetLevel(...)1100100
GetLevel()1100100
WriteLeShort(...)1100100
WriteLeInt(...)1100100
WriteLeLong(...)1100100
PutNextEntry(...)4088.7086.30
CloseEntry()1885.2577.14
WriteEncryptionHeader(...)1100100
AddExtraDataAES(...)100
WriteAESHeader(...)100
Write(...)97564.71
CopyAndEncrypt(...)410080
Finish()2690.4173.33

File(s)

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

#LineLine coverage
 1using System;
 2using System.IO;
 3using System.Collections;
 4using ICSharpCode.SharpZipLib.Checksum;
 5using ICSharpCode.SharpZipLib.Zip.Compression;
 6using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
 7
 8namespace ICSharpCode.SharpZipLib.Zip
 9{
 10  /// <summary>
 11  /// This is a DeflaterOutputStream that writes the files into a zip
 12  /// archive one after another.  It has a special method to start a new
 13  /// zip entry.  The zip entries contains information about the file name
 14  /// size, compressed size, CRC, etc.
 15  ///
 16  /// It includes support for Stored and Deflated entries.
 17  /// This class is not thread safe.
 18  /// <br/>
 19  /// <br/>Author of the original java version : Jochen Hoenicke
 20  /// </summary>
 21  /// <example> This sample shows how to create a zip file
 22  /// <code>
 23  /// using System;
 24  /// using System.IO;
 25  ///
 26  /// using ICSharpCode.SharpZipLib.Core;
 27  /// using ICSharpCode.SharpZipLib.Zip;
 28  ///
 29  /// class MainClass
 30  /// {
 31  ///   public static void Main(string[] args)
 32  ///   {
 33  ///     string[] filenames = Directory.GetFiles(args[0]);
 34  ///     byte[] buffer = new byte[4096];
 35  ///
 36  ///     using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) {
 37  ///
 38  ///       s.SetLevel(9); // 0 - store only to 9 - means best compression
 39  ///
 40  ///       foreach (string file in filenames) {
 41  ///         ZipEntry entry = new ZipEntry(file);
 42  ///         s.PutNextEntry(entry);
 43  ///
 44  ///         using (FileStream fs = File.OpenRead(file)) {
 45  ///            StreamUtils.Copy(fs, s, buffer);
 46  ///         }
 47  ///       }
 48  ///     }
 49  ///   }
 50  /// }
 51  /// </code>
 52  /// </example>
 53  public class ZipOutputStream : DeflaterOutputStream
 54  {
 55    #region Constructors
 56    /// <summary>
 57    /// Creates a new Zip output stream, writing a zip archive.
 58    /// </summary>
 59    /// <param name="baseOutputStream">
 60    /// The output stream to which the archive contents are written.
 61    /// </param>
 62    public ZipOutputStream(Stream baseOutputStream)
 8763      : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true))
 64    {
 8765    }
 66
 67    /// <summary>
 68    /// Creates a new Zip output stream, writing a zip archive.
 69    /// </summary>
 70    /// <param name="baseOutputStream">The output stream to which the archive contents are written.</param>
 71    /// <param name="bufferSize">Size of the buffer to use.</param>
 72    public ZipOutputStream(Stream baseOutputStream, int bufferSize)
 073      : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true), bufferSize)
 74    {
 075    }
 76    #endregion
 77
 78    /// <summary>
 79    /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added.
 80    /// </summary>
 81    /// <remarks>No further entries can be added once this has been done.</remarks>
 82    public bool IsFinished {
 83      get {
 184        return entries == null;
 85      }
 86    }
 87
 88    /// <summary>
 89    /// Set the zip file comment.
 90    /// </summary>
 91    /// <param name="comment">
 92    /// The comment text for the entire archive.
 93    /// </param>
 94    /// <exception name ="ArgumentOutOfRangeException">
 95    /// The converted comment is longer than 0xffff bytes.
 96    /// </exception>
 97    public void SetComment(string comment)
 98    {
 99      // TODO: Its not yet clear how to handle unicode comments here.
 8100      byte[] commentBytes = ZipConstants.ConvertToArray(comment);
 8101       if (commentBytes.Length > 0xffff) {
 1102        throw new ArgumentOutOfRangeException(nameof(comment));
 103      }
 7104      zipComment = commentBytes;
 7105    }
 106
 107    /// <summary>
 108    /// Sets the compression level.  The new level will be activated
 109    /// immediately.
 110    /// </summary>
 111    /// <param name="level">The new compression level (1 to 9).</param>
 112    /// <exception cref="ArgumentOutOfRangeException">
 113    /// Level specified is not supported.
 114    /// </exception>
 115    /// <see cref="ICSharpCode.SharpZipLib.Zip.Compression.Deflater"/>
 116    public void SetLevel(int level)
 117    {
 55118      deflater_.SetLevel(level);
 55119      defaultCompressionLevel = level;
 55120    }
 121
 122    /// <summary>
 123    /// Get the current deflater compression level
 124    /// </summary>
 125    /// <returns>The current compression level</returns>
 126    public int GetLevel()
 127    {
 3128      return deflater_.GetLevel();
 129    }
 130
 131    /// <summary>
 132    /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
 133    /// </summary>
 134    /// <remarks>Older archivers may not understand Zip64 extensions.
 135    /// If backwards compatability is an issue be careful when adding <see cref="ZipEntry.Size">entries</see> to an arch
 136    /// Setting this property to off is workable but less desirable as in those circumstances adding a file
 137    /// larger then 4GB will fail.</remarks>
 138    public UseZip64 UseZip64 {
 0139      get { return useZip64_; }
 14140      set { useZip64_ = value; }
 141    }
 142
 143    /// <summary>
 144    /// Write an unsigned short in little endian byte order.
 145    /// </summary>
 146    private void WriteLeShort(int value)
 147    {
 148      unchecked {
 6369149        baseOutputStream_.WriteByte((byte)(value & 0xff));
 6369150        baseOutputStream_.WriteByte((byte)((value >> 8) & 0xff));
 151      }
 6369152    }
 153
 154    /// <summary>
 155    /// Write an int in little endian byte order.
 156    /// </summary>
 157    private void WriteLeInt(int value)
 158    {
 159      unchecked {
 2286160        WriteLeShort(value);
 2286161        WriteLeShort(value >> 16);
 162      }
 2286163    }
 164
 165    /// <summary>
 166    /// Write an int in little endian byte order.
 167    /// </summary>
 168    private void WriteLeLong(long value)
 169    {
 170      unchecked {
 268171        WriteLeInt((int)value);
 268172        WriteLeInt((int)(value >> 32));
 173      }
 268174    }
 175
 176    /// <summary>
 177    /// Starts a new Zip entry. It automatically closes the previous
 178    /// entry if present.
 179    /// All entry elements bar name are optional, but must be correct if present.
 180    /// If the compression method is stored and the output is not patchable
 181    /// the compression for that entry is automatically changed to deflate level 0
 182    /// </summary>
 183    /// <param name="entry">
 184    /// the entry.
 185    /// </param>
 186    /// <exception cref="System.ArgumentNullException">
 187    /// if entry passed is null.
 188    /// </exception>
 189    /// <exception cref="System.IO.IOException">
 190    /// if an I/O error occured.
 191    /// </exception>
 192    /// <exception cref="System.InvalidOperationException">
 193    /// if stream was finished
 194    /// </exception>
 195    /// <exception cref="ZipException">
 196    /// Too many entries in the Zip file<br/>
 197    /// Entry name is too long<br/>
 198    /// Finish has already been called<br/>
 199    /// </exception>
 200    public void PutNextEntry(ZipEntry entry)
 201    {
 130202       if (entry == null) {
 0203        throw new ArgumentNullException(nameof(entry));
 204      }
 205
 130206       if (entries == null) {
 1207        throw new InvalidOperationException("ZipOutputStream was finished");
 208      }
 209
 129210       if (curEntry != null) {
 48211        CloseEntry();
 212      }
 213
 129214       if (entries.Count == int.MaxValue) {
 0215        throw new ZipException("Too many entries for Zip file");
 216      }
 217
 129218      CompressionMethod method = entry.CompressionMethod;
 129219      int compressionLevel = defaultCompressionLevel;
 220
 221      // Clear flags that the library manages internally
 129222      entry.Flags &= (int)GeneralBitFlags.UnicodeText;
 129223      patchEntryHeader = false;
 224
 225      bool headerInfoAvailable;
 226
 227      // No need to compress - definitely no data.
 129228       if (entry.Size == 0) {
 1229        entry.CompressedSize = entry.Size;
 1230        entry.Crc = 0;
 1231        method = CompressionMethod.Stored;
 1232        headerInfoAvailable = true;
 1233      } else {
 128234        headerInfoAvailable = (entry.Size >= 0) && entry.HasCrc && entry.CompressedSize >= 0;
 235
 236        // Switch to deflation if storing isnt possible.
 128237         if (method == CompressionMethod.Stored) {
 13238           if (!headerInfoAvailable) {
 13239             if (!CanPatchEntries) {
 240              // Can't patch entries so storing is not possible.
 5241              method = CompressionMethod.Deflated;
 5242              compressionLevel = 0;
 243            }
 5244          } else // entry.size must be > 0
 245            {
 0246            entry.CompressedSize = entry.Size;
 0247            headerInfoAvailable = entry.HasCrc;
 248          }
 249        }
 250      }
 251
 129252       if (headerInfoAvailable == false) {
 128253         if (CanPatchEntries == false) {
 254          // Only way to record size and compressed size is to append a data descriptor
 255          // after compressed data.
 256
 257          // Stored entries of this form have already been converted to deflating.
 32258          entry.Flags |= 8;
 32259        } else {
 96260          patchEntryHeader = true;
 261        }
 262      }
 263
 129264       if (Password != null) {
 33265        entry.IsCrypted = true;
 33266         if (entry.Crc < 0) {
 267          // Need to append a data descriptor as the crc isnt available for use
 268          // with encryption, the date is used instead.  Setting the flag
 269          // indicates this to the decompressor.
 29270          entry.Flags |= 8;
 271        }
 272      }
 273
 129274      entry.Offset = offset;
 129275      entry.CompressionMethod = (CompressionMethod)method;
 276
 129277      curMethod = method;
 129278      sizePatchPos = -1;
 279
 129280       if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic))) {
 121281        entry.ForceZip64();
 282      }
 283
 284      // Write the local file header
 129285      WriteLeInt(ZipConstants.LocalHeaderSignature);
 286
 129287      WriteLeShort(entry.Version);
 129288      WriteLeShort(entry.Flags);
 129289      WriteLeShort((byte)entry.CompressionMethodForHeader);
 129290      WriteLeInt((int)entry.DosTime);
 291
 292      // TODO: Refactor header writing.  Its done in several places.
 129293       if (headerInfoAvailable) {
 1294        WriteLeInt((int)entry.Crc);
 1295         if (entry.LocalHeaderRequiresZip64) {
 1296          WriteLeInt(-1);
 1297          WriteLeInt(-1);
 1298        } else {
 0299          WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.Compressed
 0300          WriteLeInt((int)entry.Size);
 301        }
 0302      } else {
 128303         if (patchEntryHeader) {
 96304          crcPatchPos = baseOutputStream_.Position;
 305        }
 128306        WriteLeInt(0);  // Crc
 307
 128308         if (patchEntryHeader) {
 96309          sizePatchPos = baseOutputStream_.Position;
 310        }
 311
 312        // For local header both sizes appear in Zip64 Extended Information
 128313         if (entry.LocalHeaderRequiresZip64 || patchEntryHeader) {
 125314          WriteLeInt(-1);
 125315          WriteLeInt(-1);
 125316        } else {
 3317          WriteLeInt(0);  // Compressed size
 3318          WriteLeInt(0);  // Uncompressed size
 319        }
 320      }
 321
 129322      byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
 323
 129324       if (name.Length > 0xFFFF) {
 0325        throw new ZipException("Entry name too long.");
 326      }
 327
 129328      var ed = new ZipExtraData(entry.ExtraData);
 329
 129330       if (entry.LocalHeaderRequiresZip64) {
 122331        ed.StartNewEntry();
 122332         if (headerInfoAvailable) {
 1333          ed.AddLeLong(entry.Size);
 1334          ed.AddLeLong(entry.CompressedSize);
 1335        } else {
 121336          ed.AddLeLong(-1);
 121337          ed.AddLeLong(-1);
 338        }
 122339        ed.AddNewEntry(1);
 340
 122341         if (!ed.Find(1)) {
 0342          throw new ZipException("Internal error cant find extra data");
 343        }
 344
 122345         if (patchEntryHeader) {
 92346          sizePatchPos = ed.CurrentReadIndex;
 347        }
 92348      } else {
 7349        ed.Delete(1);
 350      }
 351
 129352       if (entry.AESKeySize > 0) {
 0353        AddExtraDataAES(entry, ed);
 354      }
 129355      byte[] extra = ed.GetEntryData();
 356
 129357      WriteLeShort(name.Length);
 129358      WriteLeShort(extra.Length);
 359
 129360       if (name.Length > 0) {
 129361        baseOutputStream_.Write(name, 0, name.Length);
 362      }
 363
 128364       if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) {
 91365        sizePatchPos += baseOutputStream_.Position;
 366      }
 367
 128368       if (extra.Length > 0) {
 121369        baseOutputStream_.Write(extra, 0, extra.Length);
 370      }
 371
 128372      offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
 373      // Fix offsetOfCentraldir for AES
 128374       if (entry.AESKeySize > 0)
 0375        offset += entry.AESOverheadSize;
 376
 377      // Activate the entry.
 128378      curEntry = entry;
 128379      crc.Reset();
 128380       if (method == CompressionMethod.Deflated) {
 119381        deflater_.Reset();
 119382        deflater_.SetLevel(compressionLevel);
 383      }
 128384      size = 0;
 385
 128386       if (entry.IsCrypted) {
 33387         if (entry.AESKeySize > 0) {
 0388          WriteAESHeader(entry);
 0389        } else {
 33390           if (entry.Crc < 0) {            // so testing Zip will says its ok
 29391            WriteEncryptionHeader(entry.DosTime << 16);
 29392          } else {
 4393            WriteEncryptionHeader(entry.Crc);
 394          }
 395        }
 396      }
 99397    }
 398
 399    /// <summary>
 400    /// Closes the current entry, updating header and footer information as required
 401    /// </summary>
 402    /// <exception cref="System.IO.IOException">
 403    /// An I/O error occurs.
 404    /// </exception>
 405    /// <exception cref="System.InvalidOperationException">
 406    /// No entry is active.
 407    /// </exception>
 408    public void CloseEntry()
 409    {
 128410       if (curEntry == null) {
 0411        throw new InvalidOperationException("No open entry");
 412      }
 413
 128414      long csize = size;
 415
 416      // First finish the deflater, if appropriate
 128417       if (curMethod == CompressionMethod.Deflated) {
 119418         if (size >= 0) {
 119419          base.Finish();
 119420          csize = deflater_.TotalOut;
 119421        } else {
 0422          deflater_.Reset();
 423        }
 424      }
 425
 426      // Write the AES Authentication Code (a hash of the compressed and encrypted data)
 128427       if (curEntry.AESKeySize > 0) {
 0428        baseOutputStream_.Write(AESAuthCode, 0, 10);
 429      }
 430
 128431       if (curEntry.Size < 0) {
 121432        curEntry.Size = size;
 128433       } else if (curEntry.Size != size) {
 0434        throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
 435      }
 436
 128437       if (curEntry.CompressedSize < 0) {
 127438        curEntry.CompressedSize = csize;
 128439       } else if (curEntry.CompressedSize != csize) {
 0440        throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
 441      }
 442
 128443       if (curEntry.Crc < 0) {
 114444        curEntry.Crc = crc.Value;
 128445       } else if (curEntry.Crc != crc.Value) {
 0446        throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
 447      }
 448
 128449      offset += csize;
 450
 128451       if (curEntry.IsCrypted) {
 33452         if (curEntry.AESKeySize > 0) {
 0453          curEntry.CompressedSize += curEntry.AESOverheadSize;
 454
 0455        } else {
 33456          curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
 457        }
 458      }
 459
 460      // Patch the header if possible
 128461       if (patchEntryHeader) {
 95462        patchEntryHeader = false;
 463
 95464        long curPos = baseOutputStream_.Position;
 95465        baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
 95466        WriteLeInt((int)curEntry.Crc);
 467
 95468         if (curEntry.LocalHeaderRequiresZip64) {
 469
 91470           if (sizePatchPos == -1) {
 0471            throw new ZipException("Entry requires zip64 but this has been turned off");
 472          }
 473
 91474          baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
 91475          WriteLeLong(curEntry.Size);
 91476          WriteLeLong(curEntry.CompressedSize);
 91477        } else {
 4478          WriteLeInt((int)curEntry.CompressedSize);
 4479          WriteLeInt((int)curEntry.Size);
 480        }
 95481        baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
 482      }
 483
 484      // Add data descriptor if flagged as required
 128485       if ((curEntry.Flags & 8) != 0) {
 48486        WriteLeInt(ZipConstants.DataDescriptorSignature);
 48487        WriteLeInt(unchecked((int)curEntry.Crc));
 488
 48489         if (curEntry.LocalHeaderRequiresZip64) {
 43490          WriteLeLong(curEntry.CompressedSize);
 43491          WriteLeLong(curEntry.Size);
 43492          offset += ZipConstants.Zip64DataDescriptorSize;
 43493        } else {
 5494          WriteLeInt((int)curEntry.CompressedSize);
 5495          WriteLeInt((int)curEntry.Size);
 5496          offset += ZipConstants.DataDescriptorSize;
 497        }
 498      }
 499
 128500      entries.Add(curEntry);
 128501      curEntry = null;
 128502    }
 503
 504    void WriteEncryptionHeader(long crcValue)
 505    {
 33506      offset += ZipConstants.CryptoHeaderSize;
 507
 33508      InitializePassword(Password);
 509
 33510      byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
 33511      var rnd = new Random();
 33512      rnd.NextBytes(cryptBuffer);
 33513      cryptBuffer[11] = (byte)(crcValue >> 24);
 514
 33515      EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
 33516      baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
 33517    }
 518
 519    private static void AddExtraDataAES(ZipEntry entry, ZipExtraData extraData)
 520    {
 521
 522      // Vendor Version: AE-1 IS 1. AE-2 is 2. With AE-2 no CRC is required and 0 is stored.
 523      const int VENDOR_VERSION = 2;
 524      // Vendor ID is the two ASCII characters "AE".
 525      const int VENDOR_ID = 0x4541; //not 6965;
 0526      extraData.StartNewEntry();
 527      // Pack AES extra data field see http://www.winzip.com/aes_info.htm
 528      //extraData.AddLeShort(7);              // Data size (currently 7)
 0529      extraData.AddLeShort(VENDOR_VERSION);               // 2 = AE-2
 0530      extraData.AddLeShort(VENDOR_ID);                    // "AE"
 0531      extraData.AddData(entry.AESEncryptionStrength);     //  1 = 128, 2 = 192, 3 = 256
 0532      extraData.AddLeShort((int)entry.CompressionMethod); // The actual compression method used to compress the file
 0533      extraData.AddNewEntry(0x9901);
 0534    }
 535
 536    // Replaces WriteEncryptionHeader for AES
 537    //
 538    private void WriteAESHeader(ZipEntry entry)
 539    {
 540      byte[] salt;
 541      byte[] pwdVerifier;
 0542      InitializeAESPassword(entry, Password, out salt, out pwdVerifier);
 543      // File format for AES:
 544      // Size (bytes)   Content
 545      // ------------   -------
 546      // Variable       Salt value
 547      // 2              Password verification value
 548      // Variable       Encrypted file data
 549      // 10             Authentication code
 550      //
 551      // Value in the "compressed size" fields of the local file header and the central directory entry
 552      // is the total size of all the items listed above. In other words, it is the total size of the
 553      // salt value, password verification value, encrypted data, and authentication code.
 0554      baseOutputStream_.Write(salt, 0, salt.Length);
 0555      baseOutputStream_.Write(pwdVerifier, 0, pwdVerifier.Length);
 0556    }
 557
 558    /// <summary>
 559    /// Writes the given buffer to the current entry.
 560    /// </summary>
 561    /// <param name="buffer">The buffer containing data to write.</param>
 562    /// <param name="offset">The offset of the first byte to write.</param>
 563    /// <param name="count">The number of bytes to write.</param>
 564    /// <exception cref="ZipException">Archive size is invalid</exception>
 565    /// <exception cref="System.InvalidOperationException">No entry is active.</exception>
 566    public override void Write(byte[] buffer, int offset, int count)
 567    {
 4503568       if (curEntry == null) {
 0569        throw new InvalidOperationException("No open entry.");
 570      }
 571
 4503572       if (buffer == null) {
 0573        throw new ArgumentNullException(nameof(buffer));
 574      }
 575
 4503576       if (offset < 0) {
 0577        throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
 578      }
 579
 4503580       if (count < 0) {
 0581        throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
 582      }
 583
 4503584       if ((buffer.Length - offset) < count) {
 0585        throw new ArgumentException("Invalid offset/count combination");
 586      }
 587
 4503588      crc.Update(buffer, offset, count);
 4502589      size += count;
 590
 4502591       switch (curMethod) {
 592        case CompressionMethod.Deflated:
 4303593          base.Write(buffer, offset, count);
 4303594          break;
 595
 596        case CompressionMethod.Stored:
 199597           if (Password != null) {
 99598            CopyAndEncrypt(buffer, offset, count);
 99599          } else {
 100600            baseOutputStream_.Write(buffer, offset, count);
 601          }
 602          break;
 603      }
 100604    }
 605
 606    void CopyAndEncrypt(byte[] buffer, int offset, int count)
 607    {
 608      const int CopyBufferSize = 4096;
 99609      byte[] localBuffer = new byte[CopyBufferSize];
 198610       while (count > 0) {
 99611         int bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize;
 612
 99613        Array.Copy(buffer, offset, localBuffer, 0, bufferCount);
 99614        EncryptBlock(localBuffer, 0, bufferCount);
 99615        baseOutputStream_.Write(localBuffer, 0, bufferCount);
 99616        count -= bufferCount;
 99617        offset += bufferCount;
 618      }
 99619    }
 620
 621    /// <summary>
 622    /// Finishes the stream.  This will write the central directory at the
 623    /// end of the zip file and flush the stream.
 624    /// </summary>
 625    /// <remarks>
 626    /// This is automatically called when the stream is closed.
 627    /// </remarks>
 628    /// <exception cref="System.IO.IOException">
 629    /// An I/O error occurs.
 630    /// </exception>
 631    /// <exception cref="ZipException">
 632    /// Comment exceeds the maximum length<br/>
 633    /// Entry name exceeds the maximum length
 634    /// </exception>
 635    public override void Finish()
 636    {
 88637       if (entries == null) {
 2638        return;
 639      }
 640
 86641       if (curEntry != null) {
 77642        CloseEntry();
 643      }
 644
 86645      long numEntries = entries.Count;
 86646      long sizeEntries = 0;
 647
 428648      foreach (ZipEntry entry in entries) {
 128649        WriteLeInt(ZipConstants.CentralHeaderSignature);
 128650        WriteLeShort(ZipConstants.VersionMadeBy);
 128651        WriteLeShort(entry.Version);
 128652        WriteLeShort(entry.Flags);
 128653        WriteLeShort((short)entry.CompressionMethodForHeader);
 128654        WriteLeInt((int)entry.DosTime);
 128655        WriteLeInt((int)entry.Crc);
 656
 128657         if (entry.IsZip64Forced() ||
 128658          (entry.CompressedSize >= uint.MaxValue)) {
 121659          WriteLeInt(-1);
 121660        } else {
 7661          WriteLeInt((int)entry.CompressedSize);
 662        }
 663
 128664         if (entry.IsZip64Forced() ||
 128665          (entry.Size >= uint.MaxValue)) {
 121666          WriteLeInt(-1);
 121667        } else {
 7668          WriteLeInt((int)entry.Size);
 669        }
 670
 128671        byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
 672
 128673         if (name.Length > 0xffff) {
 0674          throw new ZipException("Name too long.");
 675        }
 676
 128677        var ed = new ZipExtraData(entry.ExtraData);
 678
 128679         if (entry.CentralHeaderRequiresZip64) {
 121680          ed.StartNewEntry();
 121681           if (entry.IsZip64Forced() ||
 121682            (entry.Size >= 0xffffffff)) {
 121683            ed.AddLeLong(entry.Size);
 684          }
 685
 121686           if (entry.IsZip64Forced() ||
 121687            (entry.CompressedSize >= 0xffffffff)) {
 121688            ed.AddLeLong(entry.CompressedSize);
 689          }
 690
 121691           if (entry.Offset >= 0xffffffff) {
 0692            ed.AddLeLong(entry.Offset);
 693          }
 694
 121695          ed.AddNewEntry(1);
 121696        } else {
 7697          ed.Delete(1);
 698        }
 699
 128700         if (entry.AESKeySize > 0) {
 0701          AddExtraDataAES(entry, ed);
 702        }
 128703        byte[] extra = ed.GetEntryData();
 704
 128705         byte[] entryComment =
 128706          (entry.Comment != null) ?
 128707          ZipConstants.ConvertToArray(entry.Flags, entry.Comment) :
 128708          new byte[0];
 709
 128710         if (entryComment.Length > 0xffff) {
 0711          throw new ZipException("Comment too long.");
 712        }
 713
 128714        WriteLeShort(name.Length);
 128715        WriteLeShort(extra.Length);
 128716        WriteLeShort(entryComment.Length);
 128717        WriteLeShort(0);    // disk number
 128718        WriteLeShort(0);    // internal file attributes
 719                  // external file attributes
 720
 128721         if (entry.ExternalFileAttributes != -1) {
 4722          WriteLeInt(entry.ExternalFileAttributes);
 4723        } else {
 124724           if (entry.IsDirectory) {                         // mark entry as directory (from nikolam.AT.perfectinfo.com)
 8725            WriteLeInt(16);
 8726          } else {
 116727            WriteLeInt(0);
 728          }
 729        }
 730
 128731         if (entry.Offset >= uint.MaxValue) {
 0732          WriteLeInt(-1);
 0733        } else {
 128734          WriteLeInt((int)entry.Offset);
 735        }
 736
 128737         if (name.Length > 0) {
 128738          baseOutputStream_.Write(name, 0, name.Length);
 739        }
 740
 128741         if (extra.Length > 0) {
 121742          baseOutputStream_.Write(extra, 0, extra.Length);
 743        }
 744
 128745         if (entryComment.Length > 0) {
 0746          baseOutputStream_.Write(entryComment, 0, entryComment.Length);
 747        }
 748
 128749        sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
 750      }
 751
 86752      using (ZipHelperStream zhs = new ZipHelperStream(baseOutputStream_)) {
 86753        zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, offset, zipComment);
 85754      }
 755
 85756      entries = null;
 85757    }
 758
 759    #region Instance Fields
 760    /// <summary>
 761    /// The entries for the archive.
 762    /// </summary>
 87763    ArrayList entries = new ArrayList();
 764
 765    /// <summary>
 766    /// Used to track the crc of data added to entries.
 767    /// </summary>
 87768    Crc32 crc = new Crc32();
 769
 770    /// <summary>
 771    /// The current entry being added.
 772    /// </summary>
 773    ZipEntry curEntry;
 774
 87775    int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;
 776
 87777    CompressionMethod curMethod = CompressionMethod.Deflated;
 778
 779    /// <summary>
 780    /// Used to track the size of data for an entry during writing.
 781    /// </summary>
 782    long size;
 783
 784    /// <summary>
 785    /// Offset to be recorded for each entry in the central header.
 786    /// </summary>
 787    long offset;
 788
 789    /// <summary>
 790    /// Comment for the entire archive recorded in central header.
 791    /// </summary>
 87792    byte[] zipComment = new byte[0];
 793
 794    /// <summary>
 795    /// Flag indicating that header patching is required for the current entry.
 796    /// </summary>
 797    bool patchEntryHeader;
 798
 799    /// <summary>
 800    /// Position to patch crc
 801    /// </summary>
 87802    long crcPatchPos = -1;
 803
 804    /// <summary>
 805    /// Position to patch size.
 806    /// </summary>
 87807    long sizePatchPos = -1;
 808
 809    // Default is dynamic which is not backwards compatible and can cause problems
 810    // with XP's built in compression which cant read Zip64 archives.
 811    // However it does avoid the situation were a large file is added and cannot be completed correctly.
 812    // NOTE: Setting the size for entries before they are added is the best solution!
 87813    UseZip64 useZip64_ = UseZip64.Dynamic;
 814    #endregion
 815  }
 816}