1 | // ZipFile.Read.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-05 11:38:59>
|
---|
19 | //
|
---|
20 | // ------------------------------------------------------------------
|
---|
21 | //
|
---|
22 | // This module defines the methods for Reading zip files.
|
---|
23 | //
|
---|
24 | // ------------------------------------------------------------------
|
---|
25 | //
|
---|
26 |
|
---|
27 |
|
---|
28 | using System;
|
---|
29 | using System.IO;
|
---|
30 | using System.Collections.Generic;
|
---|
31 |
|
---|
32 | namespace OfficeOpenXml.Packaging.Ionic.Zip
|
---|
33 | {
|
---|
34 | /// <summary>
|
---|
35 | /// A class for collecting the various options that can be used when
|
---|
36 | /// Reading zip files for extraction or update.
|
---|
37 | /// </summary>
|
---|
38 | ///
|
---|
39 | /// <remarks>
|
---|
40 | /// <para>
|
---|
41 | /// When reading a zip file, there are several options an
|
---|
42 | /// application can set, to modify how the file is read, or what
|
---|
43 | /// the library does while reading. This class collects those
|
---|
44 | /// options into one container.
|
---|
45 | /// </para>
|
---|
46 | ///
|
---|
47 | /// <para>
|
---|
48 | /// Pass an instance of the <c>ReadOptions</c> class into the
|
---|
49 | /// <c>ZipFile.Read()</c> method.
|
---|
50 | /// </para>
|
---|
51 | ///
|
---|
52 | /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>.
|
---|
53 | /// <seealso cref="ZipFile.Read(Stream, ReadOptions)"/>.
|
---|
54 | /// </remarks>
|
---|
55 | internal class ReadOptions
|
---|
56 | {
|
---|
57 | /// <summary>
|
---|
58 | /// An event handler for Read operations. When opening large zip
|
---|
59 | /// archives, you may want to display a progress bar or other
|
---|
60 | /// indicator of status progress while reading. This parameter
|
---|
61 | /// allows you to specify a ReadProgress Event Handler directly.
|
---|
62 | /// When you call <c>Read()</c>, the progress event is invoked as
|
---|
63 | /// necessary.
|
---|
64 | /// </summary>
|
---|
65 | public EventHandler<ReadProgressEventArgs> ReadProgress { get; set; }
|
---|
66 |
|
---|
67 | /// <summary>
|
---|
68 | /// The <c>System.IO.TextWriter</c> to use for writing verbose status messages
|
---|
69 | /// during operations on the zip archive. A console application may wish to
|
---|
70 | /// pass <c>System.Console.Out</c> to get messages on the Console. A graphical
|
---|
71 | /// or headless application may wish to capture the messages in a different
|
---|
72 | /// <c>TextWriter</c>, such as a <c>System.IO.StringWriter</c>.
|
---|
73 | /// </summary>
|
---|
74 | public TextWriter StatusMessageWriter { get; set; }
|
---|
75 |
|
---|
76 | /// <summary>
|
---|
77 | /// The <c>System.Text.Encoding</c> to use when reading in the zip archive. Be
|
---|
78 | /// careful specifying the encoding. If the value you use here is not the same
|
---|
79 | /// as the Encoding used when the zip archive was created (possibly by a
|
---|
80 | /// different archiver) you will get unexpected results and possibly exceptions.
|
---|
81 | /// </summary>
|
---|
82 | ///
|
---|
83 | /// <seealso cref="ZipFile.ProvisionalAlternateEncoding"/>
|
---|
84 | ///
|
---|
85 | public System.Text.Encoding @Encoding { get; set; }
|
---|
86 | }
|
---|
87 |
|
---|
88 |
|
---|
89 | internal partial class ZipFile
|
---|
90 | {
|
---|
91 | /// <summary>
|
---|
92 | /// Reads a zip file archive and returns the instance.
|
---|
93 | /// </summary>
|
---|
94 | ///
|
---|
95 | /// <remarks>
|
---|
96 | /// <para>
|
---|
97 | /// The stream is read using the default <c>System.Text.Encoding</c>, which is the
|
---|
98 | /// <c>IBM437</c> codepage.
|
---|
99 | /// </para>
|
---|
100 | /// </remarks>
|
---|
101 | ///
|
---|
102 | /// <exception cref="System.Exception">
|
---|
103 | /// Thrown if the <c>ZipFile</c> cannot be read. The implementation of this method
|
---|
104 | /// relies on <c>System.IO.File.OpenRead</c>, which can throw a variety of exceptions,
|
---|
105 | /// including specific exceptions if a file is not found, an unauthorized access
|
---|
106 | /// exception, exceptions for poorly formatted filenames, and so on.
|
---|
107 | /// </exception>
|
---|
108 | ///
|
---|
109 | /// <param name="fileName">
|
---|
110 | /// The name of the zip archive to open. This can be a fully-qualified or relative
|
---|
111 | /// pathname.
|
---|
112 | /// </param>
|
---|
113 | ///
|
---|
114 | /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>.
|
---|
115 | ///
|
---|
116 | /// <returns>The instance read from the zip archive.</returns>
|
---|
117 | ///
|
---|
118 | public static ZipFile Read(string fileName)
|
---|
119 | {
|
---|
120 | return ZipFile.Read(fileName, null, null, null);
|
---|
121 | }
|
---|
122 |
|
---|
123 |
|
---|
124 | /// <summary>
|
---|
125 | /// Reads a zip file archive from the named filesystem file using the
|
---|
126 | /// specified options.
|
---|
127 | /// </summary>
|
---|
128 | ///
|
---|
129 | /// <remarks>
|
---|
130 | /// <para>
|
---|
131 | /// This version of the <c>Read()</c> method allows the caller to pass
|
---|
132 | /// in a <c>TextWriter</c> an <c>Encoding</c>, via an instance of the
|
---|
133 | /// <c>ReadOptions</c> class. The <c>ZipFile</c> is read in using the
|
---|
134 | /// specified encoding for entries where UTF-8 encoding is not
|
---|
135 | /// explicitly specified.
|
---|
136 | /// </para>
|
---|
137 | /// </remarks>
|
---|
138 | ///
|
---|
139 | /// <example>
|
---|
140 | ///
|
---|
141 | /// <para>
|
---|
142 | /// This example shows how to read a zip file using the Big-5 Chinese
|
---|
143 | /// code page (950), and extract each entry in the zip file, while
|
---|
144 | /// sending status messages out to the Console.
|
---|
145 | /// </para>
|
---|
146 | ///
|
---|
147 | /// <para>
|
---|
148 | /// For this code to work as intended, the zipfile must have been
|
---|
149 | /// created using the big5 code page (CP950). This is typical, for
|
---|
150 | /// example, when using WinRar on a machine with CP950 set as the
|
---|
151 | /// default code page. In that case, the names of entries within the
|
---|
152 | /// Zip archive will be stored in that code page, and reading the zip
|
---|
153 | /// archive must be done using that code page. If the application did
|
---|
154 | /// not use the correct code page in ZipFile.Read(), then names of
|
---|
155 | /// entries within the zip archive would not be correctly retrieved.
|
---|
156 | /// </para>
|
---|
157 | ///
|
---|
158 | /// <code lang="C#">
|
---|
159 | /// string zipToExtract = "MyArchive.zip";
|
---|
160 | /// string extractDirectory = "extract";
|
---|
161 | /// var options = new ReadOptions
|
---|
162 | /// {
|
---|
163 | /// StatusMessageWriter = System.Console.Out,
|
---|
164 | /// Encoding = System.Text.Encoding.GetEncoding(950)
|
---|
165 | /// };
|
---|
166 | /// using (ZipFile zip = ZipFile.Read(zipToExtract, options))
|
---|
167 | /// {
|
---|
168 | /// foreach (ZipEntry e in zip)
|
---|
169 | /// {
|
---|
170 | /// e.Extract(extractDirectory);
|
---|
171 | /// }
|
---|
172 | /// }
|
---|
173 | /// </code>
|
---|
174 | ///
|
---|
175 | ///
|
---|
176 | /// <code lang="VB">
|
---|
177 | /// Dim zipToExtract as String = "MyArchive.zip"
|
---|
178 | /// Dim extractDirectory as String = "extract"
|
---|
179 | /// Dim options as New ReadOptions
|
---|
180 | /// options.Encoding = System.Text.Encoding.GetEncoding(950)
|
---|
181 | /// options.StatusMessageWriter = System.Console.Out
|
---|
182 | /// Using zip As ZipFile = ZipFile.Read(zipToExtract, options)
|
---|
183 | /// Dim e As ZipEntry
|
---|
184 | /// For Each e In zip
|
---|
185 | /// e.Extract(extractDirectory)
|
---|
186 | /// Next
|
---|
187 | /// End Using
|
---|
188 | /// </code>
|
---|
189 | /// </example>
|
---|
190 | ///
|
---|
191 | ///
|
---|
192 | /// <example>
|
---|
193 | ///
|
---|
194 | /// <para>
|
---|
195 | /// This example shows how to read a zip file using the default
|
---|
196 | /// code page, to remove entries that have a modified date before a given threshold,
|
---|
197 | /// sending status messages out to a <c>StringWriter</c>.
|
---|
198 | /// </para>
|
---|
199 | ///
|
---|
200 | /// <code lang="C#">
|
---|
201 | /// var options = new ReadOptions
|
---|
202 | /// {
|
---|
203 | /// StatusMessageWriter = new System.IO.StringWriter()
|
---|
204 | /// };
|
---|
205 | /// using (ZipFile zip = ZipFile.Read("PackedDocuments.zip", options))
|
---|
206 | /// {
|
---|
207 | /// var Threshold = new DateTime(2007,7,4);
|
---|
208 | /// // We cannot remove the entry from the list, within the context of
|
---|
209 | /// // an enumeration of said list.
|
---|
210 | /// // So we add the doomed entry to a list to be removed later.
|
---|
211 | /// // pass 1: mark the entries for removal
|
---|
212 | /// var MarkedEntries = new System.Collections.Generic.List<ZipEntry>();
|
---|
213 | /// foreach (ZipEntry e in zip)
|
---|
214 | /// {
|
---|
215 | /// if (e.LastModified < Threshold)
|
---|
216 | /// MarkedEntries.Add(e);
|
---|
217 | /// }
|
---|
218 | /// // pass 2: actually remove the entry.
|
---|
219 | /// foreach (ZipEntry zombie in MarkedEntries)
|
---|
220 | /// zip.RemoveEntry(zombie);
|
---|
221 | /// zip.Comment = "This archive has been updated.";
|
---|
222 | /// zip.Save();
|
---|
223 | /// }
|
---|
224 | /// // can now use contents of sw, eg store in an audit log
|
---|
225 | /// </code>
|
---|
226 | ///
|
---|
227 | /// <code lang="VB">
|
---|
228 | /// Dim options as New ReadOptions
|
---|
229 | /// options.StatusMessageWriter = New System.IO.StringWriter
|
---|
230 | /// Using zip As ZipFile = ZipFile.Read("PackedDocuments.zip", options)
|
---|
231 | /// Dim Threshold As New DateTime(2007, 7, 4)
|
---|
232 | /// ' We cannot remove the entry from the list, within the context of
|
---|
233 | /// ' an enumeration of said list.
|
---|
234 | /// ' So we add the doomed entry to a list to be removed later.
|
---|
235 | /// ' pass 1: mark the entries for removal
|
---|
236 | /// Dim MarkedEntries As New System.Collections.Generic.List(Of ZipEntry)
|
---|
237 | /// Dim e As ZipEntry
|
---|
238 | /// For Each e In zip
|
---|
239 | /// If (e.LastModified < Threshold) Then
|
---|
240 | /// MarkedEntries.Add(e)
|
---|
241 | /// End If
|
---|
242 | /// Next
|
---|
243 | /// ' pass 2: actually remove the entry.
|
---|
244 | /// Dim zombie As ZipEntry
|
---|
245 | /// For Each zombie In MarkedEntries
|
---|
246 | /// zip.RemoveEntry(zombie)
|
---|
247 | /// Next
|
---|
248 | /// zip.Comment = "This archive has been updated."
|
---|
249 | /// zip.Save
|
---|
250 | /// End Using
|
---|
251 | /// ' can now use contents of sw, eg store in an audit log
|
---|
252 | /// </code>
|
---|
253 | /// </example>
|
---|
254 | ///
|
---|
255 | /// <exception cref="System.Exception">
|
---|
256 | /// Thrown if the zipfile cannot be read. The implementation of
|
---|
257 | /// this method relies on <c>System.IO.File.OpenRead</c>, which
|
---|
258 | /// can throw a variety of exceptions, including specific
|
---|
259 | /// exceptions if a file is not found, an unauthorized access
|
---|
260 | /// exception, exceptions for poorly formatted filenames, and so
|
---|
261 | /// on.
|
---|
262 | /// </exception>
|
---|
263 | ///
|
---|
264 | /// <param name="fileName">
|
---|
265 | /// The name of the zip archive to open.
|
---|
266 | /// This can be a fully-qualified or relative pathname.
|
---|
267 | /// </param>
|
---|
268 | ///
|
---|
269 | /// <param name="options">
|
---|
270 | /// The set of options to use when reading the zip file.
|
---|
271 | /// </param>
|
---|
272 | ///
|
---|
273 | /// <returns>The ZipFile instance read from the zip archive.</returns>
|
---|
274 | ///
|
---|
275 | /// <seealso cref="ZipFile.Read(Stream, ReadOptions)"/>
|
---|
276 | ///
|
---|
277 | internal static ZipFile Read(string fileName,
|
---|
278 | ReadOptions options)
|
---|
279 | {
|
---|
280 | if (options == null)
|
---|
281 | throw new ArgumentNullException("options");
|
---|
282 | return Read(fileName,
|
---|
283 | options.StatusMessageWriter,
|
---|
284 | options.Encoding,
|
---|
285 | options.ReadProgress);
|
---|
286 | }
|
---|
287 |
|
---|
288 | /// <summary>
|
---|
289 | /// Reads a zip file archive using the specified text encoding, the specified
|
---|
290 | /// TextWriter for status messages, and the specified ReadProgress event handler,
|
---|
291 | /// and returns the instance.
|
---|
292 | /// </summary>
|
---|
293 | ///
|
---|
294 | /// <param name="fileName">
|
---|
295 | /// The name of the zip archive to open.
|
---|
296 | /// This can be a fully-qualified or relative pathname.
|
---|
297 | /// </param>
|
---|
298 | ///
|
---|
299 | /// <param name="readProgress">
|
---|
300 | /// An event handler for Read operations.
|
---|
301 | /// </param>
|
---|
302 | ///
|
---|
303 | /// <param name="statusMessageWriter">
|
---|
304 | /// The <c>System.IO.TextWriter</c> to use for writing verbose status messages
|
---|
305 | /// during operations on the zip archive. A console application may wish to
|
---|
306 | /// pass <c>System.Console.Out</c> to get messages on the Console. A graphical
|
---|
307 | /// or headless application may wish to capture the messages in a different
|
---|
308 | /// <c>TextWriter</c>, such as a <c>System.IO.StringWriter</c>.
|
---|
309 | /// </param>
|
---|
310 | ///
|
---|
311 | /// <param name="encoding">
|
---|
312 | /// The <c>System.Text.Encoding</c> to use when reading in the zip archive. Be
|
---|
313 | /// careful specifying the encoding. If the value you use here is not the same
|
---|
314 | /// as the Encoding used when the zip archive was created (possibly by a
|
---|
315 | /// different archiver) you will get unexpected results and possibly exceptions.
|
---|
316 | /// </param>
|
---|
317 | ///
|
---|
318 | /// <returns>The instance read from the zip archive.</returns>
|
---|
319 | ///
|
---|
320 | private static ZipFile Read(string fileName,
|
---|
321 | TextWriter statusMessageWriter,
|
---|
322 | System.Text.Encoding encoding,
|
---|
323 | EventHandler<ReadProgressEventArgs> readProgress)
|
---|
324 | {
|
---|
325 | ZipFile zf = new ZipFile();
|
---|
326 | zf.AlternateEncoding = encoding ?? DefaultEncoding;
|
---|
327 | zf.AlternateEncodingUsage = ZipOption.Always;
|
---|
328 | zf._StatusMessageTextWriter = statusMessageWriter;
|
---|
329 | zf._name = fileName;
|
---|
330 | if (readProgress != null)
|
---|
331 | zf.ReadProgress = readProgress;
|
---|
332 |
|
---|
333 | if (zf.Verbose) zf._StatusMessageTextWriter.WriteLine("reading from {0}...", fileName);
|
---|
334 |
|
---|
335 | ReadIntoInstance(zf);
|
---|
336 | zf._fileAlreadyExists = true;
|
---|
337 |
|
---|
338 | return zf;
|
---|
339 | }
|
---|
340 |
|
---|
341 | /// <summary>
|
---|
342 | /// Reads a zip archive from a stream.
|
---|
343 | /// </summary>
|
---|
344 | ///
|
---|
345 | /// <remarks>
|
---|
346 | ///
|
---|
347 | /// <para>
|
---|
348 | /// When reading from a file, it's probably easier to just use
|
---|
349 | /// <see cref="ZipFile.Read(String,
|
---|
350 | /// ReadOptions)">ZipFile.Read(String, ReadOptions)</see>. This
|
---|
351 | /// overload is useful when when the zip archive content is
|
---|
352 | /// available from an already-open stream. The stream must be
|
---|
353 | /// open and readable and seekable when calling this method. The
|
---|
354 | /// stream is left open when the reading is completed.
|
---|
355 | /// </para>
|
---|
356 | ///
|
---|
357 | /// <para>
|
---|
358 | /// Using this overload, the stream is read using the default
|
---|
359 | /// <c>System.Text.Encoding</c>, which is the <c>IBM437</c>
|
---|
360 | /// codepage. If you want to specify the encoding to use when
|
---|
361 | /// reading the zipfile content, see
|
---|
362 | /// <see cref="ZipFile.Read(Stream,
|
---|
363 | /// ReadOptions)">ZipFile.Read(Stream, ReadOptions)</see>. This
|
---|
364 | /// </para>
|
---|
365 | ///
|
---|
366 | /// <para>
|
---|
367 | /// Reading of zip content begins at the current position in the
|
---|
368 | /// stream. This means if you have a stream that concatenates
|
---|
369 | /// regular data and zip data, if you position the open, readable
|
---|
370 | /// stream at the start of the zip data, you will be able to read
|
---|
371 | /// the zip archive using this constructor, or any of the ZipFile
|
---|
372 | /// constructors that accept a <see cref="System.IO.Stream" /> as
|
---|
373 | /// input. Some examples of where this might be useful: the zip
|
---|
374 | /// content is concatenated at the end of a regular EXE file, as
|
---|
375 | /// some self-extracting archives do. (Note: SFX files produced
|
---|
376 | /// by DotNetZip do not work this way; they can be read as normal
|
---|
377 | /// ZIP files). Another example might be a stream being read from
|
---|
378 | /// a database, where the zip content is embedded within an
|
---|
379 | /// aggregate stream of data.
|
---|
380 | /// </para>
|
---|
381 | ///
|
---|
382 | /// </remarks>
|
---|
383 | ///
|
---|
384 | /// <example>
|
---|
385 | /// <para>
|
---|
386 | /// This example shows how to Read zip content from a stream, and
|
---|
387 | /// extract one entry into a different stream. In this example,
|
---|
388 | /// the filename "NameOfEntryInArchive.doc", refers only to the
|
---|
389 | /// name of the entry within the zip archive. A file by that
|
---|
390 | /// name is not created in the filesystem. The I/O is done
|
---|
391 | /// strictly with the given streams.
|
---|
392 | /// </para>
|
---|
393 | ///
|
---|
394 | /// <code>
|
---|
395 | /// using (ZipFile zip = ZipFile.Read(InputStream))
|
---|
396 | /// {
|
---|
397 | /// zip.Extract("NameOfEntryInArchive.doc", OutputStream);
|
---|
398 | /// }
|
---|
399 | /// </code>
|
---|
400 | ///
|
---|
401 | /// <code lang="VB">
|
---|
402 | /// Using zip as ZipFile = ZipFile.Read(InputStream)
|
---|
403 | /// zip.Extract("NameOfEntryInArchive.doc", OutputStream)
|
---|
404 | /// End Using
|
---|
405 | /// </code>
|
---|
406 | /// </example>
|
---|
407 | ///
|
---|
408 | /// <param name="zipStream">the stream containing the zip data.</param>
|
---|
409 | ///
|
---|
410 | /// <returns>The ZipFile instance read from the stream</returns>
|
---|
411 | ///
|
---|
412 | public static ZipFile Read(Stream zipStream)
|
---|
413 | {
|
---|
414 | return Read(zipStream, null, null, null);
|
---|
415 | }
|
---|
416 |
|
---|
417 | /// <summary>
|
---|
418 | /// Reads a zip file archive from the given stream using the
|
---|
419 | /// specified options.
|
---|
420 | /// </summary>
|
---|
421 | ///
|
---|
422 | /// <remarks>
|
---|
423 | ///
|
---|
424 | /// <para>
|
---|
425 | /// When reading from a file, it's probably easier to just use
|
---|
426 | /// <see cref="ZipFile.Read(String,
|
---|
427 | /// ReadOptions)">ZipFile.Read(String, ReadOptions)</see>. This
|
---|
428 | /// overload is useful when when the zip archive content is
|
---|
429 | /// available from an already-open stream. The stream must be
|
---|
430 | /// open and readable and seekable when calling this method. The
|
---|
431 | /// stream is left open when the reading is completed.
|
---|
432 | /// </para>
|
---|
433 | ///
|
---|
434 | /// <para>
|
---|
435 | /// Reading of zip content begins at the current position in the
|
---|
436 | /// stream. This means if you have a stream that concatenates
|
---|
437 | /// regular data and zip data, if you position the open, readable
|
---|
438 | /// stream at the start of the zip data, you will be able to read
|
---|
439 | /// the zip archive using this constructor, or any of the ZipFile
|
---|
440 | /// constructors that accept a <see cref="System.IO.Stream" /> as
|
---|
441 | /// input. Some examples of where this might be useful: the zip
|
---|
442 | /// content is concatenated at the end of a regular EXE file, as
|
---|
443 | /// some self-extracting archives do. (Note: SFX files produced
|
---|
444 | /// by DotNetZip do not work this way; they can be read as normal
|
---|
445 | /// ZIP files). Another example might be a stream being read from
|
---|
446 | /// a database, where the zip content is embedded within an
|
---|
447 | /// aggregate stream of data.
|
---|
448 | /// </para>
|
---|
449 | /// </remarks>
|
---|
450 | ///
|
---|
451 | /// <param name="zipStream">the stream containing the zip data.</param>
|
---|
452 | ///
|
---|
453 | /// <param name="options">
|
---|
454 | /// The set of options to use when reading the zip file.
|
---|
455 | /// </param>
|
---|
456 | ///
|
---|
457 | /// <exception cref="System.Exception">
|
---|
458 | /// Thrown if the zip archive cannot be read.
|
---|
459 | /// </exception>
|
---|
460 | ///
|
---|
461 | /// <returns>The ZipFile instance read from the stream.</returns>
|
---|
462 | ///
|
---|
463 | /// <seealso cref="ZipFile.Read(String, ReadOptions)"/>
|
---|
464 | ///
|
---|
465 | internal static ZipFile Read(Stream zipStream, ReadOptions options)
|
---|
466 | {
|
---|
467 | if (options == null)
|
---|
468 | throw new ArgumentNullException("options");
|
---|
469 |
|
---|
470 | return Read(zipStream,
|
---|
471 | options.StatusMessageWriter,
|
---|
472 | options.Encoding,
|
---|
473 | options.ReadProgress);
|
---|
474 | }
|
---|
475 |
|
---|
476 |
|
---|
477 |
|
---|
478 | /// <summary>
|
---|
479 | /// Reads a zip archive from a stream, using the specified text Encoding, the
|
---|
480 | /// specified TextWriter for status messages,
|
---|
481 | /// and the specified ReadProgress event handler.
|
---|
482 | /// </summary>
|
---|
483 | ///
|
---|
484 | /// <remarks>
|
---|
485 | /// <para>
|
---|
486 | /// Reading of zip content begins at the current position in the stream. This
|
---|
487 | /// means if you have a stream that concatenates regular data and zip data, if
|
---|
488 | /// you position the open, readable stream at the start of the zip data, you
|
---|
489 | /// will be able to read the zip archive using this constructor, or any of the
|
---|
490 | /// ZipFile constructors that accept a <see cref="System.IO.Stream" /> as
|
---|
491 | /// input. Some examples of where this might be useful: the zip content is
|
---|
492 | /// concatenated at the end of a regular EXE file, as some self-extracting
|
---|
493 | /// archives do. (Note: SFX files produced by DotNetZip do not work this
|
---|
494 | /// way). Another example might be a stream being read from a database, where
|
---|
495 | /// the zip content is embedded within an aggregate stream of data.
|
---|
496 | /// </para>
|
---|
497 | /// </remarks>
|
---|
498 | ///
|
---|
499 | /// <param name="zipStream">the stream containing the zip data.</param>
|
---|
500 | ///
|
---|
501 | /// <param name="statusMessageWriter">
|
---|
502 | /// The <c>System.IO.TextWriter</c> to which verbose status messages are written
|
---|
503 | /// during operations on the <c>ZipFile</c>. For example, in a console
|
---|
504 | /// application, System.Console.Out works, and will get a message for each entry
|
---|
505 | /// added to the ZipFile. If the TextWriter is <c>null</c>, no verbose messages
|
---|
506 | /// are written.
|
---|
507 | /// </param>
|
---|
508 | ///
|
---|
509 | /// <param name="encoding">
|
---|
510 | /// The text encoding to use when reading entries that do not have the UTF-8
|
---|
511 | /// encoding bit set. Be careful specifying the encoding. If the value you use
|
---|
512 | /// here is not the same as the Encoding used when the zip archive was created
|
---|
513 | /// (possibly by a different archiver) you will get unexpected results and
|
---|
514 | /// possibly exceptions. See the <see cref="ProvisionalAlternateEncoding"/>
|
---|
515 | /// property for more information.
|
---|
516 | /// </param>
|
---|
517 | ///
|
---|
518 | /// <param name="readProgress">
|
---|
519 | /// An event handler for Read operations.
|
---|
520 | /// </param>
|
---|
521 | ///
|
---|
522 | /// <returns>an instance of ZipFile</returns>
|
---|
523 | private static ZipFile Read(Stream zipStream,
|
---|
524 | TextWriter statusMessageWriter,
|
---|
525 | System.Text.Encoding encoding,
|
---|
526 | EventHandler<ReadProgressEventArgs> readProgress)
|
---|
527 | {
|
---|
528 | if (zipStream == null)
|
---|
529 | throw new ArgumentNullException("zipStream");
|
---|
530 |
|
---|
531 | ZipFile zf = new ZipFile();
|
---|
532 | zf._StatusMessageTextWriter = statusMessageWriter;
|
---|
533 | zf._alternateEncoding = encoding ?? ZipFile.DefaultEncoding;
|
---|
534 | zf._alternateEncodingUsage = ZipOption.Always;
|
---|
535 | if (readProgress != null)
|
---|
536 | zf.ReadProgress += readProgress;
|
---|
537 | zf._readstream = (zipStream.Position == 0L)
|
---|
538 | ? zipStream
|
---|
539 | : new OffsetStream(zipStream);
|
---|
540 | zf._ReadStreamIsOurs = false;
|
---|
541 | if (zf.Verbose) zf._StatusMessageTextWriter.WriteLine("reading from stream...");
|
---|
542 |
|
---|
543 | ReadIntoInstance(zf);
|
---|
544 | return zf;
|
---|
545 | }
|
---|
546 |
|
---|
547 |
|
---|
548 |
|
---|
549 | private static void ReadIntoInstance(ZipFile zf)
|
---|
550 | {
|
---|
551 | Stream s = zf.ReadStream;
|
---|
552 | try
|
---|
553 | {
|
---|
554 | zf._readName = zf._name; // workitem 13915
|
---|
555 | if (!s.CanSeek)
|
---|
556 | {
|
---|
557 | ReadIntoInstance_Orig(zf);
|
---|
558 | return;
|
---|
559 | }
|
---|
560 |
|
---|
561 | zf.OnReadStarted();
|
---|
562 |
|
---|
563 | // change for workitem 8098
|
---|
564 | //zf._originPosition = s.Position;
|
---|
565 |
|
---|
566 | // Try reading the central directory, rather than scanning the file.
|
---|
567 |
|
---|
568 | uint datum = ReadFirstFourBytes(s);
|
---|
569 |
|
---|
570 | if (datum == ZipConstants.EndOfCentralDirectorySignature)
|
---|
571 | return;
|
---|
572 |
|
---|
573 |
|
---|
574 | // start at the end of the file...
|
---|
575 | // seek backwards a bit, then look for the EoCD signature.
|
---|
576 | int nTries = 0;
|
---|
577 | bool success = false;
|
---|
578 |
|
---|
579 | // The size of the end-of-central-directory-footer plus 2 bytes is 18.
|
---|
580 | // This implies an archive comment length of 0. We'll add a margin of
|
---|
581 | // safety and start "in front" of that, when looking for the
|
---|
582 | // EndOfCentralDirectorySignature
|
---|
583 | long posn = s.Length - 64;
|
---|
584 | long maxSeekback = Math.Max(s.Length - 0x4000, 10);
|
---|
585 | do
|
---|
586 | {
|
---|
587 | if (posn < 0) posn = 0; // BOF
|
---|
588 | s.Seek(posn, SeekOrigin.Begin);
|
---|
589 | long bytesRead = SharedUtilities.FindSignature(s, (int)ZipConstants.EndOfCentralDirectorySignature);
|
---|
590 | if (bytesRead != -1)
|
---|
591 | success = true;
|
---|
592 | else
|
---|
593 | {
|
---|
594 | if (posn==0) break; // started at the BOF and found nothing
|
---|
595 | nTries++;
|
---|
596 | // Weird: with NETCF, negative offsets from SeekOrigin.End DO
|
---|
597 | // NOT WORK. So rather than seek a negative offset, we seek
|
---|
598 | // from SeekOrigin.Begin using a smaller number.
|
---|
599 | posn -= (32 * (nTries + 1) * nTries);
|
---|
600 | }
|
---|
601 | }
|
---|
602 | while (!success && posn > maxSeekback);
|
---|
603 |
|
---|
604 | if (success)
|
---|
605 | {
|
---|
606 | // workitem 8299
|
---|
607 | zf._locEndOfCDS = s.Position - 4;
|
---|
608 |
|
---|
609 | byte[] block = new byte[16];
|
---|
610 | s.Read(block, 0, block.Length);
|
---|
611 |
|
---|
612 | zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2);
|
---|
613 |
|
---|
614 | if (zf._diskNumberWithCd == 0xFFFF)
|
---|
615 | throw new ZipException("Spanned archives with more than 65534 segments are not supported at this time.");
|
---|
616 |
|
---|
617 | zf._diskNumberWithCd++; // I think the number in the file differs from reality by 1
|
---|
618 |
|
---|
619 | int i = 12;
|
---|
620 |
|
---|
621 | uint offset32 = (uint) BitConverter.ToUInt32(block, i);
|
---|
622 | if (offset32 == 0xFFFFFFFF)
|
---|
623 | {
|
---|
624 | Zip64SeekToCentralDirectory(zf);
|
---|
625 | }
|
---|
626 | else
|
---|
627 | {
|
---|
628 | zf._OffsetOfCentralDirectory = offset32;
|
---|
629 | // change for workitem 8098
|
---|
630 | s.Seek(offset32, SeekOrigin.Begin);
|
---|
631 | }
|
---|
632 |
|
---|
633 | ReadCentralDirectory(zf);
|
---|
634 | }
|
---|
635 | else
|
---|
636 | {
|
---|
637 | // Could not find the central directory.
|
---|
638 | // Fallback to the old method.
|
---|
639 | // workitem 8098: ok
|
---|
640 | //s.Seek(zf._originPosition, SeekOrigin.Begin);
|
---|
641 | s.Seek(0L, SeekOrigin.Begin);
|
---|
642 | ReadIntoInstance_Orig(zf);
|
---|
643 | }
|
---|
644 | }
|
---|
645 | catch (Exception ex1)
|
---|
646 | {
|
---|
647 | if (zf._ReadStreamIsOurs && zf._readstream != null)
|
---|
648 | {
|
---|
649 | try
|
---|
650 | {
|
---|
651 | #if NETCF
|
---|
652 | zf._readstream.Close();
|
---|
653 | #else
|
---|
654 | zf._readstream.Dispose();
|
---|
655 | #endif
|
---|
656 | zf._readstream = null;
|
---|
657 | }
|
---|
658 | finally { }
|
---|
659 | }
|
---|
660 |
|
---|
661 | throw new ZipException("Cannot read that as a ZipFile", ex1);
|
---|
662 | }
|
---|
663 |
|
---|
664 | // the instance has been read in
|
---|
665 | zf._contentsChanged = false;
|
---|
666 | }
|
---|
667 |
|
---|
668 |
|
---|
669 |
|
---|
670 | private static void Zip64SeekToCentralDirectory(ZipFile zf)
|
---|
671 | {
|
---|
672 | Stream s = zf.ReadStream;
|
---|
673 | byte[] block = new byte[16];
|
---|
674 |
|
---|
675 | // seek back to find the ZIP64 EoCD.
|
---|
676 | // I think this might not work for .NET CF ?
|
---|
677 | s.Seek(-40, SeekOrigin.Current);
|
---|
678 | s.Read(block, 0, 16);
|
---|
679 |
|
---|
680 | Int64 offset64 = BitConverter.ToInt64(block, 8);
|
---|
681 | zf._OffsetOfCentralDirectory = 0xFFFFFFFF;
|
---|
682 | zf._OffsetOfCentralDirectory64 = offset64;
|
---|
683 | // change for workitem 8098
|
---|
684 | s.Seek(offset64, SeekOrigin.Begin);
|
---|
685 | //zf.SeekFromOrigin(Offset64);
|
---|
686 |
|
---|
687 | uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
|
---|
688 | if (datum != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature)
|
---|
689 | throw new BadReadException(String.Format(" Bad signature (0x{0:X8}) looking for ZIP64 EoCD Record at position 0x{1:X8}", datum, s.Position));
|
---|
690 |
|
---|
691 | s.Read(block, 0, 8);
|
---|
692 | Int64 Size = BitConverter.ToInt64(block, 0);
|
---|
693 |
|
---|
694 | block = new byte[Size];
|
---|
695 | s.Read(block, 0, block.Length);
|
---|
696 |
|
---|
697 | offset64 = BitConverter.ToInt64(block, 36);
|
---|
698 | // change for workitem 8098
|
---|
699 | s.Seek(offset64, SeekOrigin.Begin);
|
---|
700 | //zf.SeekFromOrigin(Offset64);
|
---|
701 | }
|
---|
702 |
|
---|
703 |
|
---|
704 | private static uint ReadFirstFourBytes(Stream s)
|
---|
705 | {
|
---|
706 | uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
|
---|
707 | return datum;
|
---|
708 | }
|
---|
709 |
|
---|
710 |
|
---|
711 |
|
---|
712 | private static void ReadCentralDirectory(ZipFile zf)
|
---|
713 | {
|
---|
714 | // We must have the central directory footer record, in order to properly
|
---|
715 | // read zip dir entries from the central directory. This because the logic
|
---|
716 | // knows when to open a spanned file when the volume number for the central
|
---|
717 | // directory differs from the volume number for the zip entry. The
|
---|
718 | // _diskNumberWithCd was set when originally finding the offset for the
|
---|
719 | // start of the Central Directory.
|
---|
720 |
|
---|
721 | // workitem 9214
|
---|
722 | bool inputUsesZip64 = false;
|
---|
723 | ZipEntry de;
|
---|
724 | // in lieu of hashset, use a dictionary
|
---|
725 | var previouslySeen = new Dictionary<String,object>();
|
---|
726 | while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null)
|
---|
727 | {
|
---|
728 | de.ResetDirEntry();
|
---|
729 | zf.OnReadEntry(true, null);
|
---|
730 |
|
---|
731 | if (zf.Verbose)
|
---|
732 | zf.StatusMessageTextWriter.WriteLine("entry {0}", de.FileName);
|
---|
733 |
|
---|
734 | zf._entries.Add(de.FileName,de);
|
---|
735 |
|
---|
736 | // workitem 9214
|
---|
737 | if (de._InputUsesZip64) inputUsesZip64 = true;
|
---|
738 | previouslySeen.Add(de.FileName, null); // to prevent dupes
|
---|
739 | }
|
---|
740 |
|
---|
741 | // workitem 9214; auto-set the zip64 flag
|
---|
742 | if (inputUsesZip64) zf.UseZip64WhenSaving = Zip64Option.Always;
|
---|
743 |
|
---|
744 | // workitem 8299
|
---|
745 | if (zf._locEndOfCDS > 0)
|
---|
746 | zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin);
|
---|
747 |
|
---|
748 | ReadCentralDirectoryFooter(zf);
|
---|
749 |
|
---|
750 | if (zf.Verbose && !String.IsNullOrEmpty(zf.Comment))
|
---|
751 | zf.StatusMessageTextWriter.WriteLine("Zip file Comment: {0}", zf.Comment);
|
---|
752 |
|
---|
753 | // We keep the read stream open after reading.
|
---|
754 |
|
---|
755 | if (zf.Verbose)
|
---|
756 | zf.StatusMessageTextWriter.WriteLine("read in {0} entries.", zf._entries.Count);
|
---|
757 |
|
---|
758 | zf.OnReadCompleted();
|
---|
759 | }
|
---|
760 |
|
---|
761 |
|
---|
762 |
|
---|
763 |
|
---|
764 | // build the TOC by reading each entry in the file.
|
---|
765 | private static void ReadIntoInstance_Orig(ZipFile zf)
|
---|
766 | {
|
---|
767 | zf.OnReadStarted();
|
---|
768 | //zf._entries = new System.Collections.Generic.List<ZipEntry>();
|
---|
769 | zf._entries = new System.Collections.Generic.Dictionary<String,ZipEntry>();
|
---|
770 |
|
---|
771 | ZipEntry e;
|
---|
772 | if (zf.Verbose)
|
---|
773 | if (zf.Name == null)
|
---|
774 | zf.StatusMessageTextWriter.WriteLine("Reading zip from stream...");
|
---|
775 | else
|
---|
776 | zf.StatusMessageTextWriter.WriteLine("Reading zip {0}...", zf.Name);
|
---|
777 |
|
---|
778 | // work item 6647: PK00 (packed to removable disk)
|
---|
779 | bool firstEntry = true;
|
---|
780 | ZipContainer zc = new ZipContainer(zf);
|
---|
781 | while ((e = ZipEntry.ReadEntry(zc, firstEntry)) != null)
|
---|
782 | {
|
---|
783 | if (zf.Verbose)
|
---|
784 | zf.StatusMessageTextWriter.WriteLine(" {0}", e.FileName);
|
---|
785 |
|
---|
786 | zf._entries.Add(e.FileName,e);
|
---|
787 | firstEntry = false;
|
---|
788 | }
|
---|
789 |
|
---|
790 | // read the zipfile's central directory structure here.
|
---|
791 | // workitem 9912
|
---|
792 | // But, because it may be corrupted, ignore errors.
|
---|
793 | try
|
---|
794 | {
|
---|
795 | ZipEntry de;
|
---|
796 | // in lieu of hashset, use a dictionary
|
---|
797 | var previouslySeen = new Dictionary<String,Object>();
|
---|
798 | while ((de = ZipEntry.ReadDirEntry(zf, previouslySeen)) != null)
|
---|
799 | {
|
---|
800 | // Housekeeping: Since ZipFile exposes ZipEntry elements in the enumerator,
|
---|
801 | // we need to copy the comment that we grab from the ZipDirEntry
|
---|
802 | // into the ZipEntry, so the application can access the comment.
|
---|
803 | // Also since ZipEntry is used to Write zip files, we need to copy the
|
---|
804 | // file attributes to the ZipEntry as appropriate.
|
---|
805 | ZipEntry e1 = zf._entries[de.FileName];
|
---|
806 | if (e1 != null)
|
---|
807 | {
|
---|
808 | e1._Comment = de.Comment;
|
---|
809 | if (de.IsDirectory) e1.MarkAsDirectory();
|
---|
810 | }
|
---|
811 | previouslySeen.Add(de.FileName,null); // to prevent dupes
|
---|
812 | }
|
---|
813 |
|
---|
814 | // workitem 8299
|
---|
815 | if (zf._locEndOfCDS > 0)
|
---|
816 | zf.ReadStream.Seek(zf._locEndOfCDS, SeekOrigin.Begin);
|
---|
817 |
|
---|
818 | ReadCentralDirectoryFooter(zf);
|
---|
819 |
|
---|
820 | if (zf.Verbose && !String.IsNullOrEmpty(zf.Comment))
|
---|
821 | zf.StatusMessageTextWriter.WriteLine("Zip file Comment: {0}", zf.Comment);
|
---|
822 | }
|
---|
823 | catch (ZipException) { }
|
---|
824 | catch (IOException) { }
|
---|
825 |
|
---|
826 | zf.OnReadCompleted();
|
---|
827 | }
|
---|
828 |
|
---|
829 |
|
---|
830 |
|
---|
831 |
|
---|
832 | private static void ReadCentralDirectoryFooter(ZipFile zf)
|
---|
833 | {
|
---|
834 | Stream s = zf.ReadStream;
|
---|
835 | int signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
|
---|
836 |
|
---|
837 | byte[] block = null;
|
---|
838 | int j = 0;
|
---|
839 | if (signature == ZipConstants.Zip64EndOfCentralDirectoryRecordSignature)
|
---|
840 | {
|
---|
841 | // We have a ZIP64 EOCD
|
---|
842 | // This data block is 4 bytes sig, 8 bytes size, 44 bytes fixed data,
|
---|
843 | // followed by a variable-sized extension block. We have read the sig already.
|
---|
844 | // 8 - datasize (64 bits)
|
---|
845 | // 2 - version made by
|
---|
846 | // 2 - version needed to extract
|
---|
847 | // 4 - number of this disk
|
---|
848 | // 4 - number of the disk with the start of the CD
|
---|
849 | // 8 - total number of entries in the CD on this disk
|
---|
850 | // 8 - total number of entries in the CD
|
---|
851 | // 8 - size of the CD
|
---|
852 | // 8 - offset of the CD
|
---|
853 | // -----------------------
|
---|
854 | // 52 bytes
|
---|
855 |
|
---|
856 | block = new byte[8 + 44];
|
---|
857 | s.Read(block, 0, block.Length);
|
---|
858 |
|
---|
859 | Int64 DataSize = BitConverter.ToInt64(block, 0); // == 44 + the variable length
|
---|
860 |
|
---|
861 | if (DataSize < 44)
|
---|
862 | throw new ZipException("Bad size in the ZIP64 Central Directory.");
|
---|
863 |
|
---|
864 | zf._versionMadeBy = BitConverter.ToUInt16(block, j);
|
---|
865 | j += 2;
|
---|
866 | zf._versionNeededToExtract = BitConverter.ToUInt16(block, j);
|
---|
867 | j += 2;
|
---|
868 | zf._diskNumberWithCd = BitConverter.ToUInt32(block, j);
|
---|
869 | j += 2;
|
---|
870 |
|
---|
871 | //zf._diskNumberWithCd++; // hack!!
|
---|
872 |
|
---|
873 | // read the extended block
|
---|
874 | block = new byte[DataSize - 44];
|
---|
875 | s.Read(block, 0, block.Length);
|
---|
876 | // discard the result
|
---|
877 |
|
---|
878 | signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
|
---|
879 | if (signature != ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature)
|
---|
880 | throw new ZipException("Inconsistent metadata in the ZIP64 Central Directory.");
|
---|
881 |
|
---|
882 | block = new byte[16];
|
---|
883 | s.Read(block, 0, block.Length);
|
---|
884 | // discard the result
|
---|
885 |
|
---|
886 | signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
|
---|
887 | }
|
---|
888 |
|
---|
889 | // Throw if this is not a signature for "end of central directory record"
|
---|
890 | // This is a sanity check.
|
---|
891 | if (signature != ZipConstants.EndOfCentralDirectorySignature)
|
---|
892 | {
|
---|
893 | s.Seek(-4, SeekOrigin.Current);
|
---|
894 | throw new BadReadException(String.Format("Bad signature ({0:X8}) at position 0x{1:X8}",
|
---|
895 | signature, s.Position));
|
---|
896 | }
|
---|
897 |
|
---|
898 | // read the End-of-Central-Directory-Record
|
---|
899 | block = new byte[16];
|
---|
900 | zf.ReadStream.Read(block, 0, block.Length);
|
---|
901 |
|
---|
902 | // off sz data
|
---|
903 | // -------------------------------------------------------
|
---|
904 | // 0 4 end of central dir signature (0x06054b50)
|
---|
905 | // 4 2 number of this disk
|
---|
906 | // 6 2 number of the disk with start of the central directory
|
---|
907 | // 8 2 total number of entries in the central directory on this disk
|
---|
908 | // 10 2 total number of entries in the central directory
|
---|
909 | // 12 4 size of the central directory
|
---|
910 | // 16 4 offset of start of central directory with respect to the starting disk number
|
---|
911 | // 20 2 ZIP file comment length
|
---|
912 | // 22 ?? ZIP file comment
|
---|
913 |
|
---|
914 | if (zf._diskNumberWithCd == 0)
|
---|
915 | {
|
---|
916 | zf._diskNumberWithCd = BitConverter.ToUInt16(block, 2);
|
---|
917 | //zf._diskNumberWithCd++; // hack!!
|
---|
918 | }
|
---|
919 |
|
---|
920 | // read the comment here
|
---|
921 | ReadZipFileComment(zf);
|
---|
922 | }
|
---|
923 |
|
---|
924 |
|
---|
925 |
|
---|
926 | private static void ReadZipFileComment(ZipFile zf)
|
---|
927 | {
|
---|
928 | // read the comment here
|
---|
929 | byte[] block = new byte[2];
|
---|
930 | zf.ReadStream.Read(block, 0, block.Length);
|
---|
931 |
|
---|
932 | Int16 commentLength = (short)(block[0] + block[1] * 256);
|
---|
933 | if (commentLength > 0)
|
---|
934 | {
|
---|
935 | block = new byte[commentLength];
|
---|
936 | zf.ReadStream.Read(block, 0, block.Length);
|
---|
937 |
|
---|
938 | // workitem 10392 - prefer ProvisionalAlternateEncoding,
|
---|
939 | // first. The fix for workitem 6513 tried to use UTF8
|
---|
940 | // only as necessary, but that is impossible to test
|
---|
941 | // for, in this direction. There's no way to know what
|
---|
942 | // characters the already-encoded bytes refer
|
---|
943 | // to. Therefore, must do what the user tells us.
|
---|
944 |
|
---|
945 | string s1 = zf.AlternateEncoding.GetString(block, 0, block.Length);
|
---|
946 | zf.Comment = s1;
|
---|
947 | }
|
---|
948 | }
|
---|
949 |
|
---|
950 |
|
---|
951 | // private static bool BlocksAreEqual(byte[] a, byte[] b)
|
---|
952 | // {
|
---|
953 | // if (a.Length != b.Length) return false;
|
---|
954 | // for (int i = 0; i < a.Length; i++)
|
---|
955 | // {
|
---|
956 | // if (a[i] != b[i]) return false;
|
---|
957 | // }
|
---|
958 | // return true;
|
---|
959 | // }
|
---|
960 |
|
---|
961 |
|
---|
962 |
|
---|
963 | /// <summary>
|
---|
964 | /// Checks the given file to see if it appears to be a valid zip file.
|
---|
965 | /// </summary>
|
---|
966 | /// <remarks>
|
---|
967 | ///
|
---|
968 | /// <para>
|
---|
969 | /// Calling this method is equivalent to calling <see cref="IsZipFile(string,
|
---|
970 | /// bool)"/> with the testExtract parameter set to false.
|
---|
971 | /// </para>
|
---|
972 | /// </remarks>
|
---|
973 | ///
|
---|
974 | /// <param name="fileName">The file to check.</param>
|
---|
975 | /// <returns>true if the file appears to be a zip file.</returns>
|
---|
976 | public static bool IsZipFile(string fileName)
|
---|
977 | {
|
---|
978 | return IsZipFile(fileName, false);
|
---|
979 | }
|
---|
980 |
|
---|
981 |
|
---|
982 | /// <summary>
|
---|
983 | /// Checks a file to see if it is a valid zip file.
|
---|
984 | /// </summary>
|
---|
985 | ///
|
---|
986 | /// <remarks>
|
---|
987 | /// <para>
|
---|
988 | /// This method opens the specified zip file, reads in the zip archive,
|
---|
989 | /// verifying the ZIP metadata as it reads.
|
---|
990 | /// </para>
|
---|
991 | ///
|
---|
992 | /// <para>
|
---|
993 | /// If everything succeeds, then the method returns true. If anything fails -
|
---|
994 | /// for example if an incorrect signature or CRC is found, indicating a
|
---|
995 | /// corrupt file, the the method returns false. This method also returns
|
---|
996 | /// false for a file that does not exist.
|
---|
997 | /// </para>
|
---|
998 | ///
|
---|
999 | /// <para>
|
---|
1000 | /// If <paramref name="testExtract"/> is true, as part of its check, this
|
---|
1001 | /// method reads in the content for each entry, expands it, and checks CRCs.
|
---|
1002 | /// This provides an additional check beyond verifying the zip header and
|
---|
1003 | /// directory data.
|
---|
1004 | /// </para>
|
---|
1005 | ///
|
---|
1006 | /// <para>
|
---|
1007 | /// If <paramref name="testExtract"/> is true, and if any of the zip entries
|
---|
1008 | /// are protected with a password, this method will return false. If you want
|
---|
1009 | /// to verify a <c>ZipFile</c> that has entries which are protected with a
|
---|
1010 | /// password, you will need to do that manually.
|
---|
1011 | /// </para>
|
---|
1012 | ///
|
---|
1013 | /// </remarks>
|
---|
1014 | ///
|
---|
1015 | /// <param name="fileName">The zip file to check.</param>
|
---|
1016 | /// <param name="testExtract">true if the caller wants to extract each entry.</param>
|
---|
1017 | /// <returns>true if the file contains a valid zip file.</returns>
|
---|
1018 | public static bool IsZipFile(string fileName, bool testExtract)
|
---|
1019 | {
|
---|
1020 | bool result = false;
|
---|
1021 | try
|
---|
1022 | {
|
---|
1023 | if (!File.Exists(fileName)) return false;
|
---|
1024 |
|
---|
1025 | using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
---|
1026 | {
|
---|
1027 | result = IsZipFile(s, testExtract);
|
---|
1028 | }
|
---|
1029 | }
|
---|
1030 | catch (IOException) { }
|
---|
1031 | catch (ZipException) { }
|
---|
1032 | return result;
|
---|
1033 | }
|
---|
1034 |
|
---|
1035 |
|
---|
1036 | /// <summary>
|
---|
1037 | /// Checks a stream to see if it contains a valid zip archive.
|
---|
1038 | /// </summary>
|
---|
1039 | ///
|
---|
1040 | /// <remarks>
|
---|
1041 | /// <para>
|
---|
1042 | /// This method reads the zip archive contained in the specified stream, verifying
|
---|
1043 | /// the ZIP metadata as it reads. If testExtract is true, this method also extracts
|
---|
1044 | /// each entry in the archive, dumping all the bits into <see cref="Stream.Null"/>.
|
---|
1045 | /// </para>
|
---|
1046 | ///
|
---|
1047 | /// <para>
|
---|
1048 | /// If everything succeeds, then the method returns true. If anything fails -
|
---|
1049 | /// for example if an incorrect signature or CRC is found, indicating a corrupt
|
---|
1050 | /// file, the the method returns false. This method also returns false for a
|
---|
1051 | /// file that does not exist.
|
---|
1052 | /// </para>
|
---|
1053 | ///
|
---|
1054 | /// <para>
|
---|
1055 | /// If <c>testExtract</c> is true, this method reads in the content for each
|
---|
1056 | /// entry, expands it, and checks CRCs. This provides an additional check
|
---|
1057 | /// beyond verifying the zip header data.
|
---|
1058 | /// </para>
|
---|
1059 | ///
|
---|
1060 | /// <para>
|
---|
1061 | /// If <c>testExtract</c> is true, and if any of the zip entries are protected
|
---|
1062 | /// with a password, this method will return false. If you want to verify a
|
---|
1063 | /// ZipFile that has entries which are protected with a password, you will need
|
---|
1064 | /// to do that manually.
|
---|
1065 | /// </para>
|
---|
1066 | /// </remarks>
|
---|
1067 | ///
|
---|
1068 | /// <seealso cref="IsZipFile(string, bool)"/>
|
---|
1069 | ///
|
---|
1070 | /// <param name="stream">The stream to check.</param>
|
---|
1071 | /// <param name="testExtract">true if the caller wants to extract each entry.</param>
|
---|
1072 | /// <returns>true if the stream contains a valid zip archive.</returns>
|
---|
1073 | public static bool IsZipFile(Stream stream, bool testExtract)
|
---|
1074 | {
|
---|
1075 | if (stream == null)
|
---|
1076 | throw new ArgumentNullException("stream");
|
---|
1077 |
|
---|
1078 | bool result = false;
|
---|
1079 | try
|
---|
1080 | {
|
---|
1081 | if (!stream.CanRead) return false;
|
---|
1082 |
|
---|
1083 | var bitBucket = Stream.Null;
|
---|
1084 |
|
---|
1085 | using (ZipFile zip1 = ZipFile.Read(stream, null, null, null))
|
---|
1086 | {
|
---|
1087 | if (testExtract)
|
---|
1088 | {
|
---|
1089 | foreach (var e in zip1)
|
---|
1090 | {
|
---|
1091 | if (!e.IsDirectory)
|
---|
1092 | {
|
---|
1093 | e.Extract(bitBucket);
|
---|
1094 | }
|
---|
1095 | }
|
---|
1096 | }
|
---|
1097 | }
|
---|
1098 | result = true;
|
---|
1099 | }
|
---|
1100 | catch (IOException) { }
|
---|
1101 | catch (ZipException) { }
|
---|
1102 | return result;
|
---|
1103 | }
|
---|
1104 |
|
---|
1105 |
|
---|
1106 |
|
---|
1107 |
|
---|
1108 | }
|
---|
1109 |
|
---|
1110 | }
|
---|