aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.Compression.Zip
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs5
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs157
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs250
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj21
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs80
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs478
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs60
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs104
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipFormat.cs697
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs82
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs489
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs336
12 files changed, 2759 insertions, 0 deletions
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs
new file mode 100644
index 00000000..f782bbb8
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/AssemblyInfo.cs
@@ -0,0 +1,5 @@
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
3using System.Diagnostics.CodeAnalysis;
4
5[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Dtf.Compression.Zip")]
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs
new file mode 100644
index 00000000..20d675d9
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ConcatStream.cs
@@ -0,0 +1,157 @@
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.IO;
7
8 /// <summary>
9 /// Used to trick a DeflateStream into reading from or writing to
10 /// a series of (chunked) streams instead of a single steream.
11 /// </summary>
12 internal class ConcatStream : Stream
13 {
14 private Stream source;
15 private long position;
16 private long length;
17 private Action<ConcatStream> nextStreamHandler;
18
19 public ConcatStream(Action<ConcatStream> nextStreamHandler)
20 {
21 if (nextStreamHandler == null)
22 {
23 throw new ArgumentNullException("nextStreamHandler");
24 }
25
26 this.nextStreamHandler = nextStreamHandler;
27 this.length = Int64.MaxValue;
28 }
29
30 public Stream Source
31 {
32 get { return this.source; }
33 set { this.source = value; }
34 }
35
36 public override bool CanRead
37 {
38 get { return true; }
39 }
40
41 public override bool CanWrite
42 {
43 get { return true; }
44 }
45
46 public override bool CanSeek
47 {
48 get { return false; }
49 }
50
51 public override long Length
52 {
53 get
54 {
55 return this.length;
56 }
57 }
58
59 public override long Position
60 {
61 get { return this.position; }
62 set { throw new NotSupportedException(); }
63 }
64
65 public override int Read(byte[] buffer, int offset, int count)
66 {
67 if (this.source == null)
68 {
69 this.nextStreamHandler(this);
70 }
71
72 count = (int) Math.Min(count, this.length - this.position);
73
74 int bytesRemaining = count;
75 while (bytesRemaining > 0)
76 {
77 if (this.source == null)
78 {
79 throw new InvalidOperationException();
80 }
81
82 int partialCount = (int) Math.Min(bytesRemaining,
83 this.source.Length - this.source.Position);
84
85 if (partialCount == 0)
86 {
87 this.nextStreamHandler(this);
88 continue;
89 }
90
91 partialCount = this.source.Read(
92 buffer, offset + count - bytesRemaining, partialCount);
93 bytesRemaining -= partialCount;
94 this.position += partialCount;
95 }
96
97 return count;
98 }
99
100 public override void Write(byte[] buffer, int offset, int count)
101 {
102 if (this.source == null)
103 {
104 this.nextStreamHandler(this);
105 }
106
107 int bytesRemaining = count;
108 while (bytesRemaining > 0)
109 {
110 if (this.source == null)
111 {
112 throw new InvalidOperationException();
113 }
114
115 int partialCount = (int) Math.Min(bytesRemaining,
116 Math.Max(0, this.length - this.source.Position));
117
118 if (partialCount == 0)
119 {
120 this.nextStreamHandler(this);
121 continue;
122 }
123
124 this.source.Write(
125 buffer, offset + count - bytesRemaining, partialCount);
126 bytesRemaining -= partialCount;
127 this.position += partialCount;
128 }
129 }
130
131 public override void Flush()
132 {
133 if (this.source != null)
134 {
135 this.source.Flush();
136 }
137 }
138
139 public override long Seek(long offset, SeekOrigin origin)
140 {
141 throw new NotSupportedException();
142 }
143
144 public override void SetLength(long value)
145 {
146 this.length = value;
147 }
148
149 public override void Close()
150 {
151 if (this.source != null)
152 {
153 this.source.Close();
154 }
155 }
156 }
157}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs
new file mode 100644
index 00000000..c645ecc1
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/CrcStream.cs
@@ -0,0 +1,250 @@
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.IO;
7 using System.Diagnostics.CodeAnalysis;
8
9 /// <summary>
10 /// Wraps a source stream and calcaluates a CRC over all bytes that are read or written.
11 /// </summary>
12 /// <remarks>
13 /// The CRC algorithm matches that used in the standard ZIP file format.
14 /// </remarks>
15 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
16 public class CrcStream : Stream
17 {
18 private Stream source;
19 private uint crc;
20
21 /// <summary>
22 /// Creates a new CrcStream instance from a source stream.
23 /// </summary>
24 /// <param name="source">Underlying stream where bytes will be read from or written to.</param>
25 public CrcStream(Stream source)
26 {
27 this.source = source;
28 }
29
30 /// <summary>
31 /// Gets the current CRC over all bytes that have been read or written
32 /// since this instance was created.
33 /// </summary>
34 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
35 public uint Crc
36 {
37 get
38 {
39 return this.crc;
40 }
41 }
42
43 /// <summary>
44 /// Gets the underlying stream that this stream reads from or writes to.
45 /// </summary>
46 public Stream Source
47 {
48 get
49 {
50 return this.source;
51 }
52 }
53
54 /// <summary>
55 /// Gets a value indicating whether the source stream supports reading.
56 /// </summary>
57 /// <value>true if the stream supports reading; otherwise, false.</value>
58 public override bool CanRead
59 {
60 get
61 {
62 return this.source.CanRead;
63 }
64 }
65
66 /// <summary>
67 /// Gets a value indicating whether the source stream supports writing.
68 /// </summary>
69 /// <value>true if the stream supports writing; otherwise, false.</value>
70 public override bool CanWrite
71 {
72 get
73 {
74 return this.source.CanWrite;
75 }
76 }
77
78 /// <summary>
79 /// Gets a value indicating whether the source stream supports seeking.
80 /// </summary>
81 /// <value>true if the stream supports seeking; otherwise, false.</value>
82 public override bool CanSeek
83 {
84 get
85 {
86 return this.source.CanSeek;
87 }
88 }
89
90 /// <summary>
91 /// Gets the length of the source stream.
92 /// </summary>
93 public override long Length
94 {
95 get
96 {
97 return this.source.Length;
98 }
99 }
100
101 /// <summary>
102 /// Gets or sets the position of the source stream.
103 /// </summary>
104 public override long Position
105 {
106 get
107 {
108 return this.source.Position;
109 }
110
111 set
112 {
113 this.source.Position = value;
114 }
115 }
116
117 /// <summary>
118 /// Sets the position within the source stream.
119 /// </summary>
120 /// <param name="offset">A byte offset relative to the origin parameter.</param>
121 /// <param name="origin">A value of type SeekOrigin indicating
122 /// the reference point used to obtain the new position.</param>
123 /// <returns>The new position within the source stream.</returns>
124 /// <remarks>
125 /// Note the CRC is only calculated over bytes that are actually read or
126 /// written, so any bytes skipped by seeking will not contribute to the CRC.
127 /// </remarks>
128 public override long Seek(long offset, SeekOrigin origin)
129 {
130 return this.source.Seek(offset, origin);
131 }
132
133 /// <summary>
134 /// Sets the length of the source stream.
135 /// </summary>
136 /// <param name="value">The desired length of the
137 /// stream in bytes.</param>
138 public override void SetLength(long value)
139 {
140 this.source.SetLength(value);
141 }
142
143 /// <summary>
144 /// Reads a sequence of bytes from the source stream and advances
145 /// the position within the stream by the number of bytes read.
146 /// </summary>
147 /// <param name="buffer">An array of bytes. When this method returns, the buffer
148 /// contains the specified byte array with the values between offset and
149 /// (offset + count - 1) replaced by the bytes read from the current source.</param>
150 /// <param name="offset">The zero-based byte offset in buffer at which to begin
151 /// storing the data read from the current stream.</param>
152 /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
153 /// <returns>The total number of bytes read into the buffer. This can be less
154 /// than the number of bytes requested if that many bytes are not currently available,
155 /// or zero (0) if the end of the stream has been reached.</returns>
156 public override int Read(byte[] buffer, int offset, int count)
157 {
158 count = this.source.Read(buffer, offset, count);
159 this.UpdateCrc(buffer, offset, count);
160 return count;
161 }
162
163 /// <summary>
164 /// Writes a sequence of bytes to the source stream and advances the
165 /// current position within this stream by the number of bytes written.
166 /// </summary>
167 /// <param name="buffer">An array of bytes. This method copies count
168 /// bytes from buffer to the current stream.</param>
169 /// <param name="offset">The zero-based byte offset in buffer at which
170 /// to begin copying bytes to the current stream.</param>
171 /// <param name="count">The number of bytes to be written to the
172 /// current stream.</param>
173 public override void Write(byte[] buffer, int offset, int count)
174 {
175 this.source.Write(buffer, offset, count);
176 this.UpdateCrc(buffer, offset, count);
177 }
178
179 /// <summary>
180 /// Flushes the source stream.
181 /// </summary>
182 public override void Flush()
183 {
184 this.source.Flush();
185 }
186
187 /// <summary>
188 /// Closes the underlying stream.
189 /// </summary>
190 public override void Close()
191 {
192 this.source.Close();
193 base.Close();
194 }
195
196 /// <summary>
197 /// Updates the CRC with a range of bytes that were read or written.
198 /// </summary>
199 private void UpdateCrc(byte[] buffer, int offset, int count)
200 {
201 this.crc = ~this.crc;
202 for( ; count > 0; count--, offset++)
203 {
204 this.crc = (this.crc >> 8) ^
205 CrcStream.crcTable[(this.crc & 0xFF) ^ buffer[offset]];
206 }
207 this.crc = ~this.crc;
208 }
209
210 private static uint[] crcTable = MakeCrcTable();
211
212 /// <summary>
213 /// Computes a table that speeds up calculation of the CRC.
214 /// </summary>
215 private static uint[] MakeCrcTable()
216 {
217 const uint poly = 0x04C11DB7u;
218 uint[] crcTable = new uint[256];
219 for(uint n = 0; n < 256; n++)
220 {
221 uint c = CrcStream.Reflect(n, 8);
222 c = c << 24;
223 for(uint k = 0; k < 8; k++)
224 {
225 c = (c << 1) ^ ((c & 0x80000000u) != 0 ? poly : 0);
226 }
227 crcTable[n] = CrcStream.Reflect(c, 32);
228 }
229 return crcTable;
230 }
231
232 /// <summary>
233 /// Reflects the ordering of certain number of bits. For exmample when reflecting
234 /// one byte, bit one is swapped with bit eight, bit two with bit seven, etc.
235 /// </summary>
236 private static uint Reflect(uint value, int bits)
237 {
238 for (int i = 0; i < bits / 2; i++)
239 {
240 uint leftBit = 1u << (bits - 1 - i);
241 uint rightBit = 1u << i;
242 if (((value & leftBit) != 0) != ((value & rightBit) != 0))
243 {
244 value ^= leftBit | rightBit;
245 }
246 }
247 return value;
248 }
249 }
250}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj b/src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj
new file mode 100644
index 00000000..2f5f2f27
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/WixToolset.Dtf.Compression.Zip.csproj
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <RootNamespace>WixToolset.Dtf.Compression.Zip</RootNamespace>
7 <AssemblyName>WixToolset.Dtf.Compression.Zip</AssemblyName>
8 <TargetFrameworks>netstandard2.0;net20</TargetFrameworks>
9 <Description>Managed libraries for zip archive packing and unpacking</Description>
10 <CreateDocumentationFile>true</CreateDocumentationFile>
11 </PropertyGroup>
12
13 <ItemGroup>
14 <ProjectReference Include="..\WixToolset.Dtf.Compression\WixToolset.Dtf.Compression.csproj" />
15 </ItemGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
19 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
20 </ItemGroup>
21</Project>
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs
new file mode 100644
index 00000000..2e1c7567
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipCompressionMethod.cs
@@ -0,0 +1,80 @@
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.Diagnostics.CodeAnalysis;
6
7 /// <summary>
8 /// Identifies the compression method or &quot;algorithm&quot;
9 /// used for a single file within a zip archive.
10 /// </summary>
11 /// <remarks>
12 /// Proprietary zip implementations may define additional compression
13 /// methods outside of those included here.
14 /// </remarks>
15 public enum ZipCompressionMethod
16 {
17 /// <summary>
18 /// The file is stored (no compression)
19 /// </summary>
20 Store = 0,
21
22 /// <summary>
23 /// The file is Shrunk
24 /// </summary>
25 Shrink = 1,
26
27 /// <summary>
28 /// The file is Reduced with compression factor 1
29 /// </summary>
30 Reduce1 = 2,
31
32 /// <summary>
33 /// The file is Reduced with compression factor 2
34 /// </summary>
35 Reduce2 = 3,
36
37 /// <summary>
38 /// The file is Reduced with compression factor 3
39 /// </summary>
40 Reduce3 = 4,
41
42 /// <summary>
43 /// The file is Reduced with compression factor 4
44 /// </summary>
45 Reduce4 = 5,
46
47 /// <summary>
48 /// The file is Imploded
49 /// </summary>
50 Implode = 6,
51
52 /// <summary>
53 /// The file is Deflated;
54 /// the most common and widely-compatible form of zip compression.
55 /// </summary>
56 Deflate = 8,
57
58 /// <summary>
59 /// The file is Deflated using the enhanced Deflate64 method.
60 /// </summary>
61 Deflate64 = 9,
62
63 /// <summary>
64 /// The file is compressed using the BZIP2 algorithm.
65 /// </summary>
66 BZip2 = 12,
67
68 /// <summary>
69 /// The file is compressed using the LZMA algorithm.
70 /// </summary>
71 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lzma")]
72 Lzma = 14,
73
74 /// <summary>
75 /// The file is compressed using the PPMd algorithm.
76 /// </summary>
77 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppmd")]
78 Ppmd = 98
79 }
80}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs
new file mode 100644
index 00000000..36b4db89
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipEngine.cs
@@ -0,0 +1,478 @@
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.IO;
7 using System.IO.Compression;
8 using System.Collections.Generic;
9 using System.Reflection;
10 using System.Diagnostics.CodeAnalysis;
11
12 /// <summary>
13 /// Engine capable of packing and unpacking archives in the zip format.
14 /// </summary>
15 public partial class ZipEngine : CompressionEngine
16 {
17 private static Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>
18 compressionStreamCreators;
19 private static Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>
20 decompressionStreamCreators;
21
22 private static void InitCompressionStreamCreators()
23 {
24 if (ZipEngine.compressionStreamCreators == null)
25 {
26 ZipEngine.compressionStreamCreators = new
27 Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>();
28 ZipEngine.decompressionStreamCreators = new
29 Dictionary<ZipCompressionMethod, Converter<Stream, Stream>>();
30
31 ZipEngine.RegisterCompressionStreamCreator(
32 ZipCompressionMethod.Store,
33 CompressionMode.Compress,
34 delegate(Stream stream) {
35 return stream;
36 });
37 ZipEngine.RegisterCompressionStreamCreator(
38 ZipCompressionMethod.Deflate,
39 CompressionMode.Compress,
40 delegate(Stream stream) {
41 return new DeflateStream(stream, CompressionMode.Compress, true);
42 });
43 ZipEngine.RegisterCompressionStreamCreator(
44 ZipCompressionMethod.Store,
45 CompressionMode.Decompress,
46 delegate(Stream stream) {
47 return stream;
48 });
49 ZipEngine.RegisterCompressionStreamCreator(
50 ZipCompressionMethod.Deflate,
51 CompressionMode.Decompress,
52 delegate(Stream stream) {
53 return new DeflateStream(stream, CompressionMode.Decompress, true);
54 });
55 }
56 }
57
58 /// <summary>
59 /// Registers a delegate that can create a warpper stream for
60 /// compressing or uncompressing the data of a source stream.
61 /// </summary>
62 /// <param name="compressionMethod">Compression method being registered.</param>
63 /// <param name="compressionMode">Indicates registration for ether
64 /// compress or decompress mode.</param>
65 /// <param name="creator">Delegate being registered.</param>
66 /// <remarks>
67 /// For compression, the delegate accepts a stream that writes to the archive
68 /// and returns a wrapper stream that compresses bytes as they are written.
69 /// For decompression, the delegate accepts a stream that reads from the archive
70 /// and returns a wrapper stream that decompresses bytes as they are read.
71 /// This wrapper stream model follows the design used by
72 /// System.IO.Compression.DeflateStream, and indeed that class is used
73 /// to implement the Deflate compression method by default.
74 /// <para>To unregister a delegate, call this method again and pass
75 /// null for the delegate parameter.</para>
76 /// </remarks>
77 /// <example>
78 /// When the ZipEngine class is initialized, the Deflate compression method
79 /// is automatically registered like this:
80 /// <code>
81 /// ZipEngine.RegisterCompressionStreamCreator(
82 /// ZipCompressionMethod.Deflate,
83 /// CompressionMode.Compress,
84 /// delegate(Stream stream) {
85 /// return new DeflateStream(stream, CompressionMode.Compress, true);
86 /// });
87 /// ZipEngine.RegisterCompressionStreamCreator(
88 /// ZipCompressionMethod.Deflate,
89 /// CompressionMode.Decompress,
90 /// delegate(Stream stream) {
91 /// return new DeflateStream(stream, CompressionMode.Decompress, true);
92 /// });
93 /// </code></example>
94 public static void RegisterCompressionStreamCreator(
95 ZipCompressionMethod compressionMethod,
96 CompressionMode compressionMode,
97 Converter<Stream, Stream> creator)
98 {
99 ZipEngine.InitCompressionStreamCreators();
100 if (compressionMode == CompressionMode.Compress)
101 {
102 ZipEngine.compressionStreamCreators[compressionMethod] = creator;
103 }
104 else
105 {
106 ZipEngine.decompressionStreamCreators[compressionMethod] = creator;
107 }
108 }
109
110 // Progress data
111 private string currentFileName;
112 private int currentFileNumber;
113 private int totalFiles;
114 private long currentFileBytesProcessed;
115 private long currentFileTotalBytes;
116 private string mainArchiveName;
117 private string currentArchiveName;
118 private short currentArchiveNumber;
119 private short totalArchives;
120 private long currentArchiveBytesProcessed;
121 private long currentArchiveTotalBytes;
122 private long fileBytesProcessed;
123 private long totalFileBytes;
124 private string comment;
125
126 /// <summary>
127 /// Creates a new instance of the zip engine.
128 /// </summary>
129 public ZipEngine()
130 : base()
131 {
132 ZipEngine.InitCompressionStreamCreators();
133 }
134
135 /// <summary>
136 /// Gets the comment from the last-examined archive,
137 /// or sets the comment to be added to any created archives.
138 /// </summary>
139 public string ArchiveComment
140 {
141 get
142 {
143 return this.comment;
144 }
145 set
146 {
147 this.comment = value;
148 }
149 }
150
151 /// <summary>
152 /// Checks whether a Stream begins with a header that indicates
153 /// it is a valid archive file.
154 /// </summary>
155 /// <param name="stream">Stream for reading the archive file.</param>
156 /// <returns>True if the stream is a valid zip archive
157 /// (with no offset); false otherwise.</returns>
158 public override bool IsArchive(Stream stream)
159 {
160 if (stream == null)
161 {
162 throw new ArgumentNullException("stream");
163 }
164
165 if (stream.Length - stream.Position < 4)
166 {
167 return false;
168 }
169
170 BinaryReader reader = new BinaryReader(stream);
171 uint sig = reader.ReadUInt32();
172 switch (sig)
173 {
174 case ZipFileHeader.LFHSIG:
175 case ZipEndOfCentralDirectory.EOCDSIG:
176 case ZipEndOfCentralDirectory.EOCD64SIG:
177 case ZipFileHeader.SPANSIG:
178 case ZipFileHeader.SPANSIG2:
179 return true;
180 default:
181 return false;
182 }
183 }
184
185 /// <summary>
186 /// Gets the offset of an archive that is positioned 0 or more bytes
187 /// from the start of the Stream.
188 /// </summary>
189 /// <param name="stream">A stream for reading the archive.</param>
190 /// <returns>The offset in bytes of the archive,
191 /// or -1 if no archive is found in the Stream.</returns>
192 /// <remarks>The archive must begin on a 4-byte boundary.</remarks>
193 public override long FindArchiveOffset(Stream stream)
194 {
195 long offset = base.FindArchiveOffset(stream);
196 if (offset > 0)
197 {
198 // Some self-extract packages include the exe stub in file offset calculations.
199 // Check the first header directory offset to decide whether the entire
200 // archive needs to be offset or not.
201
202 ZipEndOfCentralDirectory eocd = this.GetEOCD(null, stream);
203 if (eocd != null && eocd.totalEntries > 0)
204 {
205 stream.Seek(eocd.dirOffset, SeekOrigin.Begin);
206
207 ZipFileHeader header = new ZipFileHeader();
208 if (header.Read(stream, true) && header.localHeaderOffset < stream.Length)
209 {
210 stream.Seek(header.localHeaderOffset, SeekOrigin.Begin);
211 if (header.Read(stream, false))
212 {
213 return 0;
214 }
215 }
216 }
217 }
218
219 return offset;
220 }
221
222 /// <summary>
223 /// Gets information about files in a zip archive or archive chain.
224 /// </summary>
225 /// <param name="streamContext">A context interface to handle opening
226 /// and closing of archive and file streams.</param>
227 /// <param name="fileFilter">A predicate that can determine
228 /// which files to process, optional.</param>
229 /// <returns>Information about files in the archive stream.</returns>
230 /// <exception cref="ArchiveException">The archive provided
231 /// by the stream context is not valid.</exception>
232 /// <remarks>
233 /// The <paramref name="fileFilter"/> predicate takes an internal file
234 /// path and returns true to include the file or false to exclude it.
235 /// </remarks>
236 public override IList<ArchiveFileInfo> GetFileInfo(
237 IUnpackStreamContext streamContext,
238 Predicate<string> fileFilter)
239 {
240 if (streamContext == null)
241 {
242 throw new ArgumentNullException("streamContext");
243 }
244
245 lock (this)
246 {
247 IList<ZipFileHeader> headers = this.GetCentralDirectory(streamContext);
248 if (headers == null)
249 {
250 throw new ZipException("Zip central directory not found.");
251 }
252
253 List<ArchiveFileInfo> files = new List<ArchiveFileInfo>(headers.Count);
254 foreach (ZipFileHeader header in headers)
255 {
256 if (!header.IsDirectory &&
257 (fileFilter == null || fileFilter(header.fileName)))
258 {
259 files.Add(header.ToZipFileInfo());
260 }
261 }
262
263 return files.AsReadOnly();
264 }
265 }
266
267 /// <summary>
268 /// Reads all the file headers from the central directory in the main archive.
269 /// </summary>
270 private IList<ZipFileHeader> GetCentralDirectory(IUnpackStreamContext streamContext)
271 {
272 Stream archiveStream = null;
273 this.currentArchiveNumber = 0;
274 try
275 {
276 List<ZipFileHeader> headers = new List<ZipFileHeader>();
277 archiveStream = this.OpenArchive(streamContext, 0);
278
279 ZipEndOfCentralDirectory eocd = this.GetEOCD(streamContext, archiveStream);
280 if (eocd == null)
281 {
282 return null;
283 }
284 else if (eocd.totalEntries == 0)
285 {
286 return headers;
287 }
288
289 headers.Capacity = (int) eocd.totalEntries;
290
291 if (eocd.dirOffset > archiveStream.Length - ZipFileHeader.CFH_FIXEDSIZE)
292 {
293 streamContext.CloseArchiveReadStream(
294 this.currentArchiveNumber, String.Empty, archiveStream);
295 archiveStream = null;
296 }
297 else
298 {
299 archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
300 uint sig = new BinaryReader(archiveStream).ReadUInt32();
301 if (sig != ZipFileHeader.CFHSIG)
302 {
303 streamContext.CloseArchiveReadStream(
304 this.currentArchiveNumber, String.Empty, archiveStream);
305 archiveStream = null;
306 }
307 }
308
309 if (archiveStream == null)
310 {
311 this.currentArchiveNumber = (short) (eocd.dirStartDiskNumber + 1);
312 archiveStream = streamContext.OpenArchiveReadStream(
313 this.currentArchiveNumber, String.Empty, this);
314
315 if (archiveStream == null)
316 {
317 return null;
318 }
319 }
320
321 archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
322
323 while (headers.Count < eocd.totalEntries)
324 {
325 ZipFileHeader header = new ZipFileHeader();
326 if (!header.Read(archiveStream, true))
327 {
328 throw new ZipException(
329 "Missing or invalid central directory file header");
330 }
331
332 headers.Add(header);
333
334 if (headers.Count < eocd.totalEntries &&
335 archiveStream.Position == archiveStream.Length)
336 {
337 streamContext.CloseArchiveReadStream(
338 this.currentArchiveNumber, String.Empty, archiveStream);
339 this.currentArchiveNumber++;
340 archiveStream = streamContext.OpenArchiveReadStream(
341 this.currentArchiveNumber, String.Empty, this);
342 if (archiveStream == null)
343 {
344 this.currentArchiveNumber = 0;
345 archiveStream = streamContext.OpenArchiveReadStream(
346 this.currentArchiveNumber, String.Empty, this);
347 }
348 }
349 }
350
351 return headers;
352 }
353 finally
354 {
355 if (archiveStream != null)
356 {
357 streamContext.CloseArchiveReadStream(
358 this.currentArchiveNumber, String.Empty, archiveStream);
359 }
360 }
361 }
362
363 /// <summary>
364 /// Locates and reads the end of central directory record near the
365 /// end of the archive.
366 /// </summary>
367 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
368 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "streamContext")]
369 private ZipEndOfCentralDirectory GetEOCD(
370 IUnpackStreamContext streamContext, Stream archiveStream)
371 {
372 BinaryReader reader = new BinaryReader(archiveStream);
373 long offset = archiveStream.Length
374 - ZipEndOfCentralDirectory.EOCD_RECORD_FIXEDSIZE;
375 while (offset >= 0)
376 {
377 archiveStream.Seek(offset, SeekOrigin.Begin);
378
379 uint sig = reader.ReadUInt32();
380 if (sig == ZipEndOfCentralDirectory.EOCDSIG)
381 {
382 break;
383 }
384
385 offset--;
386 }
387
388 if (offset < 0)
389 {
390 return null;
391 }
392
393 ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
394 archiveStream.Seek(offset, SeekOrigin.Begin);
395 if (!eocd.Read(archiveStream))
396 {
397 throw new ZipException("Invalid end of central directory record");
398 }
399
400 if (eocd.dirOffset == (long) UInt32.MaxValue)
401 {
402 string saveComment = eocd.comment;
403
404 archiveStream.Seek(
405 offset - Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE,
406 SeekOrigin.Begin);
407
408 Zip64EndOfCentralDirectoryLocator eocdl =
409 new Zip64EndOfCentralDirectoryLocator();
410 if (!eocdl.Read(archiveStream))
411 {
412 throw new ZipException("Missing or invalid end of " +
413 "central directory record locator");
414 }
415
416 if (eocdl.dirStartDiskNumber == eocdl.totalDisks - 1)
417 {
418 // ZIP64 eocd is entirely in current stream.
419 archiveStream.Seek(eocdl.dirOffset, SeekOrigin.Begin);
420 if (!eocd.Read(archiveStream))
421 {
422 throw new ZipException("Missing or invalid ZIP64 end of " +
423 "central directory record");
424 }
425 }
426 else if (streamContext == null)
427 {
428 return null;
429 }
430 else
431 {
432 // TODO: handle EOCD64 spanning archives!
433 throw new NotImplementedException("Zip implementation does not " +
434 "handle end of central directory record that spans archives.");
435 }
436
437 eocd.comment = saveComment;
438 }
439
440 return eocd;
441 }
442
443 private void ResetProgressData()
444 {
445 this.currentFileName = null;
446 this.currentFileNumber = 0;
447 this.totalFiles = 0;
448 this.currentFileBytesProcessed = 0;
449 this.currentFileTotalBytes = 0;
450 this.currentArchiveName = null;
451 this.currentArchiveNumber = 0;
452 this.totalArchives = 0;
453 this.currentArchiveBytesProcessed = 0;
454 this.currentArchiveTotalBytes = 0;
455 this.fileBytesProcessed = 0;
456 this.totalFileBytes = 0;
457 }
458
459 private void OnProgress(ArchiveProgressType progressType)
460 {
461 ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
462 progressType,
463 this.currentFileName,
464 this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
465 this.totalFiles,
466 this.currentFileBytesProcessed,
467 this.currentFileTotalBytes,
468 this.currentArchiveName,
469 this.currentArchiveNumber,
470 this.totalArchives,
471 this.currentArchiveBytesProcessed,
472 this.currentArchiveTotalBytes,
473 this.fileBytesProcessed,
474 this.totalFileBytes);
475 this.OnProgress(e);
476 }
477 }
478}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs
new file mode 100644
index 00000000..50fd6156
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipException.cs
@@ -0,0 +1,60 @@
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.IO;
7 using System.Resources;
8 using System.Globalization;
9 using System.Runtime.Serialization;
10
11 /// <summary>
12 /// Exception class for zip operations.
13 /// </summary>
14 [Serializable]
15 public class ZipException : ArchiveException
16 {
17 /// <summary>
18 /// Creates a new ZipException with a specified error message and a reference to the
19 /// inner exception that is the cause of this exception.
20 /// </summary>
21 /// <param name="message">The message that describes the error.</param>
22 /// <param name="innerException">The exception that is the cause of the current exception. If the
23 /// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
24 /// is raised in a catch block that handles the inner exception.</param>
25 public ZipException(string message, Exception innerException)
26 : base(message, innerException) { }
27
28 /// <summary>
29 /// Creates a new ZipException with a specified error message.
30 /// </summary>
31 /// <param name="message">The message that describes the error.</param>
32 public ZipException(string message)
33 : this(message, null) { }
34
35 /// <summary>
36 /// Creates a new ZipException.
37 /// </summary>
38 public ZipException()
39 : this(null, null) { }
40
41 /// <summary>
42 /// Initializes a new instance of the ZipException class with serialized data.
43 /// </summary>
44 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
45 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
46 protected ZipException(SerializationInfo info, StreamingContext context) : base(info, context)
47 {
48 }
49
50 /// <summary>
51 /// Sets the SerializationInfo with information about the exception.
52 /// </summary>
53 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
54 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
55 public override void GetObjectData(SerializationInfo info, StreamingContext context)
56 {
57 base.GetObjectData(info, context);
58 }
59 }
60}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs
new file mode 100644
index 00000000..d865bbba
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipFileInfo.cs
@@ -0,0 +1,104 @@
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.IO;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a compressed file within a zip package; provides operations for getting
11 /// the file properties and extracting the file.
12 /// </summary>
13 [Serializable]
14 public class ZipFileInfo : ArchiveFileInfo
15 {
16 private long compressedLength;
17 private ZipCompressionMethod compressionMethod;
18
19 /// <summary>
20 /// Creates a new ZipFileInfo object representing a file within a zip in a specified path.
21 /// </summary>
22 /// <param name="zipInfo">An object representing the zip archive containing the file.</param>
23 /// <param name="filePath">The path to the file within the zip archive. Usually, this is a simple file
24 /// name, but if the zip archive contains a directory structure this may include the directory.</param>
25 public ZipFileInfo(ZipInfo zipInfo, string filePath)
26 : base(zipInfo, filePath)
27 {
28 if (zipInfo == null)
29 {
30 throw new ArgumentNullException("zipInfo");
31 }
32 }
33
34 /// <summary>
35 /// Creates a new ZipFileInfo object with all parameters specified,
36 /// used internally when reading the metadata out of a zip archive.
37 /// </summary>
38 /// <param name="filePath">The internal path and name of the file in the zip archive.</param>
39 /// <param name="zipNumber">The zip archive number where the file starts.</param>
40 /// <param name="attributes">The stored attributes of the file.</param>
41 /// <param name="lastWriteTime">The stored last write time of the file.</param>
42 /// <param name="length">The uncompressed size of the file.</param>
43 /// <param name="compressedLength">The compressed size of the file.</param>
44 /// <param name="compressionMethod">Compression algorithm used for this file.</param>
45 internal ZipFileInfo(
46 string filePath,
47 int zipNumber,
48 FileAttributes attributes,
49 DateTime lastWriteTime,
50 long length,
51 long compressedLength,
52 ZipCompressionMethod compressionMethod)
53 : base(filePath, zipNumber, attributes, lastWriteTime, length)
54 {
55 this.compressedLength = compressedLength;
56 this.compressionMethod = compressionMethod;
57 }
58
59 /// <summary>
60 /// Initializes a new instance of the ZipFileInfo class with serialized data.
61 /// </summary>
62 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
63 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
64 protected ZipFileInfo(SerializationInfo info, StreamingContext context)
65 : base(info, context)
66 {
67 this.compressedLength = info.GetInt64("compressedLength");
68 }
69
70 /// <summary>
71 /// Gets the compressed size of the file in bytes.
72 /// </summary>
73 public long CompressedLength
74 {
75 get
76 {
77 return this.compressedLength;
78 }
79 }
80
81 /// <summary>
82 /// Gets the method used to compress this file.
83 /// </summary>
84 public ZipCompressionMethod CompressionMethod
85 {
86 get
87 {
88 return this.compressionMethod;
89 }
90 }
91
92 /// <summary>
93 /// Sets the SerializationInfo with information about the archive.
94 /// </summary>
95 /// <param name="info">The SerializationInfo that holds the serialized object data.</param>
96 /// <param name="context">The StreamingContext that contains contextual information
97 /// about the source or destination.</param>
98 public override void GetObjectData(SerializationInfo info, StreamingContext context)
99 {
100 base.GetObjectData(info, context);
101 info.AddValue("compressedLength", this.compressedLength);
102 }
103 }
104}
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}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs
new file mode 100644
index 00000000..73f65fa0
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipInfo.cs
@@ -0,0 +1,82 @@
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.Collections.Generic;
7 using System.Runtime.Serialization;
8
9 /// <summary>
10 /// Object representing a zip file on disk; provides access to
11 /// file-based operations on the zip file.
12 /// </summary>
13 /// <remarks>
14 /// Generally, the methods on this class are much easier to use than the
15 /// stream-based interfaces provided by the <see cref="ZipEngine"/> class.
16 /// </remarks>
17 [Serializable]
18 public class ZipInfo : ArchiveInfo
19 {
20 /// <summary>
21 /// Creates a new CabinetInfo object representing a zip file in a specified path.
22 /// </summary>
23 /// <param name="path">The path to the zip file. When creating a zip file, this file does not
24 /// necessarily exist yet.</param>
25 public ZipInfo(string path)
26 : base(path)
27 {
28 }
29
30 /// <summary>
31 /// Initializes a new instance of the CabinetInfo class with serialized data.
32 /// </summary>
33 /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
34 /// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
35 protected ZipInfo(SerializationInfo info, StreamingContext context)
36 : base(info, context)
37 {
38 }
39
40 /// <summary>
41 /// Creates a compression engine that does the low-level work for
42 /// this object.
43 /// </summary>
44 /// <returns>A new <see cref="ZipEngine"/> instance.</returns>
45 /// <remarks>
46 /// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
47 /// immediately after use.
48 /// </remarks>
49 protected override CompressionEngine CreateCompressionEngine()
50 {
51 return new ZipEngine();
52 }
53
54 /// <summary>
55 /// Gets information about the files contained in the archive.
56 /// </summary>
57 /// <returns>A list of <see cref="ZipFileInfo"/> objects, each
58 /// containing information about a file in the archive.</returns>
59 public new IList<ZipFileInfo> GetFiles()
60 {
61 IList<ArchiveFileInfo> files = base.GetFiles();
62 List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
63 foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
64 return zipFiles.AsReadOnly();
65 }
66
67 /// <summary>
68 /// Gets information about the certain files contained in the archive file.
69 /// </summary>
70 /// <param name="searchPattern">The search string, such as
71 /// &quot;*.txt&quot;.</param>
72 /// <returns>A list of <see cref="ZipFileInfo"/> objects, each containing
73 /// information about a file in the archive.</returns>
74 public new IList<ZipFileInfo> GetFiles(string searchPattern)
75 {
76 IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
77 List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
78 foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
79 return zipFiles.AsReadOnly();
80 }
81 }
82}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs
new file mode 100644
index 00000000..b9c183d3
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipPacker.cs
@@ -0,0 +1,489 @@
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.IO;
7 using System.IO.Compression;
8 using System.Collections.Generic;
9 using System.Globalization;
10
11 public partial class ZipEngine
12 {
13 /// <summary>
14 /// Creates a zip archive or chain of zip archives.
15 /// </summary>
16 /// <param name="streamContext">A context interface to handle opening
17 /// and closing of archive and file streams.</param>
18 /// <param name="files">An array of file lists. Each list is
19 /// compressed into one stream in the archive.</param>
20 /// <param name="maxArchiveSize">The maximum number of bytes for one archive
21 /// before the contents are chained to the next archive, or zero for unlimited
22 /// archive size.</param>
23 /// <exception cref="ArchiveException">The archive could not be
24 /// created.</exception>
25 /// <remarks>
26 /// The stream context implementation may provide a mapping from the file
27 /// paths within the archive to the external file paths.
28 /// </remarks>
29 public override void Pack(
30 IPackStreamContext streamContext,
31 IEnumerable<string> files,
32 long maxArchiveSize)
33 {
34 if (streamContext == null)
35 {
36 throw new ArgumentNullException("streamContext");
37 }
38
39 if (files == null)
40 {
41 throw new ArgumentNullException("files");
42 }
43
44 lock (this)
45 {
46 Stream archiveStream = null;
47 try
48 {
49 this.ResetProgressData();
50 this.totalArchives = 1;
51
52 object forceZip64Value = streamContext.GetOption("forceZip64", null);
53 bool forceZip64 = Convert.ToBoolean(
54 forceZip64Value, CultureInfo.InvariantCulture);
55
56 // Count the total number of files and bytes to be compressed.
57 foreach (string file in files)
58 {
59 FileAttributes attributes;
60 DateTime lastWriteTime;
61 Stream fileStream = streamContext.OpenFileReadStream(
62 file,
63 out attributes,
64 out lastWriteTime);
65 if (fileStream != null)
66 {
67 this.totalFileBytes += fileStream.Length;
68 this.totalFiles++;
69 streamContext.CloseFileReadStream(file, fileStream);
70 }
71 }
72
73 List<ZipFileHeader> fileHeaders = new List<ZipFileHeader>();
74 this.currentFileNumber = -1;
75
76 if (this.currentArchiveName == null)
77 {
78 this.mainArchiveName = streamContext.GetArchiveName(0);
79 this.currentArchiveName = this.mainArchiveName;
80
81 if (String.IsNullOrEmpty(this.currentArchiveName))
82 {
83 throw new FileNotFoundException("No name provided for archive.");
84 }
85 }
86
87 this.OnProgress(ArchiveProgressType.StartArchive);
88
89 // Compress files one by one, saving header info for each.
90 foreach (string file in files)
91 {
92 ZipFileHeader fileHeader = this.PackOneFile(
93 streamContext,
94 file,
95 maxArchiveSize,
96 forceZip64,
97 ref archiveStream);
98
99 if (fileHeader != null)
100 {
101 fileHeaders.Add(fileHeader);
102 }
103
104 this.currentArchiveTotalBytes = (archiveStream != null ?
105 archiveStream.Position : 0);
106 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
107 }
108
109 bool zip64 = forceZip64 || this.totalFiles > UInt16.MaxValue;
110
111 // Write the central directory composed of all the file headers.
112 uint centralDirStartArchiveNumber = 0;
113 long centralDirStartPosition = 0;
114 long centralDirSize = 0;
115 for (int i = 0; i < fileHeaders.Count; i++)
116 {
117 ZipFileHeader fileHeader = fileHeaders[i];
118
119 int headerSize = fileHeader.GetSize(true);
120 centralDirSize += headerSize;
121
122 this.CheckArchiveWriteStream(
123 streamContext,
124 maxArchiveSize,
125 headerSize,
126 ref archiveStream);
127
128 if (i == 0)
129 {
130 centralDirStartArchiveNumber = (uint) this.currentArchiveNumber;
131 centralDirStartPosition = archiveStream.Position;
132 }
133
134 fileHeader.Write(archiveStream, true);
135 if (fileHeader.zip64)
136 {
137 zip64 = true;
138 }
139 }
140
141 this.currentArchiveTotalBytes =
142 (archiveStream != null ? archiveStream.Position : 0);
143 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
144
145 ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
146 eocd.dirStartDiskNumber = centralDirStartArchiveNumber;
147 eocd.entriesOnDisk = fileHeaders.Count;
148 eocd.totalEntries = fileHeaders.Count;
149 eocd.dirSize = centralDirSize;
150 eocd.dirOffset = centralDirStartPosition;
151 eocd.comment = this.comment;
152
153 Zip64EndOfCentralDirectoryLocator eocdl =
154 new Zip64EndOfCentralDirectoryLocator();
155
156 int maxFooterSize = eocd.GetSize(false);
157 if (archiveStream != null && (zip64 || archiveStream.Position >
158 ((long) UInt32.MaxValue) - eocd.GetSize(false)))
159 {
160 maxFooterSize += eocd.GetSize(true) + (int)
161 Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE;
162 zip64 = true;
163 }
164
165 this.CheckArchiveWriteStream(
166 streamContext,
167 maxArchiveSize,
168 maxFooterSize,
169 ref archiveStream);
170 eocd.diskNumber = (uint) this.currentArchiveNumber;
171
172 if (zip64)
173 {
174 eocd.versionMadeBy = 45;
175 eocd.versionNeeded = 45;
176 eocd.zip64 = true;
177 eocdl.dirOffset = archiveStream.Position;
178 eocdl.dirStartDiskNumber = (uint) this.currentArchiveNumber;
179 eocdl.totalDisks = (uint) this.currentArchiveNumber + 1;
180 eocd.Write(archiveStream);
181 eocdl.Write(archiveStream);
182
183 eocd.dirOffset = UInt32.MaxValue;
184 eocd.dirStartDiskNumber = UInt16.MaxValue;
185 }
186
187 eocd.zip64 = false;
188 eocd.Write(archiveStream);
189
190 this.currentArchiveTotalBytes = archiveStream.Position;
191 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
192 }
193 finally
194 {
195 if (archiveStream != null)
196 {
197 streamContext.CloseArchiveWriteStream(
198 this.currentArchiveNumber, this.mainArchiveName, archiveStream);
199 this.OnProgress(ArchiveProgressType.FinishArchive);
200 }
201 }
202 }
203 }
204
205 /// <summary>
206 /// Moves to the next archive in the sequence if necessary.
207 /// </summary>
208 private void CheckArchiveWriteStream(
209 IPackStreamContext streamContext,
210 long maxArchiveSize,
211 long requiredSize,
212 ref Stream archiveStream)
213 {
214 if (archiveStream != null &&
215 archiveStream.Length > 0 && maxArchiveSize > 0)
216 {
217 long sizeRemaining = maxArchiveSize - archiveStream.Length;
218 if (sizeRemaining < requiredSize)
219 {
220 string nextArchiveName = streamContext.GetArchiveName(
221 this.currentArchiveNumber + 1);
222
223 if (String.IsNullOrEmpty(nextArchiveName))
224 {
225 throw new FileNotFoundException("No name provided for archive #" +
226 this.currentArchiveNumber + 1);
227 }
228
229 this.currentArchiveTotalBytes = archiveStream.Position;
230 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
231
232 streamContext.CloseArchiveWriteStream(
233 this.currentArchiveNumber,
234 nextArchiveName,
235 archiveStream);
236 archiveStream = null;
237
238 this.OnProgress(ArchiveProgressType.FinishArchive);
239
240 this.currentArchiveNumber++;
241 this.totalArchives++;
242 this.currentArchiveBytesProcessed = 0;
243 this.currentArchiveTotalBytes = 0;
244 }
245 }
246
247 if (archiveStream == null)
248 {
249 if (this.currentArchiveNumber > 0)
250 {
251 this.OnProgress(ArchiveProgressType.StartArchive);
252 }
253
254 archiveStream = streamContext.OpenArchiveWriteStream(
255 this.currentArchiveNumber, this.mainArchiveName, true, this);
256
257 if (archiveStream == null)
258 {
259 throw new FileNotFoundException("Stream not provided for archive #" +
260 this.currentArchiveNumber);
261 }
262 }
263 }
264
265 /// <summary>
266 /// Adds one file to a zip archive in the process of being created.
267 /// </summary>
268 private ZipFileHeader PackOneFile(
269 IPackStreamContext streamContext,
270 string file,
271 long maxArchiveSize,
272 bool forceZip64,
273 ref Stream archiveStream)
274 {
275 Stream fileStream = null;
276 int headerArchiveNumber = 0;
277 try
278 {
279 // TODO: call GetOption to get compression method for the specific file
280 ZipCompressionMethod compressionMethod = ZipCompressionMethod.Deflate;
281 if (this.CompressionLevel == WixToolset.Dtf.Compression.CompressionLevel.None)
282 {
283 compressionMethod = ZipCompressionMethod.Store;
284 }
285
286 Converter<Stream, Stream> compressionStreamCreator;
287 if (!ZipEngine.compressionStreamCreators.TryGetValue(
288 compressionMethod, out compressionStreamCreator))
289 {
290 return null;
291 }
292
293 FileAttributes attributes;
294 DateTime lastWriteTime;
295 fileStream = streamContext.OpenFileReadStream(
296 file, out attributes, out lastWriteTime);
297 if (fileStream == null)
298 {
299 return null;
300 }
301
302 this.currentFileName = file;
303 this.currentFileNumber++;
304
305 this.currentFileTotalBytes = fileStream.Length;
306 this.currentFileBytesProcessed = 0;
307 this.OnProgress(ArchiveProgressType.StartFile);
308
309 ZipFileInfo fileInfo = new ZipFileInfo(
310 file,
311 this.currentArchiveNumber,
312 attributes,
313 lastWriteTime,
314 fileStream.Length,
315 0,
316 compressionMethod);
317
318 bool zip64 = forceZip64 || fileStream.Length >= (long) UInt32.MaxValue;
319 ZipFileHeader fileHeader = new ZipFileHeader(fileInfo, zip64);
320
321 this.CheckArchiveWriteStream(
322 streamContext,
323 maxArchiveSize,
324 fileHeader.GetSize(false),
325 ref archiveStream);
326
327 long headerPosition = archiveStream.Position;
328 fileHeader.Write(archiveStream, false);
329 headerArchiveNumber = this.currentArchiveNumber;
330
331 uint crc;
332 long bytesWritten = this.PackFileBytes(
333 streamContext,
334 fileStream,
335 maxArchiveSize,
336 compressionStreamCreator,
337 ref archiveStream,
338 out crc);
339
340 fileHeader.Update(
341 bytesWritten,
342 fileStream.Length,
343 crc,
344 headerPosition,
345 headerArchiveNumber);
346
347 streamContext.CloseFileReadStream(file, fileStream);
348 fileStream = null;
349
350 // Go back and rewrite the updated file header.
351 if (this.currentArchiveNumber == headerArchiveNumber)
352 {
353 long fileEndPosition = archiveStream.Position;
354 archiveStream.Seek(headerPosition, SeekOrigin.Begin);
355 fileHeader.Write(archiveStream, false);
356 archiveStream.Seek(fileEndPosition, SeekOrigin.Begin);
357 }
358 else
359 {
360 // The file spanned archives, so temporarily reopen
361 // the archive where it started.
362 string headerArchiveName = streamContext.GetArchiveName(
363 headerArchiveNumber + 1);
364 Stream headerStream = null;
365 try
366 {
367 headerStream = streamContext.OpenArchiveWriteStream(
368 headerArchiveNumber, headerArchiveName, false, this);
369 headerStream.Seek(headerPosition, SeekOrigin.Begin);
370 fileHeader.Write(headerStream, false);
371 }
372 finally
373 {
374 if (headerStream != null)
375 {
376 streamContext.CloseArchiveWriteStream(
377 headerArchiveNumber, headerArchiveName, headerStream);
378 }
379 }
380 }
381
382 this.OnProgress(ArchiveProgressType.FinishFile);
383
384 return fileHeader;
385 }
386 finally
387 {
388 if (fileStream != null)
389 {
390 streamContext.CloseFileReadStream(
391 this.currentFileName, fileStream);
392 }
393 }
394 }
395
396 /// <summary>
397 /// Writes compressed bytes of one file to the archive,
398 /// keeping track of the CRC and number of bytes written.
399 /// </summary>
400 private long PackFileBytes(
401 IPackStreamContext streamContext,
402 Stream fileStream,
403 long maxArchiveSize,
404 Converter<Stream, Stream> compressionStreamCreator,
405 ref Stream archiveStream,
406 out uint crc)
407 {
408 long writeStartPosition = archiveStream.Position;
409 long bytesWritten = 0;
410 CrcStream fileCrcStream = new CrcStream(fileStream);
411
412 ConcatStream concatStream = new ConcatStream(
413 delegate(ConcatStream s)
414 {
415 Stream sourceStream = s.Source;
416 bytesWritten += sourceStream.Position - writeStartPosition;
417
418 this.CheckArchiveWriteStream(
419 streamContext,
420 maxArchiveSize,
421 1,
422 ref sourceStream);
423
424 writeStartPosition = sourceStream.Position;
425 s.Source = sourceStream;
426 });
427
428 concatStream.Source = archiveStream;
429
430 if (maxArchiveSize > 0)
431 {
432 concatStream.SetLength(maxArchiveSize);
433 }
434
435 Stream compressionStream = compressionStreamCreator(concatStream);
436
437 try
438 {
439 byte[] buf = new byte[4096];
440 long bytesRemaining = fileStream.Length;
441 int counter = 0;
442 while (bytesRemaining > 0)
443 {
444 int count = (int) Math.Min(
445 bytesRemaining, (long) buf.Length);
446
447 count = fileCrcStream.Read(buf, 0, count);
448 if (count <= 0)
449 {
450 throw new ZipException(
451 "Failed to read file: " + this.currentFileName);
452 }
453
454 compressionStream.Write(buf, 0, count);
455 bytesRemaining -= count;
456
457 this.fileBytesProcessed += count;
458 this.currentFileBytesProcessed += count;
459 this.currentArchiveTotalBytes = concatStream.Source.Position;
460 this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
461
462 if (++counter % 16 == 0) // Report every 64K
463 {
464 this.OnProgress(ArchiveProgressType.PartialFile);
465 }
466 }
467
468 if (compressionStream is DeflateStream)
469 {
470 compressionStream.Close();
471 }
472 else
473 {
474 compressionStream.Flush();
475 }
476 }
477 finally
478 {
479 archiveStream = concatStream.Source;
480 }
481
482 bytesWritten += archiveStream.Position - writeStartPosition;
483
484 crc = fileCrcStream.Crc;
485
486 return bytesWritten;
487 }
488 }
489}
diff --git a/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs
new file mode 100644
index 00000000..681c0e46
--- /dev/null
+++ b/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs
@@ -0,0 +1,336 @@
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.IO;
7 using System.IO.Compression;
8 using System.Collections.Generic;
9
10 public partial class ZipEngine
11 {
12 /// <summary>
13 /// Extracts files from a zip archive or archive chain.
14 /// </summary>
15 /// <param name="streamContext">A context interface to handle opening
16 /// and closing of archive and file streams.</param>
17 /// <param name="fileFilter">An optional predicate that can determine
18 /// which files to process.</param>
19 /// <exception cref="ArchiveException">The archive provided
20 /// by the stream context is not valid.</exception>
21 /// <remarks>
22 /// The <paramref name="fileFilter"/> predicate takes an internal file
23 /// path and returns true to include the file or false to exclude it.
24 /// </remarks>
25 public override void Unpack(
26 IUnpackStreamContext streamContext,
27 Predicate<string> fileFilter)
28 {
29 if (streamContext == null)
30 {
31 throw new ArgumentNullException("streamContext");
32 }
33
34 lock (this)
35 {
36 IList<ZipFileHeader> allHeaders = this.GetCentralDirectory(streamContext);
37 if (allHeaders == null)
38 {
39 throw new ZipException("Zip central directory not found.");
40 }
41
42 IList<ZipFileHeader> headers = new List<ZipFileHeader>(allHeaders.Count);
43 foreach (ZipFileHeader header in allHeaders)
44 {
45 if (!header.IsDirectory &&
46 (fileFilter == null || fileFilter(header.fileName)))
47 {
48 headers.Add(header);
49 }
50 }
51
52 this.ResetProgressData();
53
54 // Count the total number of files and bytes to be compressed.
55 this.totalFiles = headers.Count;
56 foreach (ZipFileHeader header in headers)
57 {
58 long compressedSize;
59 long uncompressedSize;
60 long localHeaderOffset;
61 int archiveNumber;
62 uint crc;
63 header.GetZip64Fields(
64 out compressedSize,
65 out uncompressedSize,
66 out localHeaderOffset,
67 out archiveNumber,
68 out crc);
69
70 this.totalFileBytes += uncompressedSize;
71 if (archiveNumber >= this.totalArchives)
72 {
73 this.totalArchives = (short) (archiveNumber + 1);
74 }
75 }
76
77 this.currentArchiveNumber = -1;
78 this.currentFileNumber = -1;
79 Stream archiveStream = null;
80 try
81 {
82 foreach (ZipFileHeader header in headers)
83 {
84 this.currentFileNumber++;
85 this.UnpackOneFile(streamContext, header, ref archiveStream);
86 }
87 }
88 finally
89 {
90 if (archiveStream != null)
91 {
92 streamContext.CloseArchiveReadStream(
93 0, String.Empty, archiveStream);
94 this.currentArchiveNumber--;
95 this.OnProgress(ArchiveProgressType.FinishArchive);
96 }
97 }
98 }
99 }
100
101 /// <summary>
102 /// Unpacks a single file from an archive or archive chain.
103 /// </summary>
104 private void UnpackOneFile(
105 IUnpackStreamContext streamContext,
106 ZipFileHeader header,
107 ref Stream archiveStream)
108 {
109 ZipFileInfo fileInfo = null;
110 Stream fileStream = null;
111 try
112 {
113 Converter<Stream, Stream> compressionStreamCreator;
114 if (!ZipEngine.decompressionStreamCreators.TryGetValue(
115 header.compressionMethod, out compressionStreamCreator))
116 {
117 // Silently skip files of an unsupported compression method.
118 return;
119 }
120
121 long compressedSize;
122 long uncompressedSize;
123 long localHeaderOffset;
124 int archiveNumber;
125 uint crc;
126 header.GetZip64Fields(
127 out compressedSize,
128 out uncompressedSize,
129 out localHeaderOffset,
130 out archiveNumber,
131 out crc);
132
133 if (this.currentArchiveNumber != archiveNumber + 1)
134 {
135 if (archiveStream != null)
136 {
137 streamContext.CloseArchiveReadStream(
138 this.currentArchiveNumber,
139 String.Empty,
140 archiveStream);
141 archiveStream = null;
142
143 this.OnProgress(ArchiveProgressType.FinishArchive);
144 this.currentArchiveName = null;
145 }
146
147 this.currentArchiveNumber = (short) (archiveNumber + 1);
148 this.currentArchiveBytesProcessed = 0;
149 this.currentArchiveTotalBytes = 0;
150
151 archiveStream = this.OpenArchive(
152 streamContext, this.currentArchiveNumber);
153
154 FileStream archiveFileStream = archiveStream as FileStream;
155 this.currentArchiveName = (archiveFileStream != null ?
156 Path.GetFileName(archiveFileStream.Name) : null);
157
158 this.currentArchiveTotalBytes = archiveStream.Length;
159 this.currentArchiveNumber--;
160 this.OnProgress(ArchiveProgressType.StartArchive);
161 this.currentArchiveNumber++;
162 }
163
164 archiveStream.Seek(localHeaderOffset, SeekOrigin.Begin);
165
166 ZipFileHeader localHeader = new ZipFileHeader();
167 if (!localHeader.Read(archiveStream, false) ||
168 !ZipEngine.AreFilePathsEqual(localHeader.fileName, header.fileName))
169 {
170 string msg = "Could not read file: " + header.fileName;
171 throw new ZipException(msg);
172 }
173
174 fileInfo = header.ToZipFileInfo();
175
176 fileStream = streamContext.OpenFileWriteStream(
177 fileInfo.FullName,
178 fileInfo.Length,
179 fileInfo.LastWriteTime);
180
181 if (fileStream != null)
182 {
183 this.currentFileName = header.fileName;
184 this.currentFileBytesProcessed = 0;
185 this.currentFileTotalBytes = fileInfo.Length;
186 this.currentArchiveNumber--;
187 this.OnProgress(ArchiveProgressType.StartFile);
188 this.currentArchiveNumber++;
189
190 this.UnpackFileBytes(
191 streamContext,
192 fileInfo.FullName,
193 fileInfo.CompressedLength,
194 fileInfo.Length,
195 header.crc32,
196 fileStream,
197 compressionStreamCreator,
198 ref archiveStream);
199 }
200 }
201 finally
202 {
203 if (fileStream != null)
204 {
205 streamContext.CloseFileWriteStream(
206 fileInfo.FullName,
207 fileStream,
208 fileInfo.Attributes,
209 fileInfo.LastWriteTime);
210
211 this.currentArchiveNumber--;
212 this.OnProgress(ArchiveProgressType.FinishFile);
213 this.currentArchiveNumber++;
214 }
215 }
216 }
217
218 /// <summary>
219 /// Compares two internal file paths while ignoring case and slash differences.
220 /// </summary>
221 /// <param name="path1">The first path to compare.</param>
222 /// <param name="path2">The second path to compare.</param>
223 /// <returns>True if the paths are equivalent.</returns>
224 private static bool AreFilePathsEqual(string path1, string path2)
225 {
226 path1 = path1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
227 path2 = path2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
228 return String.Compare(path1, path2, StringComparison.OrdinalIgnoreCase) == 0;
229 }
230
231 private Stream OpenArchive(IUnpackStreamContext streamContext, int archiveNumber)
232 {
233 Stream archiveStream = streamContext.OpenArchiveReadStream(
234 archiveNumber, String.Empty, this);
235 if (archiveStream == null && archiveNumber != 0)
236 {
237 archiveStream = streamContext.OpenArchiveReadStream(
238 0, String.Empty, this);
239 }
240
241 if (archiveStream == null)
242 {
243 throw new FileNotFoundException("Archive stream not provided.");
244 }
245
246 return archiveStream;
247 }
248
249 /// <summary>
250 /// Decompresses bytes for one file from an archive or archive chain,
251 /// checking the crc at the end.
252 /// </summary>
253 private void UnpackFileBytes(
254 IUnpackStreamContext streamContext,
255 string fileName,
256 long compressedSize,
257 long uncompressedSize,
258 uint crc,
259 Stream fileStream,
260 Converter<Stream, Stream> compressionStreamCreator,
261 ref Stream archiveStream)
262 {
263 CrcStream crcStream = new CrcStream(fileStream);
264
265 ConcatStream concatStream = new ConcatStream(
266 delegate(ConcatStream s)
267 {
268 this.currentArchiveBytesProcessed = s.Source.Position;
269 streamContext.CloseArchiveReadStream(
270 this.currentArchiveNumber,
271 String.Empty,
272 s.Source);
273
274 this.currentArchiveNumber--;
275 this.OnProgress(ArchiveProgressType.FinishArchive);
276 this.currentArchiveNumber += 2;
277 this.currentArchiveName = null;
278 this.currentArchiveBytesProcessed = 0;
279 this.currentArchiveTotalBytes = 0;
280
281 s.Source = this.OpenArchive(streamContext, this.currentArchiveNumber);
282
283 FileStream archiveFileStream = s.Source as FileStream;
284 this.currentArchiveName = (archiveFileStream != null ?
285 Path.GetFileName(archiveFileStream.Name) : null);
286
287 this.currentArchiveTotalBytes = s.Source.Length;
288 this.currentArchiveNumber--;
289 this.OnProgress(ArchiveProgressType.StartArchive);
290 this.currentArchiveNumber++;
291 });
292
293 concatStream.Source = archiveStream;
294 concatStream.SetLength(compressedSize);
295
296 Stream decompressionStream = compressionStreamCreator(concatStream);
297
298 try
299 {
300 byte[] buf = new byte[4096];
301 long bytesRemaining = uncompressedSize;
302 int counter = 0;
303 while (bytesRemaining > 0)
304 {
305 int count = (int) Math.Min(buf.Length, bytesRemaining);
306 count = decompressionStream.Read(buf, 0, count);
307 crcStream.Write(buf, 0, count);
308 bytesRemaining -= count;
309
310 this.fileBytesProcessed += count;
311 this.currentFileBytesProcessed += count;
312 this.currentArchiveBytesProcessed = concatStream.Source.Position;
313
314 if (++counter % 16 == 0) // Report every 64K
315 {
316 this.currentArchiveNumber--;
317 this.OnProgress(ArchiveProgressType.PartialFile);
318 this.currentArchiveNumber++;
319 }
320 }
321 }
322 finally
323 {
324 archiveStream = concatStream.Source;
325 }
326
327 crcStream.Flush();
328
329 if (crcStream.Crc != crc)
330 {
331 throw new ZipException("CRC check failed for file: " + fileName);
332 }
333 }
334 }
335}
336