Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2498 - 1
// ZipStorer, by Jaime Olivares
2
// Website: zipstorer.codeplex.com
3
// Version: 2.35 (March 14, 2010)
4
 
5
using System.Collections.Generic;
6
using System.Text;
7
 
8
namespace System.IO.Compression
9
{
10
    /// <summary>
11
    /// Unique class for compression/decompression file. Represents a Zip file.
12
    /// </summary>
13
    public class ZipStorer : IDisposable
14
    {
15
        /// <summary>
16
        /// Compression method enumeration
17
        /// </summary>
18
        public enum Compression : ushort {
19
            /// <summary>Uncompressed storage</summary> 
20
            Store = 0,
21
            /// <summary>Deflate compression method</summary>
22
            Deflate = 8 }
23
 
24
        /// <summary>
25
        /// Represents an entry in Zip file directory
26
        /// </summary>
27
        public struct ZipFileEntry
28
        {
29
            /// <summary>Compression method</summary>
30
            public Compression Method;
31
            /// <summary>Full path and filename as stored in Zip</summary>
32
            public string FilenameInZip;
33
            /// <summary>Original file size</summary>
34
            public uint FileSize;
35
            /// <summary>Compressed file size</summary>
36
            public uint CompressedSize;
37
            /// <summary>Offset of header information inside Zip storage</summary>
38
            public uint HeaderOffset;
39
            /// <summary>Offset of file inside Zip storage</summary>
40
            public uint FileOffset;
41
            /// <summary>Size of header information</summary>
42
            public uint HeaderSize;
43
            /// <summary>32-bit checksum of entire file</summary>
44
            public uint Crc32;
45
            /// <summary>Last modification time of file</summary>
46
            public DateTime ModifyTime;
47
            /// <summary>User comment for file</summary>
48
            public string Comment;
49
            /// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
50
            public bool EncodeUTF8;
51
 
52
            /// <summary>Overriden method</summary>
53
            /// <returns>Filename in Zip</returns>
54
            public override string ToString()
55
            {
56
                return this.FilenameInZip;
57
            }
58
        }
59
 
60
        #region Public fields
61
        /// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
62
        public bool EncodeUTF8 = false;
63
        /// <summary>Force deflate algotithm even if it inflates the stored file. Off by default.</summary>
64
        public bool ForceDeflating = false;
65
        #endregion
66
 
67
        #region Private fields
68
        // List of files to store
69
        private List<ZipFileEntry> Files = new List<ZipFileEntry>();
70
        // Filename of storage file
71
        private string FileName;
72
        // Stream object of storage file
73
        private Stream ZipFileStream;
74
        // General comment
75
        private string Comment = "";
76
        // Central dir image
77
        private byte[] CentralDirImage = null;
78
        // Existing files in zip
79
        private ushort ExistingFiles = 0;
80
        // File access for Open method
81
        private FileAccess Access;
82
        // Static CRC32 Table
83
        private static UInt32[] CrcTable = null;
84
        // Default filename encoder
85
        private static Encoding DefaultEncoding = Encoding.GetEncoding(437);
86
        #endregion
87
 
88
        #region Public methods
89
        // Static constructor. Just invoked once in order to create the CRC32 lookup table.
90
        static ZipStorer()
91
        {
92
            // Generate CRC32 table
93
            CrcTable = new UInt32[256];
94
            for (int i = 0; i < CrcTable.Length; i++)
95
            {
96
                UInt32 c = (UInt32)i;
97
                for (int j = 0; j < 8; j++)
98
                {
99
                    if ((c & 1) != 0)
100
                        c = 3988292384 ^ (c >> 1);
101
                    else
102
                        c >>= 1;
103
                }
104
                CrcTable[i] = c;
105
            }
106
        }
107
        /// <summary>
108
        /// Method to create a new storage file
109
        /// </summary>
110
        /// <param name="_filename">Full path of Zip file to create</param>
111
        /// <param name="_comment">General comment for Zip file</param>
112
        /// <returns>A valid ZipStorer object</returns>
113
        public static ZipStorer Create(string _filename, string _comment)
114
        {
115
            Stream stream = new FileStream(_filename, FileMode.Create, FileAccess.ReadWrite);
116
 
117
            ZipStorer zip = Create(stream, _comment);
118
            zip.Comment = _comment;
119
            zip.FileName = _filename;
120
 
121
            return zip;
122
        }
123
        /// <summary>
124
        /// Method to create a new zip storage in a stream
125
        /// </summary>
126
        /// <param name="_stream"></param>
127
        /// <param name="_comment"></param>
128
        /// <returns>A valid ZipStorer object</returns>
129
        public static ZipStorer Create(Stream _stream, string _comment)
130
        {
131
            ZipStorer zip = new ZipStorer();
132
            zip.Comment = _comment;
133
            zip.ZipFileStream = _stream;
134
            zip.Access = FileAccess.Write;
135
 
136
            return zip;
137
        }
138
        /// <summary>
139
        /// Method to open an existing storage file
140
        /// </summary>
141
        /// <param name="_filename">Full path of Zip file to open</param>
142
        /// <param name="_access">File access mode as used in FileStream constructor</param>
143
        /// <returns>A valid ZipStorer object</returns>
144
        public static ZipStorer Open(string _filename, FileAccess _access)
145
        {
146
            Stream stream = (Stream)new FileStream(_filename, FileMode.Open, _access == FileAccess.Read ? FileAccess.Read : FileAccess.ReadWrite);
147
 
148
            ZipStorer zip = Open(stream, _access);
149
            zip.FileName = _filename;
150
 
151
            return zip;
152
        }
153
        /// <summary>
154
        /// Method to open an existing storage from stream
155
        /// </summary>
156
        /// <param name="_stream">Already opened stream with zip contents</param>
157
        /// <param name="_access">File access mode for stream operations</param>
158
        /// <returns>A valid ZipStorer object</returns>
159
        public static ZipStorer Open(Stream _stream, FileAccess _access)
160
        {
161
            if (!_stream.CanSeek && _access != FileAccess.Read)
162
                throw new InvalidOperationException("Stream cannot seek");
163
 
164
            ZipStorer zip = new ZipStorer();
165
            //zip.FileName = _filename;
166
            zip.ZipFileStream = _stream;
167
            zip.Access = _access;
168
 
169
            if (zip.ReadFileInfo())
170
                return zip;
171
 
172
            throw new System.IO.InvalidDataException();
173
        }
174
        /// <summary>
175
        /// Add full contents of a file into the Zip storage
176
        /// </summary>
177
        /// <param name="_method">Compression method</param>
178
        /// <param name="_pathname">Full path of file to add to Zip storage</param>
179
        /// <param name="_filenameInZip">Filename and path as desired in Zip directory</param>
180
        /// <param name="_comment">Comment for stored file</param>        
181
        public void AddFile(Compression _method, string _pathname, string _filenameInZip, string _comment)
182
        {
183
            if (Access == FileAccess.Read)
184
                throw new InvalidOperationException("Writing is not alowed");
185
 
186
            FileStream stream = new FileStream(_pathname, FileMode.Open, FileAccess.Read);
187
            AddStream(_method, _filenameInZip, stream, File.GetLastWriteTime(_pathname), _comment);
188
            stream.Close();
189
        }
190
        /// <summary>
191
        /// Add full contents of a stream into the Zip storage
192
        /// </summary>
193
        /// <param name="_method">Compression method</param>
194
        /// <param name="_filenameInZip">Filename and path as desired in Zip directory</param>
195
        /// <param name="_source">Stream object containing the data to store in Zip</param>
196
        /// <param name="_modTime">Modification time of the data to store</param>
197
        /// <param name="_comment">Comment for stored file</param>
198
        public void AddStream(Compression _method, string _filenameInZip, Stream _source, DateTime _modTime, string _comment)
199
        {
200
            if (Access == FileAccess.Read)
201
                throw new InvalidOperationException("Writing is not alowed");
202
 
203
            long offset;
204
            if (this.Files.Count==0)
205
                offset = 0;
206
            else
207
            {
208
                ZipFileEntry last = this.Files[this.Files.Count-1];
209
                offset = last.HeaderOffset + last.HeaderSize;
210
            }
211
 
212
            // Prepare the fileinfo
213
            ZipFileEntry zfe = new ZipFileEntry();
214
            zfe.Method = _method;
215
            zfe.EncodeUTF8 = this.EncodeUTF8;
216
            zfe.FilenameInZip = NormalizedFilename(_filenameInZip);
217
            zfe.Comment = (_comment == null ? "" : _comment);
218
 
219
            // Even though we write the header now, it will have to be rewritten, since we don't know compressed size or crc.
220
            zfe.Crc32 = 0;  // to be updated later
221
            zfe.HeaderOffset = (uint)this.ZipFileStream.Position;  // offset within file of the start of this local record
222
            zfe.ModifyTime = _modTime;
223
 
224
            // Write local header
225
            WriteLocalHeader(ref zfe);
226
            zfe.FileOffset = (uint)this.ZipFileStream.Position;
227
 
228
            // Write file to zip (store)
229
            Store(ref zfe, _source);
230
            _source.Close();
231
 
232
            this.UpdateCrcAndSizes(ref zfe);
233
 
234
            Files.Add(zfe);
235
        }
236
        /// <summary>
237
        /// Updates central directory (if pertinent) and close the Zip storage
238
        /// </summary>
239
        /// <remarks>This is a required step, unless automatic dispose is used</remarks>
240
        public void Close()
241
        {
242
           if(this.ZipFileStream == null)
243
              return;
244
 
245
            if (this.Access != FileAccess.Read)
246
            {
247
                uint centralOffset = (uint)this.ZipFileStream.Position;
248
                uint centralSize = 0;
249
 
250
                if (this.CentralDirImage != null)
251
                    this.ZipFileStream.Write(CentralDirImage, 0, CentralDirImage.Length);
252
 
253
                for (int i = 0; i < Files.Count; i++)
254
                {
255
                    long pos = this.ZipFileStream.Position;
256
                    this.WriteCentralDirRecord(Files[i]);
257
                    centralSize += (uint)(this.ZipFileStream.Position - pos);
258
                }
259
 
260
                if (this.CentralDirImage != null)
261
                    this.WriteEndRecord(centralSize + (uint)CentralDirImage.Length, centralOffset);
262
                else
263
                    this.WriteEndRecord(centralSize, centralOffset);
264
            }
265
 
266
            if (this.ZipFileStream != null)
267
            {
268
                this.ZipFileStream.Flush();
269
                this.ZipFileStream.Dispose();
270
                this.ZipFileStream = null;
271
            }
272
        }
273
        /// <summary>
274
        /// Read all the file records in the central directory 
275
        /// </summary>
276
        /// <returns>List of all entries in directory</returns>
277
        public List<ZipFileEntry> ReadCentralDir()
278
        {
279
            if (this.CentralDirImage == null)
280
                throw new InvalidOperationException("Central directory currently does not exist");
281
 
282
            List<ZipFileEntry> result = new List<ZipFileEntry>();
283
 
284
            for (int pointer = 0; pointer < this.CentralDirImage.Length; )
285
            {
286
                uint signature = BitConverter.ToUInt32(CentralDirImage, pointer);
287
                if (signature != 0x02014b50)
288
                    break;
289
 
290
                bool encodeUTF8 = (BitConverter.ToUInt16(CentralDirImage, pointer + 8) & 0x0800) != 0;
291
                ushort method = BitConverter.ToUInt16(CentralDirImage, pointer + 10);
292
                uint modifyTime = BitConverter.ToUInt32(CentralDirImage, pointer + 12);
293
                uint crc32 = BitConverter.ToUInt32(CentralDirImage, pointer + 16);
294
                uint comprSize = BitConverter.ToUInt32(CentralDirImage, pointer + 20);
295
                uint fileSize = BitConverter.ToUInt32(CentralDirImage, pointer + 24);
296
                ushort filenameSize = BitConverter.ToUInt16(CentralDirImage, pointer + 28);
297
                ushort extraSize = BitConverter.ToUInt16(CentralDirImage, pointer + 30);
298
                ushort commentSize = BitConverter.ToUInt16(CentralDirImage, pointer + 32);
299
                uint headerOffset = BitConverter.ToUInt32(CentralDirImage, pointer + 42);
300
                uint headerSize = (uint)( 46 + filenameSize + extraSize + commentSize);
301
 
302
                Encoding encoder = encodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
303
 
304
                ZipFileEntry zfe = new ZipFileEntry();
305
                zfe.Method = (Compression)method;
306
                zfe.FilenameInZip = encoder.GetString(CentralDirImage, pointer + 46, filenameSize);
307
                zfe.FileOffset = GetFileOffset(headerOffset);
308
                zfe.FileSize = fileSize;
309
                zfe.CompressedSize = comprSize;
310
                zfe.HeaderOffset = headerOffset;
311
                zfe.HeaderSize = headerSize;
312
                zfe.Crc32 = crc32;
313
                zfe.ModifyTime = DosTimeToDateTime(modifyTime);
314
                if (commentSize > 0)
315
                    zfe.Comment = encoder.GetString(CentralDirImage, pointer + 46 + filenameSize + extraSize, commentSize);
316
 
317
                result.Add(zfe);
318
                pointer += (46 + filenameSize + extraSize + commentSize);
319
            }
320
 
321
            return result;
322
        }
323
        /// <summary>
324
        /// Copy the contents of a stored file into a physical file
325
        /// </summary>
326
        /// <param name="_zfe">Entry information of file to extract</param>
327
        /// <param name="_filename">Name of file to store uncompressed data</param>
328
        /// <returns>True if success, false if not.</returns>
329
        /// <remarks>Unique compression methods are Store and Deflate</remarks>
330
        public bool ExtractFile(ZipFileEntry _zfe, string _filename)
331
        {
332
            // Make sure the parent directory exist
333
            string path = System.IO.Path.GetDirectoryName(_filename);
334
 
335
            if (!Directory.Exists(path))
336
                Directory.CreateDirectory(path);
337
            // Check it is directory. If so, do nothing
338
            if (Directory.Exists(_filename))
339
                return true;
340
 
341
            Stream output = new FileStream(_filename, FileMode.Create, FileAccess.Write);
342
            bool result = ExtractFile(_zfe, output);
343
            if (result)
344
                output.Close();
345
 
346
            File.SetCreationTime(_filename, _zfe.ModifyTime);
347
            File.SetLastWriteTime(_filename, _zfe.ModifyTime);
348
 
349
            return result;
350
        }
351
        /// <summary>
352
        /// Copy the contents of a stored file into an opened stream
353
        /// </summary>
354
        /// <param name="_zfe">Entry information of file to extract</param>
355
        /// <param name="_stream">Stream to store the uncompressed data</param>
356
        /// <returns>True if success, false if not.</returns>
357
        /// <remarks>Unique compression methods are Store and Deflate</remarks>
358
        public bool ExtractFile(ZipFileEntry _zfe, Stream _stream)
359
        {
360
            if (!_stream.CanWrite)
361
                throw new InvalidOperationException("Stream cannot be written");
362
 
363
            // check signature
364
            byte[] signature = new byte[4];
365
            this.ZipFileStream.Seek(_zfe.HeaderOffset, SeekOrigin.Begin);
366
            this.ZipFileStream.Read(signature, 0, 4);
367
            if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
368
                return false;
369
 
370
            // Select input stream for inflating or just reading
371
            Stream inStream;
372
            if (_zfe.Method == Compression.Store)
373
                inStream = this.ZipFileStream;
374
            else if (_zfe.Method == Compression.Deflate)
375
                inStream = new DeflateStream(this.ZipFileStream, CompressionMode.Decompress, true);
376
            else
377
                return false;
378
 
379
            // Buffered copy
380
            byte[] buffer = new byte[16384];
381
            this.ZipFileStream.Seek(_zfe.FileOffset, SeekOrigin.Begin);
382
            uint bytesPending = _zfe.FileSize;
383
            while (bytesPending > 0)
384
            {
385
                int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length));
386
                _stream.Write(buffer, 0, bytesRead);
387
                bytesPending -= (uint)bytesRead;
388
            }
389
            _stream.Flush();
390
 
391
            if (_zfe.Method == Compression.Deflate)
392
                inStream.Dispose();
393
            return true;
394
        }
395
        /// <summary>
396
        /// Removes one of many files in storage. It creates a new Zip file.
397
        /// </summary>
398
        /// <param name="_zip">Reference to the current Zip object</param>
399
        /// <param name="_zfes">List of Entries to remove from storage</param>
400
        /// <returns>True if success, false if not</returns>
401
        /// <remarks>This method only works for storage of type FileStream</remarks>
402
        public static bool RemoveEntries(ref ZipStorer _zip, List<ZipFileEntry> _zfes)
403
        {
404
            if (!(_zip.ZipFileStream is FileStream))
405
                throw new InvalidOperationException("RemoveEntries is allowed just over streams of type FileStream");
406
 
407
 
408
            //Get full list of entries
409
            List<ZipFileEntry> fullList = _zip.ReadCentralDir();
410
 
411
            //In order to delete we need to create a copy of the zip file excluding the selected items
412
            string tempZipName = Path.GetTempFileName();
413
            string tempEntryName = Path.GetTempFileName();
414
 
415
            try
416
            {
417
                ZipStorer tempZip = ZipStorer.Create(tempZipName, string.Empty);
418
 
419
                foreach (ZipFileEntry zfe in fullList)
420
                {
421
                    if (!_zfes.Contains(zfe))
422
                    {
423
                        if (_zip.ExtractFile(zfe, tempEntryName))
424
                        {
425
                            tempZip.AddFile(zfe.Method, tempEntryName, zfe.FilenameInZip, zfe.Comment);
426
                        }
427
                    }
428
                }
429
                _zip.Close();
430
                tempZip.Close();
431
 
432
                File.Delete(_zip.FileName);
433
                File.Move(tempZipName, _zip.FileName);
434
 
435
                _zip = ZipStorer.Open(_zip.FileName, _zip.Access);
436
            }
437
            catch
438
            {
439
                return false;
440
            }
441
            finally
442
            {
443
                if (File.Exists(tempZipName))
444
                    File.Delete(tempZipName);
445
                if (File.Exists(tempEntryName))
446
                    File.Delete(tempEntryName);
447
            }
448
            return true;
449
        }
450
        #endregion
451
 
452
        #region Private methods
453
        // Calculate the file offset by reading the corresponding local header
454
        private uint GetFileOffset(uint _headerOffset)
455
        {
456
            byte[] buffer = new byte[2];
457
 
458
            this.ZipFileStream.Seek(_headerOffset + 26, SeekOrigin.Begin);
459
            this.ZipFileStream.Read(buffer, 0, 2);
460
            ushort filenameSize = BitConverter.ToUInt16(buffer, 0);
461
            this.ZipFileStream.Read(buffer, 0, 2);
462
            ushort extraSize = BitConverter.ToUInt16(buffer, 0);
463
 
464
            return (uint)(30 + filenameSize + extraSize + _headerOffset);
465
        }
466
        /* Local file header:
467
            local file header signature     4 bytes  (0x04034b50)
468
            version needed to extract       2 bytes
469
            general purpose bit flag        2 bytes
470
            compression method              2 bytes
471
            last mod file time              2 bytes
472
            last mod file date              2 bytes
473
            crc-32                          4 bytes
474
            compressed size                 4 bytes
475
            uncompressed size               4 bytes
476
            filename length                 2 bytes
477
            extra field length              2 bytes
478
 
479
            filename (variable size)
480
            extra field (variable size)
481
        */
482
        private void WriteLocalHeader(ref ZipFileEntry _zfe)
483
        {
484
            long pos = this.ZipFileStream.Position;
485
            Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
486
            byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip);
487
 
488
            this.ZipFileStream.Write(new byte[] { 80, 75, 3, 4, 20, 0}, 0, 6); // No extra header
489
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding 
490
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2);  // zipping method
491
            this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time
492
            this.ZipFileStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 12); // unused CRC, un/compressed size, updated later
493
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // filename length
494
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length
495
 
496
            this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length);
497
            _zfe.HeaderSize = (uint)(this.ZipFileStream.Position - pos);
498
        }
499
        /* Central directory's File header:
500
            central file header signature   4 bytes  (0x02014b50)
501
            version made by                 2 bytes
502
            version needed to extract       2 bytes
503
            general purpose bit flag        2 bytes
504
            compression method              2 bytes
505
            last mod file time              2 bytes
506
            last mod file date              2 bytes
507
            crc-32                          4 bytes
508
            compressed size                 4 bytes
509
            uncompressed size               4 bytes
510
            filename length                 2 bytes
511
            extra field length              2 bytes
512
            file comment length             2 bytes
513
            disk number start               2 bytes
514
            internal file attributes        2 bytes
515
            external file attributes        4 bytes
516
            relative offset of local header 4 bytes
517
 
518
            filename (variable size)
519
            extra field (variable size)
520
            file comment (variable size)
521
        */
522
        private void WriteCentralDirRecord(ZipFileEntry _zfe)
523
        {
524
            Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
525
            byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip);
526
            byte[] encodedComment = encoder.GetBytes(_zfe.Comment);
527
 
528
            this.ZipFileStream.Write(new byte[] { 80, 75, 1, 2, 23, 0xB, 20, 0 }, 0, 8);
529
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding 
530
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2);  // zipping method
531
            this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4);  // zipping date and time
532
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // file CRC
533
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // compressed file size
534
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // uncompressed file size
535
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // Filename in zip
536
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length
537
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2);
538
 
539
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // disk=0
540
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // file type: binary
541
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // Internal file attributes
542
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0x8100), 0, 2); // External file attributes (normal/readable)
543
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.HeaderOffset), 0, 4);  // Offset of header
544
 
545
            this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length);
546
            this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length);
547
        }
548
        /* End of central dir record:
549
            end of central dir signature    4 bytes  (0x06054b50)
550
            number of this disk             2 bytes
551
            number of the disk with the
552
            start of the central directory  2 bytes
553
            total number of entries in
554
            the central dir on this disk    2 bytes
555
            total number of entries in
556
            the central dir                 2 bytes
557
            size of the central directory   4 bytes
558
            offset of start of central
559
            directory with respect to
560
            the starting disk number        4 bytes
561
            zipfile comment length          2 bytes
562
            zipfile comment (variable size)
563
        */
564
        private void WriteEndRecord(uint _size, uint _offset)
565
        {
566
            Encoding encoder = this.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
567
            byte[] encodedComment = encoder.GetBytes(this.Comment);
568
 
569
            this.ZipFileStream.Write(new byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }, 0, 8);
570
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count+ExistingFiles), 0, 2);
571
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count+ExistingFiles), 0, 2);
572
            this.ZipFileStream.Write(BitConverter.GetBytes(_size), 0, 4);
573
            this.ZipFileStream.Write(BitConverter.GetBytes(_offset), 0, 4);
574
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2);
575
            this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length);
576
        }
577
        // Copies all source file into storage file
578
        private void Store(ref ZipFileEntry _zfe, Stream _source)
579
        {
580
            byte[] buffer = new byte[16384];
581
            int bytesRead;
582
            uint totalRead = 0;
583
            Stream outStream;
584
 
585
            long posStart = this.ZipFileStream.Position;
586
            long sourceStart = _source.Position;
587
 
588
            if (_zfe.Method == Compression.Store)
589
                outStream = this.ZipFileStream;
590
            else
591
                outStream = new DeflateStream(this.ZipFileStream, CompressionMode.Compress, true);
592
 
593
            _zfe.Crc32 = 0 ^ 0xffffffff;
594
 
595
            do
596
            {
597
                bytesRead = _source.Read(buffer, 0, buffer.Length);
598
                totalRead += (uint)bytesRead;
599
                if (bytesRead > 0)
600
                {
601
                    outStream.Write(buffer, 0, bytesRead);
602
 
603
                    for (uint i = 0; i < bytesRead; i++)
604
                    {
605
                        _zfe.Crc32 = ZipStorer.CrcTable[(_zfe.Crc32 ^ buffer[i]) & 0xFF] ^ (_zfe.Crc32 >> 8);
606
                    }
607
                }
608
            } while (bytesRead == buffer.Length);
609
            outStream.Flush();
610
 
611
            if (_zfe.Method == Compression.Deflate)
612
                outStream.Dispose();
613
 
614
            _zfe.Crc32 ^= 0xffffffff;
615
            _zfe.FileSize = totalRead;
616
            _zfe.CompressedSize = (uint)(this.ZipFileStream.Position - posStart);
617
 
618
            // Verify for real compression
619
            if (_zfe.Method == Compression.Deflate && !this.ForceDeflating && _source.CanSeek && _zfe.CompressedSize > _zfe.FileSize)
620
            {
621
                // Start operation again with Store algorithm
622
                _zfe.Method = Compression.Store;
623
                this.ZipFileStream.Position = posStart;
624
                this.ZipFileStream.SetLength(posStart);
625
                _source.Position = sourceStart;
626
                this.Store(ref _zfe, _source);
627
            }
628
        }
629
        /* DOS Date and time:
630
            MS-DOS date. The date is a packed value with the following format. Bits Description
631
                0-4 Day of the month (1–31)
632
                5-8 Month (1 = January, 2 = February, and so on)
633
                9-15 Year offset from 1980 (add 1980 to get actual year)
634
            MS-DOS time. The time is a packed value with the following format. Bits Description
635
                0-4 Second divided by 2
636
                5-10 Minute (0–59)
637
                11-15 Hour (0–23 on a 24-hour clock)
638
        */
639
        private uint DateTimeToDosTime(DateTime _dt)
640
        {
641
            return (uint)(
642
                (_dt.Second / 2) | (_dt.Minute << 5) | (_dt.Hour << 11) |
643
                (_dt.Day<<16) | (_dt.Month << 21) | ((_dt.Year - 1980) << 25));
644
        }
645
        private DateTime DosTimeToDateTime(uint _dt)
646
        {
647
            return new DateTime(
648
                (int)(_dt >> 25) + 1980,
649
                (int)(_dt >> 21) & 15,
650
                (int)(_dt >> 16) & 31,
651
                (int)(_dt >> 11) & 31,
652
                (int)(_dt >> 5) & 63,
653
                (int)(_dt & 31) * 2);
654
        }
655
 
656
        /* CRC32 algorithm
657
          The 'magic number' for the CRC is 0xdebb20e3.  
658
          The proper CRC pre and post conditioning
659
          is used, meaning that the CRC register is
660
          pre-conditioned with all ones (a starting value
661
          of 0xffffffff) and the value is post-conditioned by
662
          taking the one's complement of the CRC residual.
663
          If bit 3 of the general purpose flag is set, this
664
          field is set to zero in the local header and the correct
665
          value is put in the data descriptor and in the central
666
          directory.
667
        */
668
        private void UpdateCrcAndSizes(ref ZipFileEntry _zfe)
669
        {
670
            long lastPos = this.ZipFileStream.Position;  // remember position
671
 
672
            this.ZipFileStream.Position = _zfe.HeaderOffset + 8;
673
            this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2);  // zipping method
674
 
675
            this.ZipFileStream.Position = _zfe.HeaderOffset + 14;
676
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4);  // Update CRC
677
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4);  // Compressed size
678
            this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4);  // Uncompressed size
679
 
680
            this.ZipFileStream.Position = lastPos;  // restore position
681
        }
682
        // Replaces backslashes with slashes to store in zip header
683
        private string NormalizedFilename(string _filename)
684
        {
685
            string filename = _filename.Replace('\\', '/');
686
 
687
            int pos = filename.IndexOf(':');
688
            if (pos >= 0)
689
                filename = filename.Remove(0, pos + 1);
690
 
691
            return filename.Trim('/');
692
        }
693
        // Reads the end-of-central-directory record
694
        private bool ReadFileInfo()
695
        {
696
            if (this.ZipFileStream.Length < 22)
697
                return false;
698
 
699
            try
700
            {
701
                this.ZipFileStream.Seek(-17, SeekOrigin.End);
702
                BinaryReader br = new BinaryReader(this.ZipFileStream);
703
                do
704
                {
705
                    this.ZipFileStream.Seek(-5, SeekOrigin.Current);
706
                    UInt32 sig = br.ReadUInt32();
707
                    if (sig == 0x06054b50)
708
                    {
709
                        this.ZipFileStream.Seek(6, SeekOrigin.Current);
710
 
711
                        UInt16 entries = br.ReadUInt16();
712
                        Int32 centralSize = br.ReadInt32();
713
                        UInt32 centralDirOffset = br.ReadUInt32();
714
                        UInt16 commentSize = br.ReadUInt16();
715
 
716
                        // check if comment field is the very last data in file
717
                        if (this.ZipFileStream.Position + commentSize != this.ZipFileStream.Length)
718
                            return false;
719
 
720
                        // Copy entire central directory to a memory buffer
721
                        this.ExistingFiles = entries;
722
                        this.CentralDirImage = new byte[centralSize];
723
                        this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
724
                        this.ZipFileStream.Read(this.CentralDirImage, 0, centralSize);
725
 
726
                        // Leave the pointer at the begining of central dir, to append new files
727
                        this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
728
                        return true;
729
                    }
730
                } while (this.ZipFileStream.Position > 0);
731
            }
732
            catch { }
733
 
734
            return false;
735
        }
736
        #endregion
737
 
738
        #region IDisposable Members
739
        /// <summary>
740
        /// Closes the Zip file stream
741
        /// </summary>
742
        public void Dispose()
743
        {
744
            this.Close();
745
        }
746
        #endregion
747
    }
748
}