Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2287 | - | 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 | } |