diff options
Diffstat (limited to '')
-rw-r--r-- | src/dtf/WixToolset.Dtf.Compression.Zip/ZipUnpacker.cs | 336 |
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 | |||
3 | namespace 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 | |||