aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs697
1 files changed, 697 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs
new file mode 100644
index 00000000..dc5e1137
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs
@@ -0,0 +1,697 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Dtf.Compression.Zip
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Text;
9 using System.Collections.Generic;
10 using System.Diagnostics.CodeAnalysis;
11
12 [Flags]
13 internal enum ZipFileFlags : ushort
14 {
15 None = 0x0000,
16 Encrypt = 0x0001,
17 CompressOption1 = 0x0002,
18 CompressOption2 = 0x0004,
19 DataDescriptor = 0x0008,
20 StrongEncrypt = 0x0040,
21 UTF8 = 0x0800
22 }
23
24 internal enum ZipExtraFileFieldType : ushort
25 {
26 ZIP64 = 0x0001,
27 NTFS_TIMES = 0x000A,
28 NTFS_ACLS = 0x4453,
29 EXTIME = 0x5455
30 }
31
32 internal class ZipFileHeader
33 {
34 public const uint LFHSIG = 0x04034B50;
35 public const uint CFHSIG = 0x02014B50;
36
37 public const uint SPANSIG = 0x08074b50;
38 public const uint SPANSIG2 = 0x30304b50;
39
40 public const uint LFH_FIXEDSIZE = 30;
41 public const uint CFH_FIXEDSIZE = 46;
42
43 public ushort versionMadeBy;
44 public ushort versionNeeded;
45 public ZipFileFlags flags;
46 public ZipCompressionMethod compressionMethod;
47 public short lastModTime;
48 public short lastModDate;
49 public uint crc32;
50 public uint compressedSize;
51 public uint uncompressedSize;
52 public ushort diskStart;
53 public ushort internalFileAttrs;
54 public uint externalFileAttrs;
55 public uint localHeaderOffset;
56 public string fileName;
57 public ZipExtraFileField[] extraFields;
58 public string fileComment;
59 public bool zip64;
60
61 public ZipFileHeader()
62 {
63 this.versionMadeBy = 20;
64 this.versionNeeded = 20;
65 }
66
67 public ZipFileHeader(ZipFileInfo fileInfo, bool zip64)
68 : this()
69 {
70 this.flags = ZipFileFlags.None;
71 this.compressionMethod = fileInfo.CompressionMethod;
72 this.fileName = Path.Combine(fileInfo.Path, fileInfo.Name);
73 CompressionEngine.DateTimeToDosDateAndTime(
74 fileInfo.LastWriteTime, out this.lastModDate, out this.lastModTime);
75 this.zip64 = zip64;
76
77 if (this.zip64)
78 {
79 this.compressedSize = UInt32.MaxValue;
80 this.uncompressedSize = UInt32.MaxValue;
81 this.diskStart = UInt16.MaxValue;
82 this.versionMadeBy = 45;
83 this.versionNeeded = 45;
84 ZipExtraFileField field = new ZipExtraFileField();
85 field.fieldType = ZipExtraFileFieldType.ZIP64;
86 field.SetZip64Data(
87 fileInfo.CompressedLength,
88 fileInfo.Length,
89 0,
90 fileInfo.ArchiveNumber);
91 this.extraFields = new ZipExtraFileField[] { field };
92 }
93 else
94 {
95 this.compressedSize = (uint) fileInfo.CompressedLength;
96 this.uncompressedSize = (uint) fileInfo.Length;
97 this.diskStart = (ushort) fileInfo.ArchiveNumber;
98 }
99 }
100
101 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
102 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
103 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
104 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
105 public void Update(
106 long compressedSize,
107 long uncompressedSize,
108 uint crc32,
109 long localHeaderOffset,
110 int archiveNumber)
111 {
112 this.crc32 = crc32;
113
114 if (this.zip64)
115 {
116 this.compressedSize = UInt32.MaxValue;
117 this.uncompressedSize = UInt32.MaxValue;
118 this.localHeaderOffset = UInt32.MaxValue;
119 this.diskStart = UInt16.MaxValue;
120
121 if (this.extraFields != null)
122 {
123 foreach (ZipExtraFileField field in this.extraFields)
124 {
125 if (field.fieldType == ZipExtraFileFieldType.ZIP64)
126 {
127 field.SetZip64Data(
128 compressedSize,
129 uncompressedSize,
130 localHeaderOffset,
131 archiveNumber);
132 }
133 }
134 }
135 }
136 else
137 {
138 this.compressedSize = (uint) compressedSize;
139 this.uncompressedSize = (uint) uncompressedSize;
140 this.localHeaderOffset = (uint) localHeaderOffset;
141 this.diskStart = (ushort) archiveNumber;
142 }
143 }
144
145 public bool Read(Stream stream, bool central)
146 {
147 long startPos = stream.Position;
148
149 if (stream.Length - startPos <
150 (central ? CFH_FIXEDSIZE : LFH_FIXEDSIZE))
151 {
152 return false;
153 }
154
155 BinaryReader reader = new BinaryReader(stream);
156 uint sig = reader.ReadUInt32();
157
158 if (sig == SPANSIG || sig == SPANSIG2)
159 {
160 // Spanned zip files may optionally begin with a special marker.
161 // Just ignore it and move on.
162 sig = reader.ReadUInt32();
163 }
164
165 if (sig != (central ? CFHSIG : LFHSIG))
166 {
167 return false;
168 }
169
170 this.versionMadeBy = (central ? reader.ReadUInt16() : (ushort) 0);
171 this.versionNeeded = reader.ReadUInt16();
172 this.flags = (ZipFileFlags) reader.ReadUInt16();
173 this.compressionMethod = (ZipCompressionMethod) reader.ReadUInt16();
174 this.lastModTime = reader.ReadInt16();
175 this.lastModDate = reader.ReadInt16();
176 this.crc32 = reader.ReadUInt32();
177 this.compressedSize = reader.ReadUInt32();
178 this.uncompressedSize = reader.ReadUInt32();
179
180 this.zip64 = this.uncompressedSize == UInt32.MaxValue;
181
182 int fileNameLength = reader.ReadUInt16();
183 int extraFieldLength = reader.ReadUInt16();
184 int fileCommentLength;
185
186 if (central)
187 {
188 fileCommentLength = reader.ReadUInt16();
189
190 this.diskStart = reader.ReadUInt16();
191 this.internalFileAttrs = reader.ReadUInt16();
192 this.externalFileAttrs = reader.ReadUInt32();
193 this.localHeaderOffset = reader.ReadUInt32();
194 }
195 else
196 {
197 fileCommentLength = 0;
198 this.diskStart = 0;
199 this.internalFileAttrs = 0;
200 this.externalFileAttrs = 0;
201 this.localHeaderOffset = 0;
202 }
203
204 if (stream.Length - stream.Position <
205 fileNameLength + extraFieldLength + fileCommentLength)
206 {
207 return false;
208 }
209
210 Encoding headerEncoding = ((this.flags | ZipFileFlags.UTF8) != 0 ?
211 Encoding.UTF8 : Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage));
212
213 byte[] fileNameBytes = reader.ReadBytes(fileNameLength);
214 this.fileName = headerEncoding.GetString(fileNameBytes).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
215
216 List<ZipExtraFileField> fields = new List<ZipExtraFileField>();
217 while (extraFieldLength > 0)
218 {
219 ZipExtraFileField field = new ZipExtraFileField();
220 if (!field.Read(stream, ref extraFieldLength))
221 {
222 return false;
223 }
224 fields.Add(field);
225 if (field.fieldType == ZipExtraFileFieldType.ZIP64)
226 {
227 this.zip64 = true;
228 }
229 }
230 this.extraFields = fields.ToArray();
231
232 byte[] fileCommentBytes = reader.ReadBytes(fileCommentLength);
233 this.fileComment = headerEncoding.GetString(fileCommentBytes);
234
235 return true;
236 }
237
238 public void Write(Stream stream, bool central)
239 {
240 byte[] fileNameBytes = (this.fileName != null
241 ? Encoding.UTF8.GetBytes(this.fileName) : new byte[0]);
242 byte[] fileCommentBytes = (this.fileComment != null
243 ? Encoding.UTF8.GetBytes(this.fileComment) : new byte[0]);
244 bool useUtf8 =
245 (this.fileName != null && fileNameBytes.Length > this.fileName.Length) ||
246 (this.fileComment != null && fileCommentBytes.Length > this.fileComment.Length);
247 if (useUtf8)
248 {
249 this.flags |= ZipFileFlags.UTF8;
250 }
251
252 BinaryWriter writer = new BinaryWriter(stream);
253 writer.Write(central ? CFHSIG : LFHSIG);
254 if (central)
255 {
256 writer.Write(this.versionMadeBy);
257 }
258 writer.Write(this.versionNeeded);
259 writer.Write((ushort) this.flags);
260 writer.Write((ushort) this.compressionMethod);
261 writer.Write(this.lastModTime);
262 writer.Write(this.lastModDate);
263 writer.Write(this.crc32);
264 writer.Write(this.compressedSize);
265 writer.Write(this.uncompressedSize);
266
267 ushort extraFieldLength = 0;
268 if (this.extraFields != null)
269 {
270 foreach (ZipExtraFileField field in this.extraFields)
271 {
272 if (field.data != null)
273 {
274 extraFieldLength += (ushort) (4 + field.data.Length);
275 }
276 }
277 }
278
279 writer.Write((ushort) fileNameBytes.Length);
280 writer.Write(extraFieldLength);
281
282 if (central)
283 {
284 writer.Write((ushort) fileCommentBytes.Length);
285
286 writer.Write(this.diskStart);
287 writer.Write(this.internalFileAttrs);
288 writer.Write(this.externalFileAttrs);
289 writer.Write(this.localHeaderOffset);
290 }
291
292 writer.Write(fileNameBytes);
293
294 if (this.extraFields != null)
295 {
296 foreach (ZipExtraFileField field in this.extraFields)
297 {
298 if (field.data != null)
299 {
300 field.Write(stream);
301 }
302 }
303 }
304
305 if (central)
306 {
307 writer.Write(fileCommentBytes);
308 }
309 }
310
311 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
312 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
313 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
314 [SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
315 public void GetZip64Fields(
316 out long compressedSize,
317 out long uncompressedSize,
318 out long localHeaderOffset,
319 out int archiveNumber,
320 out uint crc)
321 {
322 compressedSize = this.compressedSize;
323 uncompressedSize = this.uncompressedSize;
324 localHeaderOffset = this.localHeaderOffset;
325 archiveNumber = this.diskStart;
326 crc = this.crc32;
327
328 foreach (ZipExtraFileField field in this.extraFields)
329 {
330 if (field.fieldType == ZipExtraFileFieldType.ZIP64)
331 {
332 field.GetZip64Data(
333 out compressedSize,
334 out uncompressedSize,
335 out localHeaderOffset,
336 out archiveNumber);
337 }
338 }
339 }
340
341 public ZipFileInfo ToZipFileInfo()
342 {
343 string name = this.fileName;
344
345 long compressedSizeL;
346 long uncompressedSizeL;
347 long localHeaderOffsetL;
348 int archiveNumberL;
349 uint crc;
350 this.GetZip64Fields(
351 out compressedSizeL,
352 out uncompressedSizeL,
353 out localHeaderOffsetL,
354 out archiveNumberL,
355 out crc);
356
357 DateTime dateTime;
358 CompressionEngine.DosDateAndTimeToDateTime(
359 this.lastModDate,
360 this.lastModTime,
361 out dateTime);
362 FileAttributes attrs = FileAttributes.Normal;
363 // TODO: look for attrs or times in extra fields
364
365 return new ZipFileInfo(name, archiveNumberL, attrs, dateTime,
366 uncompressedSizeL, compressedSizeL, this.compressionMethod);
367 }
368
369 public bool IsDirectory
370 {
371 get
372 {
373 return this.fileName != null &&
374 (this.fileName.EndsWith("/", StringComparison.Ordinal) ||
375 this.fileName.EndsWith("\\", StringComparison.Ordinal));
376 }
377 }
378
379 public int GetSize(bool central)
380 {
381 int size = 30;
382
383 int fileNameSize = (this.fileName != null
384 ? Encoding.UTF8.GetByteCount(this.fileName) : 0);
385 size += fileNameSize;
386
387 if (this.extraFields != null)
388 {
389 foreach (ZipExtraFileField field in this.extraFields)
390 {
391 if (field.data != null)
392 {
393 size += 4 + field.data.Length;
394 }
395 }
396 }
397
398 if (central)
399 {
400 size += 16;
401
402 int fileCommentSize = (this.fileComment != null
403 ? Encoding.UTF8.GetByteCount(this.fileComment) : 0);
404 size += fileCommentSize;
405 }
406
407 return size;
408 }
409 }
410
411 internal class ZipExtraFileField
412 {
413 public ZipExtraFileFieldType fieldType;
414 public byte[] data;
415
416 public bool Read(Stream stream, ref int bytesRemaining)
417 {
418 if (bytesRemaining < 4)
419 {
420 return false;
421 }
422
423 BinaryReader reader = new BinaryReader(stream);
424
425 this.fieldType = (ZipExtraFileFieldType) reader.ReadUInt16();
426 ushort dataSize = reader.ReadUInt16();
427 bytesRemaining -= 4;
428
429 if (bytesRemaining < dataSize)
430 {
431 return false;
432 }
433
434 this.data = reader.ReadBytes(dataSize);
435 bytesRemaining -= dataSize;
436
437 return true;
438 }
439
440 public void Write(Stream stream)
441 {
442 BinaryWriter writer = new BinaryWriter(stream);
443 writer.Write((ushort) this.fieldType);
444
445 byte[] dataBytes = (this.data != null ? this.data : new byte[0]);
446 writer.Write((ushort) dataBytes.Length);
447 writer.Write(dataBytes);
448 }
449
450 public bool GetZip64Data(
451 out long compressedSize,
452 out long uncompressedSize,
453 out long localHeaderOffset,
454 out int diskStart)
455 {
456 uncompressedSize = 0;
457 compressedSize = 0;
458 localHeaderOffset = 0;
459 diskStart = 0;
460
461 if (this.fieldType != ZipExtraFileFieldType.ZIP64 ||
462 this.data == null || this.data.Length != 28)
463 {
464 return false;
465 }
466
467 using (MemoryStream dataStream = new MemoryStream(this.data))
468 {
469 BinaryReader reader = new BinaryReader(dataStream);
470 uncompressedSize = reader.ReadInt64();
471 compressedSize = reader.ReadInt64();
472 localHeaderOffset = reader.ReadInt64();
473 diskStart = reader.ReadInt32();
474 }
475
476 return true;
477 }
478
479 public bool SetZip64Data(
480 long compressedSize,
481 long uncompressedSize,
482 long localHeaderOffset,
483 int diskStart)
484 {
485 if (this.fieldType != ZipExtraFileFieldType.ZIP64)
486 {
487 return false;
488 }
489
490 using (MemoryStream dataStream = new MemoryStream())
491 {
492 BinaryWriter writer = new BinaryWriter(dataStream);
493 writer.Write(uncompressedSize);
494 writer.Write(compressedSize);
495 writer.Write(localHeaderOffset);
496 writer.Write(diskStart);
497 this.data = dataStream.ToArray();
498 }
499
500 return true;
501 }
502 }
503
504 internal class ZipEndOfCentralDirectory
505 {
506 public const uint EOCDSIG = 0x06054B50;
507 public const uint EOCD64SIG = 0x06064B50;
508
509 public const uint EOCD_RECORD_FIXEDSIZE = 22;
510 public const uint EOCD64_RECORD_FIXEDSIZE = 56;
511
512 public ushort versionMadeBy;
513 public ushort versionNeeded;
514 public uint diskNumber;
515 public uint dirStartDiskNumber;
516 public long entriesOnDisk;
517 public long totalEntries;
518 public long dirSize;
519 public long dirOffset;
520 public string comment;
521 public bool zip64;
522
523 public ZipEndOfCentralDirectory()
524 {
525 this.versionMadeBy = 20;
526 this.versionNeeded = 20;
527 }
528
529 public bool Read(Stream stream)
530 {
531 long startPos = stream.Position;
532
533 if (stream.Length - startPos < EOCD_RECORD_FIXEDSIZE)
534 {
535 return false;
536 }
537
538 BinaryReader reader = new BinaryReader(stream);
539 uint sig = reader.ReadUInt32();
540
541 this.zip64 = false;
542 if (sig != EOCDSIG)
543 {
544 if (sig == EOCD64SIG)
545 {
546 this.zip64 = true;
547 }
548 else
549 {
550 return false;
551 }
552 }
553
554 if (this.zip64)
555 {
556 if (stream.Length - startPos < EOCD64_RECORD_FIXEDSIZE)
557 {
558 return false;
559 }
560
561 long recordSize = reader.ReadInt64();
562 this.versionMadeBy = reader.ReadUInt16();
563 this.versionNeeded = reader.ReadUInt16();
564 this.diskNumber = reader.ReadUInt32();
565 this.dirStartDiskNumber = reader.ReadUInt32();
566 this.entriesOnDisk = reader.ReadInt64();
567 this.totalEntries = reader.ReadInt64();
568 this.dirSize = reader.ReadInt64();
569 this.dirOffset = reader.ReadInt64();
570
571 // Ignore any extended zip64 eocd data.
572 long exDataSize = recordSize + 12 - EOCD64_RECORD_FIXEDSIZE;
573
574 if (stream.Length - stream.Position < exDataSize)
575 {
576 return false;
577 }
578
579 stream.Seek(exDataSize, SeekOrigin.Current);
580
581 this.comment = null;
582 }
583 else
584 {
585 this.diskNumber = reader.ReadUInt16();
586 this.dirStartDiskNumber = reader.ReadUInt16();
587 this.entriesOnDisk = reader.ReadUInt16();
588 this.totalEntries = reader.ReadUInt16();
589 this.dirSize = reader.ReadUInt32();
590 this.dirOffset = reader.ReadUInt32();
591
592 int commentLength = reader.ReadUInt16();
593
594 if (stream.Length - stream.Position < commentLength)
595 {
596 return false;
597 }
598
599 byte[] commentBytes = reader.ReadBytes(commentLength);
600 this.comment = Encoding.UTF8.GetString(commentBytes);
601 }
602
603 return true;
604 }
605
606 public void Write(Stream stream)
607 {
608 BinaryWriter writer = new BinaryWriter(stream);
609
610 if (this.zip64)
611 {
612 writer.Write(EOCD64SIG);
613 writer.Write((long) EOCD64_RECORD_FIXEDSIZE);
614 writer.Write(this.versionMadeBy);
615 writer.Write(this.versionNeeded);
616 writer.Write(this.diskNumber);
617 writer.Write(this.dirStartDiskNumber);
618 writer.Write(this.entriesOnDisk);
619 writer.Write(this.totalEntries);
620 writer.Write(this.dirSize);
621 writer.Write(this.dirOffset);
622 }
623 else
624 {
625 writer.Write(EOCDSIG);
626 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.diskNumber));
627 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.dirStartDiskNumber));
628 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.entriesOnDisk));
629 writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.totalEntries));
630 writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirSize));
631 writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirOffset));
632
633 byte[] commentBytes = (this.comment != null
634 ? Encoding.UTF8.GetBytes(this.comment) : new byte[0]);
635 writer.Write((ushort) commentBytes.Length);
636 writer.Write(commentBytes);
637 }
638 }
639
640 public int GetSize(bool zip64Size)
641 {
642 if (zip64Size)
643 {
644 return 56;
645 }
646 else
647 {
648 int commentSize = (this.comment != null
649 ? Encoding.UTF8.GetByteCount(this.comment) : 0);
650 return 22 + commentSize;
651 }
652 }
653 }
654
655 internal class Zip64EndOfCentralDirectoryLocator
656 {
657 public const uint EOCDL64SIG = 0x07064B50;
658
659 public const uint EOCDL64_SIZE = 20;
660
661 public uint dirStartDiskNumber;
662 public long dirOffset;
663 public uint totalDisks;
664
665 public bool Read(Stream stream)
666 {
667 long startPos = stream.Position;
668 if (stream.Length - startPos < EOCDL64_SIZE)
669 {
670 return false;
671 }
672
673 BinaryReader reader = new BinaryReader(stream);
674 uint sig = reader.ReadUInt32();
675
676 if (sig != EOCDL64SIG)
677 {
678 return false;
679 }
680
681 this.dirStartDiskNumber = reader.ReadUInt32();
682 this.dirOffset = reader.ReadInt64();
683 this.totalDisks = reader.ReadUInt32();
684
685 return true;
686 }
687
688 public void Write(Stream stream)
689 {
690 BinaryWriter writer = new BinaryWriter(stream);
691 writer.Write(EOCDL64SIG);
692 writer.Write(this.dirStartDiskNumber);
693 writer.Write(this.dirOffset);
694 writer.Write(this.totalDisks);
695 }
696 }
697}