1 | // ZipEntry.Extract.cs
|
---|
2 | // ------------------------------------------------------------------
|
---|
3 | //
|
---|
4 | // Copyright (c) 2009-2011 Dino Chiesa
|
---|
5 | // All rights reserved.
|
---|
6 | //
|
---|
7 | // This code module is part of DotNetZip, a zipfile class library.
|
---|
8 | //
|
---|
9 | // ------------------------------------------------------------------
|
---|
10 | //
|
---|
11 | // This code is licensed under the Microsoft Public License.
|
---|
12 | // See the file License.txt for the license details.
|
---|
13 | // More info on: http://dotnetzip.codeplex.com
|
---|
14 | //
|
---|
15 | // ------------------------------------------------------------------
|
---|
16 | //
|
---|
17 | // last saved (in emacs):
|
---|
18 | // Time-stamp: <2011-August-06 18:08:21>
|
---|
19 | //
|
---|
20 | // ------------------------------------------------------------------
|
---|
21 | //
|
---|
22 | // This module defines logic for Extract methods on the ZipEntry class.
|
---|
23 | //
|
---|
24 | // ------------------------------------------------------------------
|
---|
25 |
|
---|
26 |
|
---|
27 | using System;
|
---|
28 | using System.IO;
|
---|
29 |
|
---|
30 | namespace OfficeOpenXml.Packaging.Ionic.Zip
|
---|
31 | {
|
---|
32 |
|
---|
33 | internal partial class ZipEntry
|
---|
34 | {
|
---|
35 | /// <summary>
|
---|
36 | /// Extract the entry to the filesystem, starting at the current
|
---|
37 | /// working directory.
|
---|
38 | /// </summary>
|
---|
39 | ///
|
---|
40 | /// <overloads>
|
---|
41 | /// This method has a bunch of overloads! One of them is sure to
|
---|
42 | /// be the right one for you... If you don't like these, check
|
---|
43 | /// out the <c>ExtractWithPassword()</c> methods.
|
---|
44 | /// </overloads>
|
---|
45 | ///
|
---|
46 | /// <seealso cref="ZipEntry.ExtractExistingFile"/>
|
---|
47 | /// <seealso cref="ZipEntry.Extract(ExtractExistingFileAction)"/>
|
---|
48 | ///
|
---|
49 | /// <remarks>
|
---|
50 | ///
|
---|
51 | /// <para>
|
---|
52 | /// This method extracts an entry from a zip file into the current
|
---|
53 | /// working directory. The path of the entry as extracted is the full
|
---|
54 | /// path as specified in the zip archive, relative to the current
|
---|
55 | /// working directory. After the file is extracted successfully, the
|
---|
56 | /// file attributes and timestamps are set.
|
---|
57 | /// </para>
|
---|
58 | ///
|
---|
59 | /// <para>
|
---|
60 | /// The action taken when extraction an entry would overwrite an
|
---|
61 | /// existing file is determined by the <see cref="ExtractExistingFile"
|
---|
62 | /// /> property.
|
---|
63 | /// </para>
|
---|
64 | ///
|
---|
65 | /// <para>
|
---|
66 | /// Within the call to <c>Extract()</c>, the content for the entry is
|
---|
67 | /// written into a filesystem file, and then the last modified time of the
|
---|
68 | /// file is set according to the <see cref="LastModified"/> property on
|
---|
69 | /// the entry. See the remarks the <see cref="LastModified"/> property for
|
---|
70 | /// some details about the last modified time.
|
---|
71 | /// </para>
|
---|
72 | ///
|
---|
73 | /// </remarks>
|
---|
74 | internal void Extract()
|
---|
75 | {
|
---|
76 | InternalExtract(".", null, null);
|
---|
77 | }
|
---|
78 |
|
---|
79 |
|
---|
80 | /// <summary>
|
---|
81 | /// Extract the entry to a file in the filesystem, using the specified
|
---|
82 | /// behavior when extraction would overwrite an existing file.
|
---|
83 | /// </summary>
|
---|
84 | ///
|
---|
85 | /// <remarks>
|
---|
86 | /// <para>
|
---|
87 | /// See the remarks on the <see cref="LastModified"/> property, for some
|
---|
88 | /// details about how the last modified time of the file is set after
|
---|
89 | /// extraction.
|
---|
90 | /// </para>
|
---|
91 | /// </remarks>
|
---|
92 | ///
|
---|
93 | /// <param name="extractExistingFile">
|
---|
94 | /// The action to take if extraction would overwrite an existing file.
|
---|
95 | /// </param>
|
---|
96 | internal void Extract(ExtractExistingFileAction extractExistingFile)
|
---|
97 | {
|
---|
98 | ExtractExistingFile = extractExistingFile;
|
---|
99 | InternalExtract(".", null, null);
|
---|
100 | }
|
---|
101 |
|
---|
102 | /// <summary>
|
---|
103 | /// Extracts the entry to the specified stream.
|
---|
104 | /// </summary>
|
---|
105 | ///
|
---|
106 | /// <remarks>
|
---|
107 | /// <para>
|
---|
108 | /// The caller can specify any write-able stream, for example a <see
|
---|
109 | /// cref="System.IO.FileStream"/>, a <see
|
---|
110 | /// cref="System.IO.MemoryStream"/>, or ASP.NET's
|
---|
111 | /// <c>Response.OutputStream</c>. The content will be decrypted and
|
---|
112 | /// decompressed as necessary. If the entry is encrypted and no password
|
---|
113 | /// is provided, this method will throw.
|
---|
114 | /// </para>
|
---|
115 | /// <para>
|
---|
116 | /// The position on the stream is not reset by this method before it extracts.
|
---|
117 | /// You may want to call stream.Seek() before calling ZipEntry.Extract().
|
---|
118 | /// </para>
|
---|
119 | /// </remarks>
|
---|
120 | ///
|
---|
121 | /// <param name="stream">
|
---|
122 | /// the stream to which the entry should be extracted.
|
---|
123 | /// </param>
|
---|
124 | ///
|
---|
125 | public void Extract(Stream stream)
|
---|
126 | {
|
---|
127 | InternalExtract(null, stream, null);
|
---|
128 | }
|
---|
129 |
|
---|
130 | /// <summary>
|
---|
131 | /// Extract the entry to the filesystem, starting at the specified base
|
---|
132 | /// directory.
|
---|
133 | /// </summary>
|
---|
134 | ///
|
---|
135 | /// <param name="baseDirectory">the pathname of the base directory</param>
|
---|
136 | ///
|
---|
137 | /// <seealso cref="ZipEntry.ExtractExistingFile"/>
|
---|
138 | /// <seealso cref="ZipEntry.Extract(string, ExtractExistingFileAction)"/>
|
---|
139 | ///
|
---|
140 | /// <example>
|
---|
141 | /// This example extracts only the entries in a zip file that are .txt files,
|
---|
142 | /// into a directory called "textfiles".
|
---|
143 | /// <code lang="C#">
|
---|
144 | /// using (ZipFile zip = ZipFile.Read("PackedDocuments.zip"))
|
---|
145 | /// {
|
---|
146 | /// foreach (string s1 in zip.EntryFilenames)
|
---|
147 | /// {
|
---|
148 | /// if (s1.EndsWith(".txt"))
|
---|
149 | /// {
|
---|
150 | /// zip[s1].Extract("textfiles");
|
---|
151 | /// }
|
---|
152 | /// }
|
---|
153 | /// }
|
---|
154 | /// </code>
|
---|
155 | /// <code lang="VB">
|
---|
156 | /// Using zip As ZipFile = ZipFile.Read("PackedDocuments.zip")
|
---|
157 | /// Dim s1 As String
|
---|
158 | /// For Each s1 In zip.EntryFilenames
|
---|
159 | /// If s1.EndsWith(".txt") Then
|
---|
160 | /// zip(s1).Extract("textfiles")
|
---|
161 | /// End If
|
---|
162 | /// Next
|
---|
163 | /// End Using
|
---|
164 | /// </code>
|
---|
165 | /// </example>
|
---|
166 | ///
|
---|
167 | /// <remarks>
|
---|
168 | ///
|
---|
169 | /// <para>
|
---|
170 | /// Using this method, existing entries in the filesystem will not be
|
---|
171 | /// overwritten. If you would like to force the overwrite of existing
|
---|
172 | /// files, see the <see cref="ExtractExistingFile"/> property, or call
|
---|
173 | /// <see cref="Extract(string, ExtractExistingFileAction)"/>.
|
---|
174 | /// </para>
|
---|
175 | ///
|
---|
176 | /// <para>
|
---|
177 | /// See the remarks on the <see cref="LastModified"/> property, for some
|
---|
178 | /// details about how the last modified time of the created file is set.
|
---|
179 | /// </para>
|
---|
180 | /// </remarks>
|
---|
181 | public void Extract(string baseDirectory)
|
---|
182 | {
|
---|
183 | InternalExtract(baseDirectory, null, null);
|
---|
184 | }
|
---|
185 |
|
---|
186 |
|
---|
187 |
|
---|
188 |
|
---|
189 |
|
---|
190 | /// <summary>
|
---|
191 | /// Extract the entry to the filesystem, starting at the specified base
|
---|
192 | /// directory, and using the specified behavior when extraction would
|
---|
193 | /// overwrite an existing file.
|
---|
194 | /// </summary>
|
---|
195 | ///
|
---|
196 | /// <remarks>
|
---|
197 | /// <para>
|
---|
198 | /// See the remarks on the <see cref="LastModified"/> property, for some
|
---|
199 | /// details about how the last modified time of the created file is set.
|
---|
200 | /// </para>
|
---|
201 | /// </remarks>
|
---|
202 | ///
|
---|
203 | /// <example>
|
---|
204 | /// <code lang="C#">
|
---|
205 | /// String sZipPath = "Airborne.zip";
|
---|
206 | /// String sFilePath = "Readme.txt";
|
---|
207 | /// String sRootFolder = "Digado";
|
---|
208 | /// using (ZipFile zip = ZipFile.Read(sZipPath))
|
---|
209 | /// {
|
---|
210 | /// if (zip.EntryFileNames.Contains(sFilePath))
|
---|
211 | /// {
|
---|
212 | /// // use the string indexer on the zip file
|
---|
213 | /// zip[sFileName].Extract(sRootFolder,
|
---|
214 | /// ExtractExistingFileAction.OverwriteSilently);
|
---|
215 | /// }
|
---|
216 | /// }
|
---|
217 | /// </code>
|
---|
218 | ///
|
---|
219 | /// <code lang="VB">
|
---|
220 | /// Dim sZipPath as String = "Airborne.zip"
|
---|
221 | /// Dim sFilePath As String = "Readme.txt"
|
---|
222 | /// Dim sRootFolder As String = "Digado"
|
---|
223 | /// Using zip As ZipFile = ZipFile.Read(sZipPath)
|
---|
224 | /// If zip.EntryFileNames.Contains(sFilePath)
|
---|
225 | /// ' use the string indexer on the zip file
|
---|
226 | /// zip(sFilePath).Extract(sRootFolder, _
|
---|
227 | /// ExtractExistingFileAction.OverwriteSilently)
|
---|
228 | /// End If
|
---|
229 | /// End Using
|
---|
230 | /// </code>
|
---|
231 | /// </example>
|
---|
232 | ///
|
---|
233 | /// <param name="baseDirectory">the pathname of the base directory</param>
|
---|
234 | /// <param name="extractExistingFile">
|
---|
235 | /// The action to take if extraction would overwrite an existing file.
|
---|
236 | /// </param>
|
---|
237 | internal void Extract(string baseDirectory, ExtractExistingFileAction extractExistingFile)
|
---|
238 | {
|
---|
239 | ExtractExistingFile = extractExistingFile;
|
---|
240 | InternalExtract(baseDirectory, null, null);
|
---|
241 | }
|
---|
242 |
|
---|
243 |
|
---|
244 | /// <summary>
|
---|
245 | /// Extract the entry to the filesystem, using the current working directory
|
---|
246 | /// and the specified password.
|
---|
247 | /// </summary>
|
---|
248 | ///
|
---|
249 | /// <overloads>
|
---|
250 | /// This method has a bunch of overloads! One of them is sure to be
|
---|
251 | /// the right one for you...
|
---|
252 | /// </overloads>
|
---|
253 | ///
|
---|
254 | /// <seealso cref="ZipEntry.ExtractExistingFile"/>
|
---|
255 | /// <seealso cref="ZipEntry.ExtractWithPassword(ExtractExistingFileAction, string)"/>
|
---|
256 | ///
|
---|
257 | /// <remarks>
|
---|
258 | ///
|
---|
259 | /// <para>
|
---|
260 | /// Existing entries in the filesystem will not be overwritten. If you
|
---|
261 | /// would like to force the overwrite of existing files, see the <see
|
---|
262 | /// cref="ZipEntry.ExtractExistingFile"/>property, or call
|
---|
263 | /// <see
|
---|
264 | /// cref="ExtractWithPassword(ExtractExistingFileAction,string)"/>.
|
---|
265 | /// </para>
|
---|
266 | ///
|
---|
267 | /// <para>
|
---|
268 | /// See the remarks on the <see cref="LastModified"/> property for some
|
---|
269 | /// details about how the "last modified" time of the created file is
|
---|
270 | /// set.
|
---|
271 | /// </para>
|
---|
272 | /// </remarks>
|
---|
273 | ///
|
---|
274 | /// <example>
|
---|
275 | /// In this example, entries that use encryption are extracted using a
|
---|
276 | /// particular password.
|
---|
277 | /// <code>
|
---|
278 | /// using (var zip = ZipFile.Read(FilePath))
|
---|
279 | /// {
|
---|
280 | /// foreach (ZipEntry e in zip)
|
---|
281 | /// {
|
---|
282 | /// if (e.UsesEncryption)
|
---|
283 | /// e.ExtractWithPassword("Secret!");
|
---|
284 | /// else
|
---|
285 | /// e.Extract();
|
---|
286 | /// }
|
---|
287 | /// }
|
---|
288 | /// </code>
|
---|
289 | /// <code lang="VB">
|
---|
290 | /// Using zip As ZipFile = ZipFile.Read(FilePath)
|
---|
291 | /// Dim e As ZipEntry
|
---|
292 | /// For Each e In zip
|
---|
293 | /// If (e.UsesEncryption)
|
---|
294 | /// e.ExtractWithPassword("Secret!")
|
---|
295 | /// Else
|
---|
296 | /// e.Extract
|
---|
297 | /// End If
|
---|
298 | /// Next
|
---|
299 | /// End Using
|
---|
300 | /// </code>
|
---|
301 | /// </example>
|
---|
302 | /// <param name="password">The Password to use for decrypting the entry.</param>
|
---|
303 | public void ExtractWithPassword(string password)
|
---|
304 | {
|
---|
305 | InternalExtract(".", null, password);
|
---|
306 | }
|
---|
307 |
|
---|
308 | /// <summary>
|
---|
309 | /// Extract the entry to the filesystem, starting at the specified base
|
---|
310 | /// directory, and using the specified password.
|
---|
311 | /// </summary>
|
---|
312 | ///
|
---|
313 | /// <seealso cref="ZipEntry.ExtractExistingFile"/>
|
---|
314 | /// <seealso cref="ZipEntry.ExtractWithPassword(string, ExtractExistingFileAction, string)"/>
|
---|
315 | ///
|
---|
316 | /// <remarks>
|
---|
317 | /// <para>
|
---|
318 | /// Existing entries in the filesystem will not be overwritten. If you
|
---|
319 | /// would like to force the overwrite of existing files, see the <see
|
---|
320 | /// cref="ZipEntry.ExtractExistingFile"/>property, or call
|
---|
321 | /// <see
|
---|
322 | /// cref="ExtractWithPassword(ExtractExistingFileAction,string)"/>.
|
---|
323 | /// </para>
|
---|
324 | ///
|
---|
325 | /// <para>
|
---|
326 | /// See the remarks on the <see cref="LastModified"/> property, for some
|
---|
327 | /// details about how the last modified time of the created file is set.
|
---|
328 | /// </para>
|
---|
329 | /// </remarks>
|
---|
330 | ///
|
---|
331 | /// <param name="baseDirectory">The pathname of the base directory.</param>
|
---|
332 | /// <param name="password">The Password to use for decrypting the entry.</param>
|
---|
333 | public void ExtractWithPassword(string baseDirectory, string password)
|
---|
334 | {
|
---|
335 | InternalExtract(baseDirectory, null, password);
|
---|
336 | }
|
---|
337 |
|
---|
338 |
|
---|
339 |
|
---|
340 |
|
---|
341 | /// <summary>
|
---|
342 | /// Extract the entry to a file in the filesystem, relative to the
|
---|
343 | /// current directory, using the specified behavior when extraction
|
---|
344 | /// would overwrite an existing file.
|
---|
345 | /// </summary>
|
---|
346 | ///
|
---|
347 | /// <remarks>
|
---|
348 | /// <para>
|
---|
349 | /// See the remarks on the <see cref="LastModified"/> property, for some
|
---|
350 | /// details about how the last modified time of the created file is set.
|
---|
351 | /// </para>
|
---|
352 | /// </remarks>
|
---|
353 | ///
|
---|
354 | /// <param name="password">The Password to use for decrypting the entry.</param>
|
---|
355 | ///
|
---|
356 | /// <param name="extractExistingFile">
|
---|
357 | /// The action to take if extraction would overwrite an existing file.
|
---|
358 | /// </param>
|
---|
359 | internal void ExtractWithPassword(ExtractExistingFileAction extractExistingFile, string password)
|
---|
360 | {
|
---|
361 | ExtractExistingFile = extractExistingFile;
|
---|
362 | InternalExtract(".", null, password);
|
---|
363 | }
|
---|
364 |
|
---|
365 |
|
---|
366 |
|
---|
367 | /// <summary>
|
---|
368 | /// Extract the entry to the filesystem, starting at the specified base
|
---|
369 | /// directory, and using the specified behavior when extraction would
|
---|
370 | /// overwrite an existing file.
|
---|
371 | /// </summary>
|
---|
372 | ///
|
---|
373 | /// <remarks>
|
---|
374 | /// See the remarks on the <see cref="LastModified"/> property, for some
|
---|
375 | /// details about how the last modified time of the created file is set.
|
---|
376 | /// </remarks>
|
---|
377 | ///
|
---|
378 | /// <param name="baseDirectory">the pathname of the base directory</param>
|
---|
379 | ///
|
---|
380 | /// <param name="extractExistingFile">The action to take if extraction would
|
---|
381 | /// overwrite an existing file.</param>
|
---|
382 | ///
|
---|
383 | /// <param name="password">The Password to use for decrypting the entry.</param>
|
---|
384 | internal void ExtractWithPassword(string baseDirectory, ExtractExistingFileAction extractExistingFile, string password)
|
---|
385 | {
|
---|
386 | ExtractExistingFile = extractExistingFile;
|
---|
387 | InternalExtract(baseDirectory, null, password);
|
---|
388 | }
|
---|
389 |
|
---|
390 | /// <summary>
|
---|
391 | /// Extracts the entry to the specified stream, using the specified
|
---|
392 | /// Password. For example, the caller could extract to Console.Out, or
|
---|
393 | /// to a MemoryStream.
|
---|
394 | /// </summary>
|
---|
395 | ///
|
---|
396 | /// <remarks>
|
---|
397 | /// <para>
|
---|
398 | /// The caller can specify any write-able stream, for example a <see
|
---|
399 | /// cref="System.IO.FileStream"/>, a <see
|
---|
400 | /// cref="System.IO.MemoryStream"/>, or ASP.NET's
|
---|
401 | /// <c>Response.OutputStream</c>. The content will be decrypted and
|
---|
402 | /// decompressed as necessary. If the entry is encrypted and no password
|
---|
403 | /// is provided, this method will throw.
|
---|
404 | /// </para>
|
---|
405 | /// <para>
|
---|
406 | /// The position on the stream is not reset by this method before it extracts.
|
---|
407 | /// You may want to call stream.Seek() before calling ZipEntry.Extract().
|
---|
408 | /// </para>
|
---|
409 | /// </remarks>
|
---|
410 | ///
|
---|
411 | ///
|
---|
412 | /// <param name="stream">
|
---|
413 | /// the stream to which the entry should be extracted.
|
---|
414 | /// </param>
|
---|
415 | /// <param name="password">
|
---|
416 | /// The password to use for decrypting the entry.
|
---|
417 | /// </param>
|
---|
418 | public void ExtractWithPassword(Stream stream, string password)
|
---|
419 | {
|
---|
420 | InternalExtract(null, stream, password);
|
---|
421 | }
|
---|
422 |
|
---|
423 |
|
---|
424 | /// <summary>
|
---|
425 | /// Opens a readable stream corresponding to the zip entry in the
|
---|
426 | /// archive. The stream decompresses and decrypts as necessary, as it
|
---|
427 | /// is read.
|
---|
428 | /// </summary>
|
---|
429 | ///
|
---|
430 | /// <remarks>
|
---|
431 | ///
|
---|
432 | /// <para>
|
---|
433 | /// DotNetZip offers a variety of ways to extract entries from a zip
|
---|
434 | /// file. This method allows an application to extract an entry by
|
---|
435 | /// reading a <see cref="System.IO.Stream"/>.
|
---|
436 | /// </para>
|
---|
437 | ///
|
---|
438 | /// <para>
|
---|
439 | /// The return value is of type <see
|
---|
440 | /// cref="Ionic.Crc.CrcCalculatorStream"/>. Use it as you would any
|
---|
441 | /// stream for reading. When an application calls <see
|
---|
442 | /// cref="Stream.Read(byte[], int, int)"/> on that stream, it will
|
---|
443 | /// receive data from the zip entry that is decrypted and decompressed
|
---|
444 | /// as necessary.
|
---|
445 | /// </para>
|
---|
446 | ///
|
---|
447 | /// <para>
|
---|
448 | /// <c>CrcCalculatorStream</c> adds one additional feature: it keeps a
|
---|
449 | /// CRC32 checksum on the bytes of the stream as it is read. The CRC
|
---|
450 | /// value is available in the <see
|
---|
451 | /// cref="Ionic.Crc.CrcCalculatorStream.Crc"/> property on the
|
---|
452 | /// <c>CrcCalculatorStream</c>. When the read is complete, your
|
---|
453 | /// application
|
---|
454 | /// <em>should</em> check this CRC against the <see cref="ZipEntry.Crc"/>
|
---|
455 | /// property on the <c>ZipEntry</c> to validate the content of the
|
---|
456 | /// ZipEntry. You don't have to validate the entry using the CRC, but
|
---|
457 | /// you should, to verify integrity. Check the example for how to do
|
---|
458 | /// this.
|
---|
459 | /// </para>
|
---|
460 | ///
|
---|
461 | /// <para>
|
---|
462 | /// If the entry is protected with a password, then you need to provide
|
---|
463 | /// a password prior to calling <see cref="OpenReader()"/>, either by
|
---|
464 | /// setting the <see cref="Password"/> property on the entry, or the
|
---|
465 | /// <see cref="ZipFile.Password"/> property on the <c>ZipFile</c>
|
---|
466 | /// itself. Or, you can use <see cref="OpenReader(String)" />, the
|
---|
467 | /// overload of OpenReader that accepts a password parameter.
|
---|
468 | /// </para>
|
---|
469 | ///
|
---|
470 | /// <para>
|
---|
471 | /// If you want to extract entry data into a write-able stream that is
|
---|
472 | /// already opened, like a <see cref="System.IO.FileStream"/>, do not
|
---|
473 | /// use this method. Instead, use <see cref="Extract(Stream)"/>.
|
---|
474 | /// </para>
|
---|
475 | ///
|
---|
476 | /// <para>
|
---|
477 | /// Your application may use only one stream created by OpenReader() at
|
---|
478 | /// a time, and you should not call other Extract methods before
|
---|
479 | /// completing your reads on a stream obtained from OpenReader(). This
|
---|
480 | /// is because there is really only one source stream for the compressed
|
---|
481 | /// content. A call to OpenReader() seeks in the source stream, to the
|
---|
482 | /// beginning of the compressed content. A subsequent call to
|
---|
483 | /// OpenReader() on a different entry will seek to a different position
|
---|
484 | /// in the source stream, as will a call to Extract() or one of its
|
---|
485 | /// overloads. This will corrupt the state for the decompressing stream
|
---|
486 | /// from the original call to OpenReader().
|
---|
487 | /// </para>
|
---|
488 | ///
|
---|
489 | /// <para>
|
---|
490 | /// The <c>OpenReader()</c> method works only when the ZipEntry is
|
---|
491 | /// obtained from an instance of <c>ZipFile</c>. This method will throw
|
---|
492 | /// an exception if the ZipEntry is obtained from a ZipInputStream.
|
---|
493 | /// </para>
|
---|
494 | /// </remarks>
|
---|
495 | ///
|
---|
496 | /// <example>
|
---|
497 | /// This example shows how to open a zip archive, then read in a named
|
---|
498 | /// entry via a stream. After the read loop is complete, the code
|
---|
499 | /// compares the calculated during the read loop with the expected CRC
|
---|
500 | /// on the <c>ZipEntry</c>, to verify the extraction.
|
---|
501 | /// <code>
|
---|
502 | /// using (ZipFile zip = new ZipFile(ZipFileToRead))
|
---|
503 | /// {
|
---|
504 | /// ZipEntry e1= zip["Elevation.mp3"];
|
---|
505 | /// using (Ionic.Zlib.CrcCalculatorStream s = e1.OpenReader())
|
---|
506 | /// {
|
---|
507 | /// byte[] buffer = new byte[4096];
|
---|
508 | /// int n, totalBytesRead= 0;
|
---|
509 | /// do {
|
---|
510 | /// n = s.Read(buffer,0, buffer.Length);
|
---|
511 | /// totalBytesRead+=n;
|
---|
512 | /// } while (n>0);
|
---|
513 | /// if (s.Crc32 != e1.Crc32)
|
---|
514 | /// throw new Exception(string.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc32, e1.Crc32));
|
---|
515 | /// if (totalBytesRead != e1.UncompressedSize)
|
---|
516 | /// throw new Exception(string.Format("We read an unexpected number of bytes. ({0}!={1})", totalBytesRead, e1.UncompressedSize));
|
---|
517 | /// }
|
---|
518 | /// }
|
---|
519 | /// </code>
|
---|
520 | /// <code lang="VB">
|
---|
521 | /// Using zip As New ZipFile(ZipFileToRead)
|
---|
522 | /// Dim e1 As ZipEntry = zip.Item("Elevation.mp3")
|
---|
523 | /// Using s As Ionic.Zlib.CrcCalculatorStream = e1.OpenReader
|
---|
524 | /// Dim n As Integer
|
---|
525 | /// Dim buffer As Byte() = New Byte(4096) {}
|
---|
526 | /// Dim totalBytesRead As Integer = 0
|
---|
527 | /// Do
|
---|
528 | /// n = s.Read(buffer, 0, buffer.Length)
|
---|
529 | /// totalBytesRead = (totalBytesRead + n)
|
---|
530 | /// Loop While (n > 0)
|
---|
531 | /// If (s.Crc32 <> e1.Crc32) Then
|
---|
532 | /// Throw New Exception(String.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc32, e1.Crc32))
|
---|
533 | /// End If
|
---|
534 | /// If (totalBytesRead <> e1.UncompressedSize) Then
|
---|
535 | /// Throw New Exception(String.Format("We read an unexpected number of bytes. ({0}!={1})", totalBytesRead, e1.UncompressedSize))
|
---|
536 | /// End If
|
---|
537 | /// End Using
|
---|
538 | /// End Using
|
---|
539 | /// </code>
|
---|
540 | /// </example>
|
---|
541 | /// <seealso cref="ZipEntry.Extract(System.IO.Stream)"/>
|
---|
542 | /// <returns>The Stream for reading.</returns>
|
---|
543 | internal Ionic.Crc.CrcCalculatorStream OpenReader()
|
---|
544 | {
|
---|
545 | // workitem 10923
|
---|
546 | if (_container.ZipFile == null)
|
---|
547 | throw new InvalidOperationException("Use OpenReader() only with ZipFile.");
|
---|
548 |
|
---|
549 | // use the entry password if it is non-null,
|
---|
550 | // else use the zipfile password, which is possibly null
|
---|
551 | return InternalOpenReader(this._Password ?? this._container.Password);
|
---|
552 | }
|
---|
553 |
|
---|
554 | /// <summary>
|
---|
555 | /// Opens a readable stream for an encrypted zip entry in the archive.
|
---|
556 | /// The stream decompresses and decrypts as necessary, as it is read.
|
---|
557 | /// </summary>
|
---|
558 | ///
|
---|
559 | /// <remarks>
|
---|
560 | /// <para>
|
---|
561 | /// See the documentation on the <see cref="OpenReader()"/> method for
|
---|
562 | /// full details. This overload allows the application to specify a
|
---|
563 | /// password for the <c>ZipEntry</c> to be read.
|
---|
564 | /// </para>
|
---|
565 | /// </remarks>
|
---|
566 | ///
|
---|
567 | /// <param name="password">The password to use for decrypting the entry.</param>
|
---|
568 | /// <returns>The Stream for reading.</returns>
|
---|
569 | internal Ionic.Crc.CrcCalculatorStream OpenReader(string password)
|
---|
570 | {
|
---|
571 | // workitem 10923
|
---|
572 | if (_container.ZipFile == null)
|
---|
573 | throw new InvalidOperationException("Use OpenReader() only with ZipFile.");
|
---|
574 |
|
---|
575 | return InternalOpenReader(password);
|
---|
576 | }
|
---|
577 |
|
---|
578 |
|
---|
579 |
|
---|
580 | internal Ionic.Crc.CrcCalculatorStream InternalOpenReader(string password)
|
---|
581 | {
|
---|
582 | ValidateCompression();
|
---|
583 | ValidateEncryption();
|
---|
584 | SetupCryptoForExtract(password);
|
---|
585 |
|
---|
586 | // workitem 7958
|
---|
587 | if (this._Source != ZipEntrySource.ZipFile)
|
---|
588 | throw new BadStateException("You must call ZipFile.Save before calling OpenReader");
|
---|
589 |
|
---|
590 | // LeftToRead is a count of bytes remaining to be read (out)
|
---|
591 | // from the stream AFTER decompression and decryption.
|
---|
592 | // It is the uncompressed size, unless ... there is no compression in which
|
---|
593 | // case ...? :< I'm not sure why it's not always UncompressedSize
|
---|
594 | Int64 LeftToRead = (_CompressionMethod_FromZipFile == (short)CompressionMethod.None)
|
---|
595 | ? this._CompressedFileDataSize
|
---|
596 | : this.UncompressedSize;
|
---|
597 |
|
---|
598 | Stream input = this.ArchiveStream;
|
---|
599 |
|
---|
600 | this.ArchiveStream.Seek(this.FileDataPosition, SeekOrigin.Begin);
|
---|
601 | // workitem 10178
|
---|
602 | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
|
---|
603 |
|
---|
604 | _inputDecryptorStream = GetExtractDecryptor(input);
|
---|
605 | Stream input3 = GetExtractDecompressor(_inputDecryptorStream);
|
---|
606 |
|
---|
607 | return new Ionic.Crc.CrcCalculatorStream(input3, LeftToRead);
|
---|
608 | }
|
---|
609 |
|
---|
610 |
|
---|
611 |
|
---|
612 | private void OnExtractProgress(Int64 bytesWritten, Int64 totalBytesToWrite)
|
---|
613 | {
|
---|
614 | if (_container.ZipFile != null)
|
---|
615 | _ioOperationCanceled = _container.ZipFile.OnExtractBlock(this, bytesWritten, totalBytesToWrite);
|
---|
616 | }
|
---|
617 |
|
---|
618 |
|
---|
619 | private void OnBeforeExtract(string path)
|
---|
620 | {
|
---|
621 | // When in the context of a ZipFile.ExtractAll, the events are generated from
|
---|
622 | // the ZipFile method, not from within the ZipEntry instance. (why?)
|
---|
623 | // Therefore we suppress the events originating from the ZipEntry method.
|
---|
624 | if (_container.ZipFile != null)
|
---|
625 | {
|
---|
626 | if (!_container.ZipFile._inExtractAll)
|
---|
627 | {
|
---|
628 | _ioOperationCanceled = _container.ZipFile.OnSingleEntryExtract(this, path, true);
|
---|
629 | }
|
---|
630 | }
|
---|
631 | }
|
---|
632 |
|
---|
633 | private void OnAfterExtract(string path)
|
---|
634 | {
|
---|
635 | // When in the context of a ZipFile.ExtractAll, the events are generated from
|
---|
636 | // the ZipFile method, not from within the ZipEntry instance. (why?)
|
---|
637 | // Therefore we suppress the events originating from the ZipEntry method.
|
---|
638 | if (_container.ZipFile != null)
|
---|
639 | {
|
---|
640 | if (!_container.ZipFile._inExtractAll)
|
---|
641 | {
|
---|
642 | _container.ZipFile.OnSingleEntryExtract(this, path, false);
|
---|
643 | }
|
---|
644 | }
|
---|
645 | }
|
---|
646 |
|
---|
647 | private void OnExtractExisting(string path)
|
---|
648 | {
|
---|
649 | if (_container.ZipFile != null)
|
---|
650 | _ioOperationCanceled = _container.ZipFile.OnExtractExisting(this, path);
|
---|
651 | }
|
---|
652 |
|
---|
653 | private static void ReallyDelete(string fileName)
|
---|
654 | {
|
---|
655 | // workitem 7881
|
---|
656 | // reset ReadOnly bit if necessary
|
---|
657 | #if NETCF
|
---|
658 | if ( (NetCfFile.GetAttributes(fileName) & (uint)FileAttributes.ReadOnly) == (uint)FileAttributes.ReadOnly)
|
---|
659 | NetCfFile.SetAttributes(fileName, (uint)FileAttributes.Normal);
|
---|
660 | #elif SILVERLIGHT
|
---|
661 | #else
|
---|
662 | if ((File.GetAttributes(fileName) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
|
---|
663 | File.SetAttributes(fileName, FileAttributes.Normal);
|
---|
664 | #endif
|
---|
665 | File.Delete(fileName);
|
---|
666 | }
|
---|
667 |
|
---|
668 |
|
---|
669 | private void WriteStatus(string format, params Object[] args)
|
---|
670 | {
|
---|
671 | if (_container.ZipFile != null && _container.ZipFile.Verbose) _container.ZipFile.StatusMessageTextWriter.WriteLine(format, args);
|
---|
672 | }
|
---|
673 |
|
---|
674 |
|
---|
675 | // Pass in either basedir or s, but not both.
|
---|
676 | // In other words, you can extract to a stream or to a directory (filesystem), but not both!
|
---|
677 | // The Password param is required for encrypted entries.
|
---|
678 | private void InternalExtract(string baseDir, Stream outstream, string password)
|
---|
679 | {
|
---|
680 | // workitem 7958
|
---|
681 | if (_container == null)
|
---|
682 | throw new BadStateException("This entry is an orphan");
|
---|
683 |
|
---|
684 | // workitem 10355
|
---|
685 | if (_container.ZipFile == null)
|
---|
686 | throw new InvalidOperationException("Use Extract() only with ZipFile.");
|
---|
687 |
|
---|
688 | _container.ZipFile.Reset(false);
|
---|
689 |
|
---|
690 | if (this._Source != ZipEntrySource.ZipFile)
|
---|
691 | throw new BadStateException("You must call ZipFile.Save before calling any Extract method");
|
---|
692 |
|
---|
693 | OnBeforeExtract(baseDir);
|
---|
694 | _ioOperationCanceled = false;
|
---|
695 | string targetFileName = null;
|
---|
696 | Stream output = null;
|
---|
697 | bool fileExistsBeforeExtraction = false;
|
---|
698 | bool checkLaterForResetDirTimes = false;
|
---|
699 | try
|
---|
700 | {
|
---|
701 | ValidateCompression();
|
---|
702 | ValidateEncryption();
|
---|
703 |
|
---|
704 | if (ValidateOutput(baseDir, outstream, out targetFileName))
|
---|
705 | {
|
---|
706 | WriteStatus("extract dir {0}...", targetFileName);
|
---|
707 | // if true, then the entry was a directory and has been created.
|
---|
708 | // We need to fire the Extract Event.
|
---|
709 | OnAfterExtract(baseDir);
|
---|
710 | return;
|
---|
711 | }
|
---|
712 |
|
---|
713 | // workitem 10639
|
---|
714 | // do we want to extract to a regular filesystem file?
|
---|
715 | if (targetFileName != null)
|
---|
716 | {
|
---|
717 | // Check for extracting to a previously extant file. The user
|
---|
718 | // can specify bejavior for that case: overwrite, don't
|
---|
719 | // overwrite, and throw. Also, if the file exists prior to
|
---|
720 | // extraction, it affects exception handling: whether to delete
|
---|
721 | // the target of extraction or not. This check needs to be done
|
---|
722 | // before the password check is done, because password check may
|
---|
723 | // throw a BadPasswordException, which triggers the catch,
|
---|
724 | // wherein the extant file may be deleted if not flagged as
|
---|
725 | // pre-existing.
|
---|
726 | if (File.Exists(targetFileName))
|
---|
727 | {
|
---|
728 | fileExistsBeforeExtraction = true;
|
---|
729 | int rc = CheckExtractExistingFile(baseDir, targetFileName);
|
---|
730 | if (rc == 2) goto ExitTry; // cancel
|
---|
731 | if (rc == 1) return; // do not overwrite
|
---|
732 | }
|
---|
733 | }
|
---|
734 |
|
---|
735 | // If no password explicitly specified, use the password on the entry itself,
|
---|
736 | // or on the zipfile itself.
|
---|
737 | string p = password ?? this._Password ?? this._container.Password;
|
---|
738 | if (_Encryption_FromZipFile != EncryptionAlgorithm.None)
|
---|
739 | {
|
---|
740 | if (p == null)
|
---|
741 | throw new BadPasswordException();
|
---|
742 | SetupCryptoForExtract(p);
|
---|
743 | }
|
---|
744 |
|
---|
745 |
|
---|
746 | // set up the output stream
|
---|
747 | if (targetFileName != null)
|
---|
748 | {
|
---|
749 | WriteStatus("extract file {0}...", targetFileName);
|
---|
750 | targetFileName += ".tmp";
|
---|
751 | var dirName = Path.GetDirectoryName(targetFileName);
|
---|
752 | // ensure the target path exists
|
---|
753 | if (!Directory.Exists(dirName))
|
---|
754 | {
|
---|
755 | // we create the directory here, but we do not set the
|
---|
756 | // create/modified/accessed times on it because it is being
|
---|
757 | // created implicitly, not explcitly. There's no entry in the
|
---|
758 | // zip archive for the directory.
|
---|
759 | Directory.CreateDirectory(dirName);
|
---|
760 | }
|
---|
761 | else
|
---|
762 | {
|
---|
763 | // workitem 8264
|
---|
764 | if (_container.ZipFile != null)
|
---|
765 | checkLaterForResetDirTimes = _container.ZipFile._inExtractAll;
|
---|
766 | }
|
---|
767 |
|
---|
768 | // File.Create(CreateNew) will overwrite any existing file.
|
---|
769 | output = new FileStream(targetFileName, FileMode.CreateNew);
|
---|
770 | }
|
---|
771 | else
|
---|
772 | {
|
---|
773 | WriteStatus("extract entry {0} to stream...", FileName);
|
---|
774 | output = outstream;
|
---|
775 | }
|
---|
776 |
|
---|
777 |
|
---|
778 | if (_ioOperationCanceled)
|
---|
779 | goto ExitTry;
|
---|
780 |
|
---|
781 | Int32 ActualCrc32 = ExtractOne(output);
|
---|
782 |
|
---|
783 | if (_ioOperationCanceled)
|
---|
784 | goto ExitTry;
|
---|
785 |
|
---|
786 | VerifyCrcAfterExtract(ActualCrc32);
|
---|
787 |
|
---|
788 | if (targetFileName != null)
|
---|
789 | {
|
---|
790 | output.Close();
|
---|
791 | output = null;
|
---|
792 |
|
---|
793 | // workitem 10639
|
---|
794 | // move file to permanent home
|
---|
795 | string tmpName = targetFileName;
|
---|
796 | string zombie = null;
|
---|
797 | targetFileName = tmpName.Substring(0,tmpName.Length-4);
|
---|
798 |
|
---|
799 | if (fileExistsBeforeExtraction)
|
---|
800 | {
|
---|
801 | // An AV program may hold the target file open, which means
|
---|
802 | // File.Delete() will succeed, though the actual deletion
|
---|
803 | // remains pending. This will prevent a subsequent
|
---|
804 | // File.Move() from succeeding. To avoid this, when the file
|
---|
805 | // already exists, we need to replace it in 3 steps:
|
---|
806 | //
|
---|
807 | // 1. rename the existing file to a zombie name;
|
---|
808 | // 2. rename the extracted file from the temp name to
|
---|
809 | // the target file name;
|
---|
810 | // 3. delete the zombie.
|
---|
811 | //
|
---|
812 | zombie = targetFileName + ".PendingOverwrite";
|
---|
813 | File.Move(targetFileName, zombie);
|
---|
814 | }
|
---|
815 |
|
---|
816 | File.Move(tmpName, targetFileName);
|
---|
817 | _SetTimes(targetFileName, true);
|
---|
818 |
|
---|
819 | if (zombie != null && File.Exists(zombie))
|
---|
820 | ReallyDelete(zombie);
|
---|
821 |
|
---|
822 | // workitem 8264
|
---|
823 | if (checkLaterForResetDirTimes)
|
---|
824 | {
|
---|
825 | // This is sort of a hack. What I do here is set the time on
|
---|
826 | // the parent directory, every time a file is extracted into
|
---|
827 | // it. If there is a directory with 1000 files, then I set
|
---|
828 | // the time on the dir, 1000 times. This allows the directory
|
---|
829 | // to have times that reflect the actual time on the entry in
|
---|
830 | // the zip archive.
|
---|
831 |
|
---|
832 | // String.Contains is not available on .NET CF 2.0
|
---|
833 | if (this.FileName.IndexOf('/') != -1)
|
---|
834 | {
|
---|
835 | string dirname = Path.GetDirectoryName(this.FileName);
|
---|
836 | if (this._container.ZipFile[dirname] == null)
|
---|
837 | {
|
---|
838 | _SetTimes(Path.GetDirectoryName(targetFileName), false);
|
---|
839 | }
|
---|
840 | }
|
---|
841 | }
|
---|
842 |
|
---|
843 | #if NETCF
|
---|
844 | // workitem 7926 - version made by OS can be zero or 10
|
---|
845 | if ((_VersionMadeBy & 0xFF00) == 0x0a00 || (_VersionMadeBy & 0xFF00) == 0x0000)
|
---|
846 | NetCfFile.SetAttributes(targetFileName, (uint)_ExternalFileAttrs);
|
---|
847 |
|
---|
848 | #else
|
---|
849 | // workitem 7071
|
---|
850 | //
|
---|
851 | // We can only apply attributes if they are relevant to the NTFS
|
---|
852 | // OS. Must do this LAST because it may involve a ReadOnly bit,
|
---|
853 | // which would prevent us from setting the time, etc.
|
---|
854 | //
|
---|
855 | // workitem 7926 - version made by OS can be zero (FAT) or 10
|
---|
856 | // (NTFS)
|
---|
857 | if ((_VersionMadeBy & 0xFF00) == 0x0a00 || (_VersionMadeBy & 0xFF00) == 0x0000)
|
---|
858 | File.SetAttributes(targetFileName, (FileAttributes)_ExternalFileAttrs);
|
---|
859 | #endif
|
---|
860 | }
|
---|
861 |
|
---|
862 | OnAfterExtract(baseDir);
|
---|
863 |
|
---|
864 | ExitTry: ;
|
---|
865 | }
|
---|
866 | catch (Exception)
|
---|
867 | {
|
---|
868 | _ioOperationCanceled = true;
|
---|
869 | throw;
|
---|
870 | }
|
---|
871 | finally
|
---|
872 | {
|
---|
873 | if (_ioOperationCanceled)
|
---|
874 | {
|
---|
875 | if (targetFileName != null)
|
---|
876 | {
|
---|
877 | try
|
---|
878 | {
|
---|
879 | if (output != null) output.Close();
|
---|
880 | // An exception has occurred. If the file exists, check
|
---|
881 | // to see if it existed before we tried extracting. If
|
---|
882 | // it did not, attempt to remove the target file. There
|
---|
883 | // is a small possibility that the existing file has
|
---|
884 | // been extracted successfully, overwriting a previously
|
---|
885 | // existing file, and an exception was thrown after that
|
---|
886 | // but before final completion (setting times, etc). In
|
---|
887 | // that case the file will remain, even though some
|
---|
888 | // error occurred. Nothing to be done about it.
|
---|
889 | if (File.Exists(targetFileName) && !fileExistsBeforeExtraction)
|
---|
890 | File.Delete(targetFileName);
|
---|
891 |
|
---|
892 | }
|
---|
893 | finally { }
|
---|
894 | }
|
---|
895 | }
|
---|
896 | }
|
---|
897 | }
|
---|
898 |
|
---|
899 |
|
---|
900 | #if NOT
|
---|
901 | internal void CalcWinZipAesMac(Stream input)
|
---|
902 | {
|
---|
903 | if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
|
---|
904 | Encryption == EncryptionAlgorithm.WinZipAes256)
|
---|
905 | {
|
---|
906 | if (input is WinZipAesCipherStream)
|
---|
907 | wzs = input as WinZipAesCipherStream;
|
---|
908 |
|
---|
909 | else if (input is Ionic.Zlib.CrcCalculatorStream)
|
---|
910 | {
|
---|
911 | xxx;
|
---|
912 | }
|
---|
913 |
|
---|
914 | }
|
---|
915 | }
|
---|
916 | #endif
|
---|
917 |
|
---|
918 |
|
---|
919 | internal void VerifyCrcAfterExtract(Int32 actualCrc32)
|
---|
920 | {
|
---|
921 |
|
---|
922 | #if AESCRYPTO
|
---|
923 | // After extracting, Validate the CRC32
|
---|
924 | if (actualCrc32 != _Crc32)
|
---|
925 | {
|
---|
926 | // CRC is not meaningful with WinZipAES and AES method 2 (AE-2)
|
---|
927 | if ((Encryption != EncryptionAlgorithm.WinZipAes128 &&
|
---|
928 | Encryption != EncryptionAlgorithm.WinZipAes256)
|
---|
929 | || _WinZipAesMethod != 0x02)
|
---|
930 | throw new BadCrcException("CRC error: the file being extracted appears to be corrupted. " +
|
---|
931 | String.Format("Expected 0x{0:X8}, Actual 0x{1:X8}", _Crc32, actualCrc32));
|
---|
932 | }
|
---|
933 |
|
---|
934 | // ignore MAC if the size of the file is zero
|
---|
935 | if (this.UncompressedSize == 0)
|
---|
936 | return;
|
---|
937 |
|
---|
938 | // calculate the MAC
|
---|
939 | if (Encryption == EncryptionAlgorithm.WinZipAes128 ||
|
---|
940 | Encryption == EncryptionAlgorithm.WinZipAes256)
|
---|
941 | {
|
---|
942 | WinZipAesCipherStream wzs = _inputDecryptorStream as WinZipAesCipherStream;
|
---|
943 | _aesCrypto_forExtract.CalculatedMac = wzs.FinalAuthentication;
|
---|
944 |
|
---|
945 | _aesCrypto_forExtract.ReadAndVerifyMac(this.ArchiveStream); // throws if MAC is bad
|
---|
946 | // side effect: advances file position.
|
---|
947 | }
|
---|
948 |
|
---|
949 |
|
---|
950 |
|
---|
951 |
|
---|
952 | #else
|
---|
953 | if (actualCrc32 != _Crc32)
|
---|
954 | throw new BadCrcException("CRC error: the file being extracted appears to be corrupted. " +
|
---|
955 | String.Format("Expected 0x{0:X8}, Actual 0x{1:X8}", _Crc32, actualCrc32));
|
---|
956 | #endif
|
---|
957 | }
|
---|
958 |
|
---|
959 |
|
---|
960 |
|
---|
961 |
|
---|
962 | private int CheckExtractExistingFile(string baseDir, string targetFileName)
|
---|
963 | {
|
---|
964 | int loop = 0;
|
---|
965 | // returns: 0 == extract, 1 = don't, 2 = cancel
|
---|
966 | do
|
---|
967 | {
|
---|
968 | switch (ExtractExistingFile)
|
---|
969 | {
|
---|
970 | case ExtractExistingFileAction.OverwriteSilently:
|
---|
971 | WriteStatus("the file {0} exists; will overwrite it...", targetFileName);
|
---|
972 | return 0;
|
---|
973 |
|
---|
974 | case ExtractExistingFileAction.DoNotOverwrite:
|
---|
975 | WriteStatus("the file {0} exists; not extracting entry...", FileName);
|
---|
976 | OnAfterExtract(baseDir);
|
---|
977 | return 1;
|
---|
978 |
|
---|
979 | case ExtractExistingFileAction.InvokeExtractProgressEvent:
|
---|
980 | if (loop>0)
|
---|
981 | throw new ZipException(String.Format("The file {0} already exists.", targetFileName));
|
---|
982 | OnExtractExisting(baseDir);
|
---|
983 | if (_ioOperationCanceled)
|
---|
984 | return 2;
|
---|
985 |
|
---|
986 | // loop around
|
---|
987 | break;
|
---|
988 |
|
---|
989 | case ExtractExistingFileAction.Throw:
|
---|
990 | default:
|
---|
991 | throw new ZipException(String.Format("The file {0} already exists.", targetFileName));
|
---|
992 | }
|
---|
993 | loop++;
|
---|
994 | }
|
---|
995 | while (true);
|
---|
996 | }
|
---|
997 |
|
---|
998 |
|
---|
999 |
|
---|
1000 |
|
---|
1001 | private void _CheckRead(int nbytes)
|
---|
1002 | {
|
---|
1003 | if (nbytes == 0)
|
---|
1004 | throw new BadReadException(String.Format("bad read of entry {0} from compressed archive.",
|
---|
1005 | this.FileName));
|
---|
1006 | }
|
---|
1007 |
|
---|
1008 |
|
---|
1009 | private Stream _inputDecryptorStream;
|
---|
1010 |
|
---|
1011 | private Int32 ExtractOne(Stream output)
|
---|
1012 | {
|
---|
1013 | Int32 CrcResult = 0;
|
---|
1014 | Stream input = this.ArchiveStream;
|
---|
1015 |
|
---|
1016 | try
|
---|
1017 | {
|
---|
1018 | // change for workitem 8098
|
---|
1019 | input.Seek(this.FileDataPosition, SeekOrigin.Begin);
|
---|
1020 | // workitem 10178
|
---|
1021 | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(input);
|
---|
1022 |
|
---|
1023 | byte[] bytes = new byte[BufferSize];
|
---|
1024 |
|
---|
1025 | // The extraction process varies depending on how the entry was
|
---|
1026 | // stored. It could have been encrypted, and it coould have
|
---|
1027 | // been compressed, or both, or neither. So we need to check
|
---|
1028 | // both the encryption flag and the compression flag, and take
|
---|
1029 | // the proper action in all cases.
|
---|
1030 |
|
---|
1031 | Int64 LeftToRead = (_CompressionMethod_FromZipFile != (short)CompressionMethod.None)
|
---|
1032 | ? this.UncompressedSize
|
---|
1033 | : this._CompressedFileDataSize;
|
---|
1034 |
|
---|
1035 | // Get a stream that either decrypts or not.
|
---|
1036 | _inputDecryptorStream = GetExtractDecryptor(input);
|
---|
1037 |
|
---|
1038 | Stream input3 = GetExtractDecompressor( _inputDecryptorStream );
|
---|
1039 |
|
---|
1040 | Int64 bytesWritten = 0;
|
---|
1041 | // As we read, we maybe decrypt, and then we maybe decompress. Then we write.
|
---|
1042 | using (var s1 = new Ionic.Crc.CrcCalculatorStream(input3))
|
---|
1043 | {
|
---|
1044 | while (LeftToRead > 0)
|
---|
1045 | {
|
---|
1046 | //Console.WriteLine("ExtractOne: LeftToRead {0}", LeftToRead);
|
---|
1047 |
|
---|
1048 | // Casting LeftToRead down to an int is ok here in the else clause,
|
---|
1049 | // because that only happens when it is less than bytes.Length,
|
---|
1050 | // which is much less than MAX_INT.
|
---|
1051 | int len = (LeftToRead > bytes.Length) ? bytes.Length : (int)LeftToRead;
|
---|
1052 | int n = s1.Read(bytes, 0, len);
|
---|
1053 |
|
---|
1054 | // must check data read - essential for detecting corrupt zip files
|
---|
1055 | _CheckRead(n);
|
---|
1056 |
|
---|
1057 | output.Write(bytes, 0, n);
|
---|
1058 | LeftToRead -= n;
|
---|
1059 | bytesWritten += n;
|
---|
1060 |
|
---|
1061 | // fire the progress event, check for cancels
|
---|
1062 | OnExtractProgress(bytesWritten, UncompressedSize);
|
---|
1063 | if (_ioOperationCanceled)
|
---|
1064 | {
|
---|
1065 | break;
|
---|
1066 | }
|
---|
1067 | }
|
---|
1068 |
|
---|
1069 | CrcResult = s1.Crc;
|
---|
1070 | }
|
---|
1071 | }
|
---|
1072 | finally
|
---|
1073 | {
|
---|
1074 | var zss = input as ZipSegmentedStream;
|
---|
1075 | if (zss != null)
|
---|
1076 | {
|
---|
1077 | #if NETCF
|
---|
1078 | zss.Close();
|
---|
1079 | #else
|
---|
1080 | // need to dispose it
|
---|
1081 | zss.Dispose();
|
---|
1082 | #endif
|
---|
1083 | _archiveStream = null;
|
---|
1084 | }
|
---|
1085 | }
|
---|
1086 |
|
---|
1087 | return CrcResult;
|
---|
1088 | }
|
---|
1089 |
|
---|
1090 |
|
---|
1091 |
|
---|
1092 | internal Stream GetExtractDecompressor(Stream input2)
|
---|
1093 | {
|
---|
1094 | // get a stream that either decompresses or not.
|
---|
1095 | switch (_CompressionMethod_FromZipFile)
|
---|
1096 | {
|
---|
1097 | case (short)CompressionMethod.None:
|
---|
1098 | return input2;
|
---|
1099 | case (short)CompressionMethod.Deflate:
|
---|
1100 | return new Ionic.Zlib.DeflateStream(input2, Ionic.Zlib.CompressionMode.Decompress, true);
|
---|
1101 | #if BZIP
|
---|
1102 | case (short)CompressionMethod.BZip2:
|
---|
1103 | return new Ionic.BZip2.BZip2InputStream(input2, true);
|
---|
1104 | #endif
|
---|
1105 | }
|
---|
1106 |
|
---|
1107 | return null;
|
---|
1108 | }
|
---|
1109 |
|
---|
1110 |
|
---|
1111 |
|
---|
1112 | internal Stream GetExtractDecryptor(Stream input)
|
---|
1113 | {
|
---|
1114 | Stream input2 = null;
|
---|
1115 | if (_Encryption_FromZipFile == EncryptionAlgorithm.PkzipWeak)
|
---|
1116 | input2 = new ZipCipherStream(input, _zipCrypto_forExtract, CryptoMode.Decrypt);
|
---|
1117 |
|
---|
1118 | #if AESCRYPTO
|
---|
1119 | else if (_Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes128 ||
|
---|
1120 | _Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes256)
|
---|
1121 | input2 = new WinZipAesCipherStream(input, _aesCrypto_forExtract, _CompressedFileDataSize, CryptoMode.Decrypt);
|
---|
1122 | #endif
|
---|
1123 |
|
---|
1124 | else
|
---|
1125 | input2 = input;
|
---|
1126 |
|
---|
1127 | return input2;
|
---|
1128 | }
|
---|
1129 |
|
---|
1130 |
|
---|
1131 |
|
---|
1132 |
|
---|
1133 | internal void _SetTimes(string fileOrDirectory, bool isFile)
|
---|
1134 | {
|
---|
1135 | #if SILVERLIGHT
|
---|
1136 | // punt on setting file times
|
---|
1137 | #else
|
---|
1138 | // workitem 8807:
|
---|
1139 | // Because setting the time is not considered to be a fatal error,
|
---|
1140 | // and because other applications can interfere with the setting
|
---|
1141 | // of a time on a directory, we're going to swallow IO exceptions
|
---|
1142 | // in this method.
|
---|
1143 |
|
---|
1144 | try
|
---|
1145 | {
|
---|
1146 | if (_ntfsTimesAreSet)
|
---|
1147 | {
|
---|
1148 | #if NETCF
|
---|
1149 | // workitem 7944: set time should not be a fatal error on CF
|
---|
1150 | int rc = NetCfFile.SetTimes(fileOrDirectory, _Ctime, _Atime, _Mtime);
|
---|
1151 | if ( rc != 0)
|
---|
1152 | {
|
---|
1153 | WriteStatus("Warning: SetTimes failed. entry({0}) file({1}) rc({2})",
|
---|
1154 | FileName, fileOrDirectory, rc);
|
---|
1155 | }
|
---|
1156 | #else
|
---|
1157 | if (isFile)
|
---|
1158 | {
|
---|
1159 | // It's possible that the extract was cancelled, in which case,
|
---|
1160 | // the file does not exist.
|
---|
1161 | if (File.Exists(fileOrDirectory))
|
---|
1162 | {
|
---|
1163 | File.SetCreationTimeUtc(fileOrDirectory, _Ctime);
|
---|
1164 | File.SetLastAccessTimeUtc(fileOrDirectory, _Atime);
|
---|
1165 | File.SetLastWriteTimeUtc(fileOrDirectory, _Mtime);
|
---|
1166 | }
|
---|
1167 | }
|
---|
1168 | else
|
---|
1169 | {
|
---|
1170 | // It's possible that the extract was cancelled, in which case,
|
---|
1171 | // the directory does not exist.
|
---|
1172 | if (Directory.Exists(fileOrDirectory))
|
---|
1173 | {
|
---|
1174 | Directory.SetCreationTimeUtc(fileOrDirectory, _Ctime);
|
---|
1175 | Directory.SetLastAccessTimeUtc(fileOrDirectory, _Atime);
|
---|
1176 | Directory.SetLastWriteTimeUtc(fileOrDirectory, _Mtime);
|
---|
1177 | }
|
---|
1178 | }
|
---|
1179 | #endif
|
---|
1180 | }
|
---|
1181 | else
|
---|
1182 | {
|
---|
1183 | // workitem 6191
|
---|
1184 | DateTime AdjustedLastModified = Ionic.Zip.SharedUtilities.AdjustTime_Reverse(LastModified);
|
---|
1185 |
|
---|
1186 | #if NETCF
|
---|
1187 | int rc = NetCfFile.SetLastWriteTime(fileOrDirectory, AdjustedLastModified);
|
---|
1188 |
|
---|
1189 | if ( rc != 0)
|
---|
1190 | {
|
---|
1191 | WriteStatus("Warning: SetLastWriteTime failed. entry({0}) file({1}) rc({2})",
|
---|
1192 | FileName, fileOrDirectory, rc);
|
---|
1193 | }
|
---|
1194 | #else
|
---|
1195 | if (isFile)
|
---|
1196 | File.SetLastWriteTime(fileOrDirectory, AdjustedLastModified);
|
---|
1197 | else
|
---|
1198 | Directory.SetLastWriteTime(fileOrDirectory, AdjustedLastModified);
|
---|
1199 | #endif
|
---|
1200 | }
|
---|
1201 | }
|
---|
1202 | catch (System.IO.IOException ioexc1)
|
---|
1203 | {
|
---|
1204 | WriteStatus("failed to set time on {0}: {1}", fileOrDirectory, ioexc1.Message);
|
---|
1205 | }
|
---|
1206 | #endif
|
---|
1207 | }
|
---|
1208 |
|
---|
1209 |
|
---|
1210 | #region Support methods
|
---|
1211 |
|
---|
1212 |
|
---|
1213 | // workitem 7968
|
---|
1214 | private string UnsupportedAlgorithm
|
---|
1215 | {
|
---|
1216 | get
|
---|
1217 | {
|
---|
1218 | string alg = String.Empty;
|
---|
1219 | switch (_UnsupportedAlgorithmId)
|
---|
1220 | {
|
---|
1221 | case 0:
|
---|
1222 | alg = "--";
|
---|
1223 | break;
|
---|
1224 | case 0x6601:
|
---|
1225 | alg = "DES";
|
---|
1226 | break;
|
---|
1227 | case 0x6602: // - RC2 (version needed to extract < 5.2)
|
---|
1228 | alg = "RC2";
|
---|
1229 | break;
|
---|
1230 | case 0x6603: // - 3DES 168
|
---|
1231 | alg = "3DES-168";
|
---|
1232 | break;
|
---|
1233 | case 0x6609: // - 3DES 112
|
---|
1234 | alg = "3DES-112";
|
---|
1235 | break;
|
---|
1236 | case 0x660E: // - AES 128
|
---|
1237 | alg = "PKWare AES128";
|
---|
1238 | break;
|
---|
1239 | case 0x660F: // - AES 192
|
---|
1240 | alg = "PKWare AES192";
|
---|
1241 | break;
|
---|
1242 | case 0x6610: // - AES 256
|
---|
1243 | alg = "PKWare AES256";
|
---|
1244 | break;
|
---|
1245 | case 0x6702: // - RC2 (version needed to extract >= 5.2)
|
---|
1246 | alg = "RC2";
|
---|
1247 | break;
|
---|
1248 | case 0x6720: // - Blowfish
|
---|
1249 | alg = "Blowfish";
|
---|
1250 | break;
|
---|
1251 | case 0x6721: // - Twofish
|
---|
1252 | alg = "Twofish";
|
---|
1253 | break;
|
---|
1254 | case 0x6801: // - RC4
|
---|
1255 | alg = "RC4";
|
---|
1256 | break;
|
---|
1257 | case 0xFFFF: // - Unknown algorithm
|
---|
1258 | default:
|
---|
1259 | alg = String.Format("Unknown (0x{0:X4})", _UnsupportedAlgorithmId);
|
---|
1260 | break;
|
---|
1261 | }
|
---|
1262 | return alg;
|
---|
1263 | }
|
---|
1264 | }
|
---|
1265 |
|
---|
1266 | // workitem 7968
|
---|
1267 | private string UnsupportedCompressionMethod
|
---|
1268 | {
|
---|
1269 | get
|
---|
1270 | {
|
---|
1271 | string meth = String.Empty;
|
---|
1272 | switch ((int)_CompressionMethod)
|
---|
1273 | {
|
---|
1274 | case 0:
|
---|
1275 | meth = "Store";
|
---|
1276 | break;
|
---|
1277 | case 1:
|
---|
1278 | meth = "Shrink";
|
---|
1279 | break;
|
---|
1280 | case 8:
|
---|
1281 | meth = "DEFLATE";
|
---|
1282 | break;
|
---|
1283 | case 9:
|
---|
1284 | meth = "Deflate64";
|
---|
1285 | break;
|
---|
1286 | case 12:
|
---|
1287 | meth = "BZIP2"; // only if BZIP not compiled in
|
---|
1288 | break;
|
---|
1289 | case 14:
|
---|
1290 | meth = "LZMA";
|
---|
1291 | break;
|
---|
1292 | case 19:
|
---|
1293 | meth = "LZ77";
|
---|
1294 | break;
|
---|
1295 | case 98:
|
---|
1296 | meth = "PPMd";
|
---|
1297 | break;
|
---|
1298 | default:
|
---|
1299 | meth = String.Format("Unknown (0x{0:X4})", _CompressionMethod);
|
---|
1300 | break;
|
---|
1301 | }
|
---|
1302 | return meth;
|
---|
1303 | }
|
---|
1304 | }
|
---|
1305 |
|
---|
1306 |
|
---|
1307 | internal void ValidateEncryption()
|
---|
1308 | {
|
---|
1309 | if (Encryption != EncryptionAlgorithm.PkzipWeak &&
|
---|
1310 | #if AESCRYPTO
|
---|
1311 | Encryption != EncryptionAlgorithm.WinZipAes128 &&
|
---|
1312 | Encryption != EncryptionAlgorithm.WinZipAes256 &&
|
---|
1313 | #endif
|
---|
1314 | Encryption != EncryptionAlgorithm.None)
|
---|
1315 | {
|
---|
1316 | // workitem 7968
|
---|
1317 | if (_UnsupportedAlgorithmId != 0)
|
---|
1318 | throw new ZipException(String.Format("Cannot extract: Entry {0} is encrypted with an algorithm not supported by DotNetZip: {1}",
|
---|
1319 | FileName, UnsupportedAlgorithm));
|
---|
1320 | else
|
---|
1321 | throw new ZipException(String.Format("Cannot extract: Entry {0} uses an unsupported encryption algorithm ({1:X2})",
|
---|
1322 | FileName, (int)Encryption));
|
---|
1323 | }
|
---|
1324 | }
|
---|
1325 |
|
---|
1326 |
|
---|
1327 | private void ValidateCompression()
|
---|
1328 | {
|
---|
1329 | if ((_CompressionMethod_FromZipFile != (short)CompressionMethod.None) &&
|
---|
1330 | (_CompressionMethod_FromZipFile != (short)CompressionMethod.Deflate)
|
---|
1331 | #if BZIP
|
---|
1332 | && (_CompressionMethod_FromZipFile != (short)CompressionMethod.BZip2)
|
---|
1333 | #endif
|
---|
1334 | )
|
---|
1335 | throw new ZipException(String.Format("Entry {0} uses an unsupported compression method (0x{1:X2}, {2})",
|
---|
1336 | FileName, _CompressionMethod_FromZipFile, UnsupportedCompressionMethod));
|
---|
1337 | }
|
---|
1338 |
|
---|
1339 |
|
---|
1340 | private void SetupCryptoForExtract(string password)
|
---|
1341 | {
|
---|
1342 | //if (password == null) return;
|
---|
1343 | if (_Encryption_FromZipFile == EncryptionAlgorithm.None) return;
|
---|
1344 |
|
---|
1345 | if (_Encryption_FromZipFile == EncryptionAlgorithm.PkzipWeak)
|
---|
1346 | {
|
---|
1347 | if (password == null)
|
---|
1348 | throw new ZipException("Missing password.");
|
---|
1349 |
|
---|
1350 | this.ArchiveStream.Seek(this.FileDataPosition - 12, SeekOrigin.Begin);
|
---|
1351 | // workitem 10178
|
---|
1352 | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
|
---|
1353 | _zipCrypto_forExtract = ZipCrypto.ForRead(password, this);
|
---|
1354 | }
|
---|
1355 |
|
---|
1356 | #if AESCRYPTO
|
---|
1357 | else if (_Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes128 ||
|
---|
1358 | _Encryption_FromZipFile == EncryptionAlgorithm.WinZipAes256)
|
---|
1359 | {
|
---|
1360 | if (password == null)
|
---|
1361 | throw new ZipException("Missing password.");
|
---|
1362 |
|
---|
1363 | // If we already have a WinZipAesCrypto object in place, use it.
|
---|
1364 | // It can be set up in the ReadDirEntry(), or during a previous Extract.
|
---|
1365 | if (_aesCrypto_forExtract != null)
|
---|
1366 | {
|
---|
1367 | _aesCrypto_forExtract.Password = password;
|
---|
1368 | }
|
---|
1369 | else
|
---|
1370 | {
|
---|
1371 | int sizeOfSaltAndPv = GetLengthOfCryptoHeaderBytes(_Encryption_FromZipFile);
|
---|
1372 | this.ArchiveStream.Seek(this.FileDataPosition - sizeOfSaltAndPv, SeekOrigin.Begin);
|
---|
1373 | // workitem 10178
|
---|
1374 | Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
|
---|
1375 | int keystrength = GetKeyStrengthInBits(_Encryption_FromZipFile);
|
---|
1376 | _aesCrypto_forExtract = WinZipAesCrypto.ReadFromStream(password, keystrength, this.ArchiveStream);
|
---|
1377 | }
|
---|
1378 | }
|
---|
1379 | #endif
|
---|
1380 | }
|
---|
1381 |
|
---|
1382 |
|
---|
1383 |
|
---|
1384 | /// <summary>
|
---|
1385 | /// Validates that the args are consistent.
|
---|
1386 | /// </summary>
|
---|
1387 | /// <remarks>
|
---|
1388 | /// Only one of {baseDir, outStream} can be non-null.
|
---|
1389 | /// If baseDir is non-null, then the outputFile is created.
|
---|
1390 | /// </remarks>
|
---|
1391 | private bool ValidateOutput(string basedir, Stream outstream, out string outFileName)
|
---|
1392 | {
|
---|
1393 | if (basedir != null)
|
---|
1394 | {
|
---|
1395 | // Sometimes the name on the entry starts with a slash.
|
---|
1396 | // Rather than unpack to the root of the volume, we're going to
|
---|
1397 | // drop the slash and unpack to the specified base directory.
|
---|
1398 | string f = this.FileName.Replace("\\","/");
|
---|
1399 |
|
---|
1400 | // workitem 11772: remove drive letter with separator
|
---|
1401 | if (f.IndexOf(':') == 1)
|
---|
1402 | f= f.Substring(2);
|
---|
1403 |
|
---|
1404 | if (f.StartsWith("/"))
|
---|
1405 | f= f.Substring(1);
|
---|
1406 |
|
---|
1407 | // String.Contains is not available on .NET CF 2.0
|
---|
1408 |
|
---|
1409 | if (_container.ZipFile.FlattenFoldersOnExtract)
|
---|
1410 | outFileName = Path.Combine(basedir,
|
---|
1411 | (f.IndexOf('/') != -1) ? Path.GetFileName(f) : f);
|
---|
1412 | else
|
---|
1413 | outFileName = Path.Combine(basedir, f);
|
---|
1414 |
|
---|
1415 | // workitem 10639
|
---|
1416 | outFileName = outFileName.Replace("/","\\");
|
---|
1417 |
|
---|
1418 | // check if it is a directory
|
---|
1419 | if ((IsDirectory) || (FileName.EndsWith("/")))
|
---|
1420 | {
|
---|
1421 | if (!Directory.Exists(outFileName))
|
---|
1422 | {
|
---|
1423 | Directory.CreateDirectory(outFileName);
|
---|
1424 | _SetTimes(outFileName, false);
|
---|
1425 | }
|
---|
1426 | else
|
---|
1427 | {
|
---|
1428 | // the dir exists, maybe we want to overwrite times.
|
---|
1429 | if (ExtractExistingFile == ExtractExistingFileAction.OverwriteSilently)
|
---|
1430 | _SetTimes(outFileName, false);
|
---|
1431 | }
|
---|
1432 | return true; // true == all done, caller will return
|
---|
1433 | }
|
---|
1434 | return false; // false == work to do by caller.
|
---|
1435 | }
|
---|
1436 |
|
---|
1437 | if (outstream != null)
|
---|
1438 | {
|
---|
1439 | outFileName = null;
|
---|
1440 | if ((IsDirectory) || (FileName.EndsWith("/")))
|
---|
1441 | {
|
---|
1442 | // extract a directory to streamwriter? nothing to do!
|
---|
1443 | return true; // true == all done! caller can return
|
---|
1444 | }
|
---|
1445 | return false;
|
---|
1446 | }
|
---|
1447 |
|
---|
1448 | throw new ArgumentNullException("outstream");
|
---|
1449 | }
|
---|
1450 |
|
---|
1451 |
|
---|
1452 | #endregion
|
---|
1453 |
|
---|
1454 | }
|
---|
1455 | }
|
---|