Added hammock project to debug streaming issues
[pithos-ms-client] / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipOutputStream.cs
1 // ZipOutputStream.cs
2 //
3 // Copyright (C) 2001 Mike Krueger
4 // Copyright (C) 2004 John Reilly
5 //
6 // This file was translated from java, it was part of the GNU Classpath
7 // Copyright (C) 2001 Free Software Foundation, Inc.
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 //
23 // Linking this library statically or dynamically with other modules is
24 // making a combined work based on this library.  Thus, the terms and
25 // conditions of the GNU General Public License cover the whole
26 // combination.
27 // 
28 // As a special exception, the copyright holders of this library give you
29 // permission to link this library with independent modules to produce an
30 // executable, regardless of the license terms of these independent
31 // modules, and to copy and distribute the resulting executable under
32 // terms of your choice, provided that you also meet, for each linked
33 // independent module, the terms and conditions of the license of that
34 // module.  An independent module is a module which is not derived from
35 // or based on this library.  If you modify this library, you may extend
36 // this exception to your version of the library, but you are not
37 // obligated to do so.  If you do not wish to do so, delete this
38 // exception statement from your version.
39
40 using System;
41 using System.Collections.Generic;
42 using System.IO;
43 using ICSharpCode.SharpZipLib.Silverlight.Checksums;
44 using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression;
45 using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression.Streams;
46 using ICSharpCode.SharpZipLib.Zip;
47
48 namespace ICSharpCode.SharpZipLib.Silverlight.Zip
49 {
50     /// <summary>
51     /// This is a DeflaterOutputStream that writes the files into a zip
52     /// archive one after another.  It has a special method to start a new
53     /// zip entry.  The zip entries contains information about the file name
54     /// size, compressed size, CRC, etc.
55     /// 
56     /// It includes support for Stored and Deflated entries.
57     /// This class is not thread safe.
58     /// <br/>
59     /// <br/>Author of the original java version : Jochen Hoenicke
60     /// </summary>
61     /// <example> This sample shows how to create a zip file
62     /// <code>
63     /// using System;
64     /// using System.IO;
65     /// 
66     /// using ICSharpCode.SharpZipLib.Core;
67     /// using ICSharpCode.SharpZipLib.Zip;
68     /// 
69     /// class MainClass
70     /// {
71     ///         public static void Main(string[] args)
72     ///         {
73     ///                 string[] filenames = Directory.GetFiles(args[0]);
74     ///                 byte[] buffer = new byte[4096];
75     ///                 
76     ///                 using ( ZipOutputStream s = new ZipOutputStream(File.Create(args[1])) ) {
77     ///                 
78     ///                         s.SetLevel(9); // 0 - store only to 9 - means best compression
79     ///                 
80     ///                         foreach (string file in filenames) {
81     ///                                 ZipEntry entry = new ZipEntry(file);
82     ///                                 s.PutNextEntry(entry);
83     ///
84     ///                                 using (FileStream fs = File.OpenRead(file)) {
85     ///                                         StreamUtils.Copy(fs, s, buffer);
86     ///                                 }
87     ///                         }
88     ///                 }
89     ///         }
90     /// }       
91     /// </code>
92     /// </example>
93     public class ZipOutputStream : DeflaterOutputStream
94     {
95         #region Constructors
96
97         /// <summary>
98         /// Creates a new Zip output stream, writing a zip archive.
99         /// </summary>
100         /// <param name="baseOutputStream">
101         /// The output stream to which the archive contents are written.
102         /// </param>
103         public ZipOutputStream(Stream baseOutputStream)
104             : base(baseOutputStream, new Deflater(Deflater.DEFAULT_COMPRESSION, true))
105         {
106         }
107
108         #endregion
109
110         /// <summary>
111         /// Gets a flag value of true if the central header has been added for this archive; false if it has not been added.
112         /// </summary>
113         /// <remarks>No further entries can be added once this has been done.</remarks>
114         public bool IsFinished
115         {
116             get { return entries == null; }
117         }
118
119         /// <summary>
120         /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
121         /// </summary>
122         /// <remarks>Older archivers may not understand Zip64 extensions.
123         /// If backwards compatability is an issue be careful when adding <see cref="ZipEntry.Size">entries</see> to an archive.
124         /// Setting this property to off is workable but less desirable as in those circumstances adding a file
125         /// larger then 4GB will fail.</remarks>
126         public UseZip64 UseZip64
127         {
128             get { return useZip64_; }
129             set { useZip64_ = value; }
130         }
131
132         /// <summary>
133         /// Set the zip file comment.
134         /// </summary>
135         /// <param name="comment">
136         /// The comment text for the entire archive.
137         /// </param>
138         /// <exception name ="ArgumentOutOfRangeException">
139         /// The converted comment is longer than 0xffff bytes.
140         /// </exception>
141         public void SetComment(string comment)
142         {
143             // TODO: Its not yet clear how to handle unicode comments here.
144             var commentBytes = ZipConstants.ConvertToArray(comment);
145             if (commentBytes.Length > 0xffff)
146             {
147                 throw new ArgumentOutOfRangeException("comment");
148             }
149             zipComment = commentBytes;
150         }
151
152         /// <summary>
153         /// Sets the compression level.  The new level will be activated
154         /// immediately.
155         /// </summary>
156         /// <param name="level">The new compression level (1 to 9).</param>
157         /// <exception cref="ArgumentOutOfRangeException">
158         /// Level specified is not supported.
159         /// </exception>
160         /// <see cref="Deflater"/>
161         public void SetLevel(int level)
162         {
163             deflater_.SetLevel(level);
164             defaultCompressionLevel = level;
165         }
166
167         /// <summary>
168         /// Get the current deflater compression level
169         /// </summary>
170         /// <returns>The current compression level</returns>
171         public int GetLevel()
172         {
173             return deflater_.GetLevel();
174         }
175
176         /// <summary>
177         /// Write an unsigned short in little endian byte order.
178         /// </summary>
179         private void WriteLeShort(int value)
180         {
181             unchecked
182             {
183                 baseOutputStream_.WriteByte((byte) (value & 0xff));
184                 baseOutputStream_.WriteByte((byte) ((value >> 8) & 0xff));
185             }
186         }
187
188         /// <summary>
189         /// Write an int in little endian byte order.
190         /// </summary>
191         private void WriteLeInt(int value)
192         {
193             unchecked
194             {
195                 WriteLeShort(value);
196                 WriteLeShort(value >> 16);
197             }
198         }
199
200         /// <summary>
201         /// Write an int in little endian byte order.
202         /// </summary>
203         private void WriteLeLong(long value)
204         {
205             unchecked
206             {
207                 WriteLeInt((int) value);
208                 WriteLeInt((int) (value >> 32));
209             }
210         }
211
212         /// <summary>
213         /// Starts a new Zip entry. It automatically closes the previous
214         /// entry if present.
215         /// All entry elements bar name are optional, but must be correct if present.
216         /// If the compression method is stored and the output is not patchable
217         /// the compression for that entry is automatically changed to deflate level 0
218         /// </summary>
219         /// <param name="entry">
220         /// the entry.
221         /// </param>
222         /// <exception cref="System.ArgumentNullException">
223         /// if entry passed is null.
224         /// </exception>
225         /// <exception cref="System.IO.IOException">
226         /// if an I/O error occured.
227         /// </exception>
228         /// <exception cref="System.InvalidOperationException">
229         /// if stream was finished
230         /// </exception>
231         /// <exception cref="ZipException">
232         /// Too many entries in the Zip file<br/>
233         /// Entry name is too long<br/>
234         /// Finish has already been called<br/>
235         /// </exception>
236         public void PutNextEntry(ZipEntry entry)
237         {
238             if (entry == null)
239             {
240                 throw new ArgumentNullException("entry");
241             }
242
243             if (entries == null)
244             {
245                 throw new InvalidOperationException("ZipOutputStream was finished");
246             }
247
248             if (curEntry != null)
249             {
250                 CloseEntry();
251             }
252
253             if (entries.Count == int.MaxValue)
254             {
255                 throw new ZipException("Too many entries for Zip file");
256             }
257
258             var method = entry.CompressionMethod;
259             var compressionLevel = defaultCompressionLevel;
260
261             // Clear flags that the library manages internally
262             entry.Flags &= (int) GeneralBitFlags.UnicodeText;
263             patchEntryHeader = false;
264             var headerInfoAvailable = true;
265
266             if (method == CompressionMethod.Stored)
267             {
268                 // Cant store values in a data descriptor as you cant extract stored files
269                 // if the length isnt known.
270                 entry.Flags &= ~8;
271                 if (entry.CompressedSize >= 0)
272                 {
273                     if (entry.Size < 0)
274                     {
275                         entry.Size = entry.CompressedSize;
276                     }
277                     else if (entry.Size != entry.CompressedSize)
278                     {
279                         throw new ZipException("Method STORED, but compressed size != size");
280                     }
281                 }
282                 else
283                 {
284                     if (entry.Size >= 0)
285                     {
286                         entry.CompressedSize = entry.Size;
287                     }
288                 }
289
290                 if (entry.Size < 0 || entry.Crc < 0)
291                 {
292                     if (CanPatchEntries)
293                     {
294                         headerInfoAvailable = false;
295                     }
296                     else
297                     {
298                         // Can't patch entries so storing is not possible.
299                         method = CompressionMethod.Deflated;
300                         compressionLevel = 0;
301                     }
302                 }
303             }
304
305             if (method == CompressionMethod.Deflated)
306             {
307                 if (entry.Size == 0)
308                 {
309                     // No need to compress - no data.
310                     entry.CompressedSize = entry.Size;
311                     entry.Crc = 0;
312                     method = CompressionMethod.Stored;
313                 }
314                 else if ((entry.CompressedSize < 0) || (entry.Size < 0) || (entry.Crc < 0))
315                 {
316                     headerInfoAvailable = false;
317                 }
318             }
319
320             if (headerInfoAvailable == false)
321             {
322                 if (CanPatchEntries == false)
323                 {
324                     // Only way to record size and compressed size is to append a data descriptor
325                     // after compressed data.
326                     entry.Flags |= 8;
327                 }
328                 else
329                 {
330                     patchEntryHeader = true;
331                 }
332             }
333
334             if (Password != null)
335             {
336                 entry.IsCrypted = true;
337                 if (entry.Crc < 0)
338                 {
339                     // Need to append a data descriptor as the crc isnt available for use
340                     // with encryption, the date is used instead.  Setting the flag
341                     // indicates this to the decompressor.
342                     entry.Flags |= 8;
343                 }
344             }
345
346             entry.Offset = _offset;
347             entry.CompressionMethod = method;
348
349             curMethod = method;
350             sizePatchPos = -1;
351
352             if ((useZip64_ == UseZip64.On) || ((entry.Size < 0) && (useZip64_ == UseZip64.Dynamic)))
353             {
354                 entry.ForceZip64();
355             }
356
357             // Write the local file header
358             WriteLeInt(ZipConstants.LocalHeaderSignature);
359
360             WriteLeShort(entry.Version);
361             WriteLeShort(entry.Flags);
362             WriteLeShort((byte) method);
363             WriteLeInt((int) entry.DosTime);
364
365             // TODO: Refactor header writing.  Its done in several places.
366             if (headerInfoAvailable)
367             {
368                 WriteLeInt((int) entry.Crc);
369                 if (entry.LocalHeaderRequiresZip64)
370                 {
371                     WriteLeInt(-1);
372                     WriteLeInt(-1);
373                 }
374                 else
375                 {
376                     WriteLeInt(entry.IsCrypted
377                                    ? (int) entry.CompressedSize + ZipConstants.CryptoHeaderSize
378                                    : (int) entry.CompressedSize);
379                     WriteLeInt((int) entry.Size);
380                 }
381             }
382             else
383             {
384                 if (patchEntryHeader)
385                 {
386                     crcPatchPos = baseOutputStream_.Position;
387                 }
388                 WriteLeInt(0); // Crc
389
390                 if (patchEntryHeader)
391                 {
392                     sizePatchPos = baseOutputStream_.Position;
393                 }
394
395                 // For local header both sizes appear in Zip64 Extended Information
396                 if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
397                 {
398                     WriteLeInt(-1);
399                     WriteLeInt(-1);
400                 }
401                 else
402                 {
403                     WriteLeInt(0); // Compressed size
404                     WriteLeInt(0); // Uncompressed size
405                 }
406             }
407
408             var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
409
410             if (name.Length > 0xFFFF)
411             {
412                 throw new ZipException("Entry name too long.");
413             }
414
415             var ed = new ZipExtraData(entry.ExtraData);
416
417             if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader))
418             {
419                 ed.StartNewEntry();
420                 if (headerInfoAvailable)
421                 {
422                     ed.AddLeLong(entry.Size);
423                     ed.AddLeLong(entry.CompressedSize);
424                 }
425                 else
426                 {
427                     ed.AddLeLong(-1);
428                     ed.AddLeLong(-1);
429                 }
430                 ed.AddNewEntry(1);
431
432                 if (!ed.Find(1))
433                 {
434                     throw new ZipException("Internal error cant find extra data");
435                 }
436
437                 if (patchEntryHeader)
438                 {
439                     sizePatchPos = ed.CurrentReadIndex;
440                 }
441             }
442             else
443             {
444                 ed.Delete(1);
445             }
446
447             var extra = ed.GetEntryData();
448
449             WriteLeShort(name.Length);
450             WriteLeShort(extra.Length);
451
452             if (name.Length > 0)
453             {
454                 baseOutputStream_.Write(name, 0, name.Length);
455             }
456
457             if (entry.LocalHeaderRequiresZip64 && patchEntryHeader)
458             {
459                 sizePatchPos += baseOutputStream_.Position;
460             }
461
462             if (extra.Length > 0)
463             {
464                 baseOutputStream_.Write(extra, 0, extra.Length);
465             }
466
467             _offset += ZipConstants.LocalHeaderBaseSize + name.Length + extra.Length;
468
469             // Activate the entry.
470             curEntry = entry;
471             crc.Reset();
472             if (method == CompressionMethod.Deflated)
473             {
474                 deflater_.Reset();
475                 deflater_.SetLevel(compressionLevel);
476             }
477             size = 0;
478
479             if (entry.IsCrypted)
480             {
481                 if (entry.Crc < 0)
482                 {
483                     // so testing Zip will says its ok
484                     WriteEncryptionHeader(entry.DosTime << 16);
485                 }
486                 else
487                 {
488                     WriteEncryptionHeader(entry.Crc);
489                 }
490             }
491         }
492
493         /// <summary>
494         /// Closes the current entry, updating header and footer information as required
495         /// </summary>
496         /// <exception cref="System.IO.IOException">
497         /// An I/O error occurs.
498         /// </exception>
499         /// <exception cref="System.InvalidOperationException">
500         /// No entry is active.
501         /// </exception>
502         public void CloseEntry()
503         {
504             if (curEntry == null)
505             {
506                 throw new InvalidOperationException("No open entry");
507             }
508
509             // First finish the deflater, if appropriate
510             if (curMethod == CompressionMethod.Deflated)
511             {
512                 base.Finish();
513             }
514
515             var csize = (curMethod == CompressionMethod.Deflated) ? deflater_.TotalOut : size;
516
517             if (curEntry.Size < 0)
518             {
519                 curEntry.Size = size;
520             }
521             else if (curEntry.Size != size)
522             {
523                 throw new ZipException("size was " + size + ", but I expected " + curEntry.Size);
524             }
525
526             if (curEntry.CompressedSize < 0)
527             {
528                 curEntry.CompressedSize = csize;
529             }
530             else if (curEntry.CompressedSize != csize)
531             {
532                 throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize);
533             }
534
535             if (curEntry.Crc < 0)
536             {
537                 curEntry.Crc = crc.Value;
538             }
539             else if (curEntry.Crc != crc.Value)
540             {
541                 throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc);
542             }
543
544             _offset += csize;
545
546             if (curEntry.IsCrypted)
547             {
548                 curEntry.CompressedSize += ZipConstants.CryptoHeaderSize;
549             }
550
551             // Patch the header if possible
552             if (patchEntryHeader)
553             {
554                 patchEntryHeader = false;
555
556                 var curPos = baseOutputStream_.Position;
557                 baseOutputStream_.Seek(crcPatchPos, SeekOrigin.Begin);
558                 WriteLeInt((int) curEntry.Crc);
559
560                 if (curEntry.LocalHeaderRequiresZip64)
561                 {
562                     if (sizePatchPos == -1)
563                     {
564                         throw new ZipException("Entry requires zip64 but this has been turned off");
565                     }
566
567                     baseOutputStream_.Seek(sizePatchPos, SeekOrigin.Begin);
568                     WriteLeLong(curEntry.Size);
569                     WriteLeLong(curEntry.CompressedSize);
570                 }
571                 else
572                 {
573                     WriteLeInt((int) curEntry.CompressedSize);
574                     WriteLeInt((int) curEntry.Size);
575                 }
576                 baseOutputStream_.Seek(curPos, SeekOrigin.Begin);
577             }
578
579             // Add data descriptor if flagged as required
580             if ((curEntry.Flags & 8) != 0)
581             {
582                 WriteLeInt(ZipConstants.DataDescriptorSignature);
583                 WriteLeInt(unchecked((int) curEntry.Crc));
584
585                 if (curEntry.LocalHeaderRequiresZip64)
586                 {
587                     WriteLeLong(curEntry.CompressedSize);
588                     WriteLeLong(curEntry.Size);
589                     _offset += ZipConstants.Zip64DataDescriptorSize;
590                 }
591                 else
592                 {
593                     WriteLeInt((int) curEntry.CompressedSize);
594                     WriteLeInt((int) curEntry.Size);
595                     _offset += ZipConstants.DataDescriptorSize;
596                 }
597             }
598
599             entries.Add(curEntry);
600             curEntry = null;
601         }
602
603         private void WriteEncryptionHeader(long crcValue)
604         {
605             _offset += ZipConstants.CryptoHeaderSize;
606
607             InitializePassword(Password);
608
609             var cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
610             var rnd = new Random();
611             rnd.NextBytes(cryptBuffer);
612             cryptBuffer[11] = (byte) (crcValue >> 24);
613
614             EncryptBlock(cryptBuffer, 0, cryptBuffer.Length);
615             baseOutputStream_.Write(cryptBuffer, 0, cryptBuffer.Length);
616         }
617
618         /// <summary>
619         /// Writes the given buffer to the current entry.
620         /// </summary>
621         /// <param name="buffer">The buffer containing data to write.</param>
622         /// <param name="offset">The offset of the first byte to write.</param>
623         /// <param name="count">The number of bytes to write.</param>
624         /// <exception cref="ZipException">Archive size is invalid</exception>
625         /// <exception cref="System.InvalidOperationException">No entry is active.</exception>
626         public override void Write(byte[] buffer, int offset, int count)
627         {
628             if (curEntry == null)
629             {
630                 throw new InvalidOperationException("No open entry.");
631             }
632
633             if (buffer == null)
634             {
635                 throw new ArgumentNullException("buffer");
636             }
637
638             if (offset < 0)
639             {
640                 throw new ArgumentOutOfRangeException("offset", "Cannot be negative");
641             }
642
643             if (count < 0)
644             {
645                 throw new ArgumentOutOfRangeException("count", "Cannot be negative");
646             }
647
648             if ((buffer.Length - offset) < count)
649             {
650                 throw new ArgumentException("Invalid offset/count combination");
651             }
652
653             crc.Update(buffer, offset, count);
654             size += count;
655
656             switch (curMethod)
657             {
658                 case CompressionMethod.Deflated:
659                     base.Write(buffer, offset, count);
660                     break;
661
662                 case CompressionMethod.Stored:
663                     if (Password != null)
664                     {
665                         CopyAndEncrypt(buffer, offset, count);
666                     }
667                     else
668                     {
669                         baseOutputStream_.Write(buffer, offset, count);
670                     }
671                     break;
672             }
673         }
674
675         private void CopyAndEncrypt(byte[] buffer, int offset, int count)
676         {
677             const int CopyBufferSize = 4096;
678             var localBuffer = new byte[CopyBufferSize];
679             while (count > 0)
680             {
681                 var bufferCount = (count < CopyBufferSize) ? count : CopyBufferSize;
682
683                 Array.Copy(buffer, offset, localBuffer, 0, bufferCount);
684                 EncryptBlock(localBuffer, 0, bufferCount);
685                 baseOutputStream_.Write(localBuffer, 0, bufferCount);
686                 count -= bufferCount;
687                 offset += bufferCount;
688             }
689         }
690
691         /// <summary>
692         /// Finishes the stream.  This will write the central directory at the
693         /// end of the zip file and flush the stream.
694         /// </summary>
695         /// <remarks>
696         /// This is automatically called when the stream is closed.
697         /// </remarks>
698         /// <exception cref="System.IO.IOException">
699         /// An I/O error occurs.
700         /// </exception>
701         /// <exception cref="ZipException">
702         /// Comment exceeds the maximum length<br/>
703         /// Entry name exceeds the maximum length
704         /// </exception>
705         public override void Finish()
706         {
707             if (entries == null)
708             {
709                 return;
710             }
711
712             if (curEntry != null)
713             {
714                 CloseEntry();
715             }
716
717             long numEntries = entries.Count;
718             long sizeEntries = 0;
719
720             foreach (var entry in entries)
721             {
722                 WriteLeInt(ZipConstants.CentralHeaderSignature);
723                 WriteLeShort(ZipConstants.VersionMadeBy);
724                 WriteLeShort(entry.Version);
725                 WriteLeShort(entry.Flags);
726                 WriteLeShort((short) entry.CompressionMethod);
727                 WriteLeInt((int) entry.DosTime);
728                 WriteLeInt((int) entry.Crc);
729
730                 if (entry.IsZip64Forced() ||
731                     (entry.CompressedSize >= uint.MaxValue))
732                 {
733                     WriteLeInt(-1);
734                 }
735                 else
736                 {
737                     WriteLeInt((int) entry.CompressedSize);
738                 }
739
740                 if (entry.IsZip64Forced() ||
741                     (entry.Size >= uint.MaxValue))
742                 {
743                     WriteLeInt(-1);
744                 }
745                 else
746                 {
747                     WriteLeInt((int) entry.Size);
748                 }
749
750                 var name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
751
752                 if (name.Length > 0xffff)
753                 {
754                     throw new ZipException("Name too long.");
755                 }
756
757                 var ed = new ZipExtraData(entry.ExtraData);
758
759                 if (entry.CentralHeaderRequiresZip64)
760                 {
761                     ed.StartNewEntry();
762                     if (entry.IsZip64Forced() ||
763                         (entry.Size >= 0xffffffff))
764                     {
765                         ed.AddLeLong(entry.Size);
766                     }
767
768                     if (entry.IsZip64Forced() ||
769                         (entry.CompressedSize >= 0xffffffff))
770                     {
771                         ed.AddLeLong(entry.CompressedSize);
772                     }
773
774                     if (entry.Offset >= 0xffffffff)
775                     {
776                         ed.AddLeLong(entry.Offset);
777                     }
778
779                     ed.AddNewEntry(1);
780                 }
781                 else
782                 {
783                     ed.Delete(1);
784                 }
785
786                 var extra = ed.GetEntryData();
787
788                 var entryComment =
789                     (entry.Comment != null)
790                         ?
791                             ZipConstants.ConvertToArray(entry.Flags, entry.Comment)
792                         :
793                             new byte[0];
794
795                 if (entryComment.Length > 0xffff)
796                 {
797                     throw new ZipException("Comment too long.");
798                 }
799
800                 WriteLeShort(name.Length);
801                 WriteLeShort(extra.Length);
802                 WriteLeShort(entryComment.Length);
803                 WriteLeShort(0); // disk number
804                 WriteLeShort(0); // internal file attributes
805                 // external file attributes
806
807                 if (entry.ExternalFileAttributes != -1)
808                 {
809                     WriteLeInt(entry.ExternalFileAttributes);
810                 }
811                 else
812                 {
813                     if (entry.IsDirectory)
814                     {
815                         // mark entry as directory (from nikolam.AT.perfectinfo.com)
816                         WriteLeInt(16);
817                     }
818                     else
819                     {
820                         WriteLeInt(0);
821                     }
822                 }
823
824                 if (entry.Offset >= uint.MaxValue)
825                 {
826                     WriteLeInt(-1);
827                 }
828                 else
829                 {
830                     WriteLeInt((int) entry.Offset);
831                 }
832
833                 if (name.Length > 0)
834                 {
835                     baseOutputStream_.Write(name, 0, name.Length);
836                 }
837
838                 if (extra.Length > 0)
839                 {
840                     baseOutputStream_.Write(extra, 0, extra.Length);
841                 }
842
843                 if (entryComment.Length > 0)
844                 {
845                     baseOutputStream_.Write(entryComment, 0, entryComment.Length);
846                 }
847
848                 sizeEntries += ZipConstants.CentralHeaderBaseSize + name.Length + extra.Length + entryComment.Length;
849             }
850
851             using (var zhs = new ZipHelperStream(baseOutputStream_))
852             {
853                 zhs.WriteEndOfCentralDirectory(numEntries, sizeEntries, _offset, zipComment);
854             }
855
856             entries = null;
857         }
858
859         #region Instance Fields
860
861         /// <summary>
862         /// Used to track the crc of data added to entries.
863         /// </summary>
864         private readonly Crc32 crc = new Crc32();
865
866         /// <summary>
867         /// Position to patch crc
868         /// </summary>
869         private long crcPatchPos = -1;
870
871         /// <summary>
872         /// The current entry being added.
873         /// </summary>
874         private ZipEntry curEntry;
875
876         private CompressionMethod curMethod = CompressionMethod.Deflated;
877         private int defaultCompressionLevel = Deflater.DEFAULT_COMPRESSION;
878
879         /// <summary>
880         /// The entries for the archive.
881         /// </summary>
882         private List<ZipEntry> entries = new List<ZipEntry>();
883
884         /// <summary>
885         /// Offset to be recorded for each entry in the central header.
886         /// </summary>
887         private long _offset;
888
889         /// <summary>
890         /// Flag indicating that header patching is required for the current entry.
891         /// </summary>
892         private bool patchEntryHeader;
893
894         /// <summary>
895         /// Used to track the size of data for an entry during writing.
896         /// </summary>
897         private long size;
898
899         /// <summary>
900         /// Position to patch size.
901         /// </summary>
902         private long sizePatchPos = -1;
903
904         // Default is dynamic which is not backwards compatible and can cause problems
905         // with XP's built in compression which cant read Zip64 archives.
906         // However it does avoid the situation were a large file is added and cannot be completed correctly.
907         // NOTE: Setting the size for entries before they are added is the best solution!
908         private UseZip64 useZip64_ = UseZip64.Dynamic;
909
910         /// <summary>
911         /// Comment for the entire archive recorded in central header.
912         /// </summary>
913         private byte[] zipComment = new byte[0];
914
915         #endregion
916     }
917 }