Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (30.2 kB)

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
}