aboutsummaryrefslogtreecommitdiff
path: root/src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs336
1 files changed, 336 insertions, 0 deletions
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