root/trunk/Updater/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs @ 597

Wersja 597, 19.6 KB (wprowadzona przez marek, 17 years temu)

re #165

RevLine 
[597]1// ZipInputStream.cs
2//
3// Copyright (C) 2001 Mike Krueger
4// Copyright (C) 2004 John Reilly
5//
6// This file was translated from java, it was part of the GNU Classpath
7// Copyright (C) 2001 Free Software Foundation, Inc.
8//
9// This program is free software; you can redistribute it and/or
10// modify it under the terms of the GNU General Public License
11// as published by the Free Software Foundation; either version 2
12// of the License, or (at your option) any later version.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17// GNU General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License
20// along with this program; if not, write to the Free Software
21// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22//
23// Linking this library statically or dynamically with other modules is
24// making a combined work based on this library.  Thus, the terms and
25// conditions of the GNU General Public License cover the whole
26// combination.
27//
28// As a special exception, the copyright holders of this library give you
29// permission to link this library with independent modules to produce an
30// executable, regardless of the license terms of these independent
31// modules, and to copy and distribute the resulting executable under
32// terms of your choice, provided that you also meet, for each linked
33// independent module, the terms and conditions of the license of that
34// module.  An independent module is a module which is not derived from
35// or based on this library.  If you modify this library, you may extend
36// this exception to your version of the library, but you are not
37// obligated to do so.  If you do not wish to do so, delete this
38// exception statement from your version.
39
40using System;
41using System.Text;
42using System.IO;
43
44using ICSharpCode.SharpZipLib.Checksums;
45using ICSharpCode.SharpZipLib.Zip.Compression;
46using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
47
48#if !NETCF_1_0
49using ICSharpCode.SharpZipLib.Encryption;
50#endif
51
52namespace ICSharpCode.SharpZipLib.Zip
53{
54        /// <summary>
55        /// This is an InflaterInputStream that reads the files baseInputStream an zip archive
56        /// one after another.  It has a special method to get the zip entry of
57        /// the next file.  The zip entry contains information about the file name
58        /// size, compressed size, Crc, etc.
59        /// It includes support for Stored and Deflated entries.
60        /// <br/>
61        /// <br/>Author of the original java version : Jochen Hoenicke
62        /// </summary>
63        ///
64        /// <example> This sample shows how to read a zip file
65        /// <code lang="C#">
66        /// using System;
67        /// using System.Text;
68        /// using System.IO;
69        ///
70        /// using ICSharpCode.SharpZipLib.Zip;
71        ///
72        /// class MainClass
73        /// {
74        ///     public static void Main(string[] args)
75        ///     {
76        ///             using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) {
77        ///
78        ///                     ZipEntry theEntry;
79        ///                     while ((theEntry = s.GetNextEntry()) != null) {
80        ///                             int size = 2048;
81        ///                             byte[] data = new byte[2048];
82        ///
83        ///                             Console.Write("Show contents (y/n) ?");
84        ///                             if (Console.ReadLine() == "y") {
85        ///                                     while (true) {
86        ///                                             size = s.Read(data, 0, data.Length);
87        ///                                             if (size > 0) {
88        ///                                                     Console.Write(new ASCIIEncoding().GetString(data, 0, size));
89        ///                                             } else {
90        ///                                                     break;
91        ///                                             }
92        ///                                     }
93        ///                             }
94        ///                     }
95        ///             }
96        ///     }
97        /// }
98        /// </code>
99        /// </example>
100        public class ZipInputStream : InflaterInputStream
101        {
102                #region Instance Fields
103
104                /// <summary>
105                /// Delegate for reading bytes from a stream.
106                /// </summary>
107                delegate int ReadDataHandler(byte[] b, int offset, int length);
108               
109                /// <summary>
110                /// The current reader this instance.
111                /// </summary>
112                ReadDataHandler internalReader;
113               
114                Crc32 crc = new Crc32();
115                ZipEntry entry;
116               
117                long size;
118                int method;
119                int flags;
120                string password;
121                #endregion
122
123                #region Constructors
124                /// <summary>
125                /// Creates a new Zip input stream, for reading a zip archive.
126                /// </summary>
127                /// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param>
128                public ZipInputStream(Stream baseInputStream)
129                        : base(baseInputStream, new Inflater(true))
130                {
131                        internalReader = new ReadDataHandler(ReadingNotAvailable);
132                }
133                #endregion
134               
135                /// <summary>
136                /// Optional password used for encryption when non-null
137                /// </summary>
138                /// <value>A password for all encrypted <see cref="ZipEntry">entries </see> in this <see cref="ZipInputStream"/></value>
139                public string Password
140                {
141                        get {
142                                return password;
143                        }
144                        set {
145                                password = value;
146                        }
147                }
148               
149               
150                /// <summary>
151                /// Gets a value indicating if there is a current entry and it can be decompressed
152                /// </summary>
153                /// <remarks>
154                /// The entry can only be decompressed if the library supports the zip features required to extract it.
155                /// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details.
156                /// </remarks>
157                public bool CanDecompressEntry {
158                        get {
159                                return (entry != null) && entry.CanDecompress;
160                        }
161                }
162               
163                /// <summary>
164                /// Advances to the next entry in the archive
165                /// </summary>
166                /// <returns>
167                /// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries.
168                /// </returns>
169                /// <remarks>
170                /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called.
171                /// </remarks>
172                /// <exception cref="InvalidOperationException">
173                /// Input stream is closed
174                /// </exception>
175                /// <exception cref="ZipException">
176                /// Password is not set, password is invalid, compression method is invalid,
177                /// version required to extract is not supported
178                /// </exception>
179                public ZipEntry GetNextEntry()
180                {
181                        if (crc == null) {
182                                throw new InvalidOperationException("Closed.");
183                        }
184                       
185                        if (entry != null) {
186                                CloseEntry();
187                        }
188                       
189                        int header = inputBuffer.ReadLeInt();
190                       
191                        if (header == ZipConstants.CentralHeaderSignature ||
192                                header == ZipConstants.EndOfCentralDirectorySignature ||
193                                header == ZipConstants.CentralHeaderDigitalSignature ||
194                                header == ZipConstants.ArchiveExtraDataSignature ||
195                                header == ZipConstants.Zip64CentralFileHeaderSignature) {
196                                // No more individual entries exist
197                                Close();
198                                return null;
199                        }
200                       
201                        // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
202                        // Spanning signature is same as descriptor signature and is untested as yet.
203                        if ( (header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature) ) {
204                                header = inputBuffer.ReadLeInt();
205                        }
206                       
207                        if (header != ZipConstants.LocalHeaderSignature) {
208                                throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
209                        }
210                       
211                        short versionRequiredToExtract = (short)inputBuffer.ReadLeShort();
212                       
213                        flags          = inputBuffer.ReadLeShort();
214                        method         = inputBuffer.ReadLeShort();
215                        uint dostime   = (uint)inputBuffer.ReadLeInt();
216                        int crc2       = inputBuffer.ReadLeInt();
217                        csize          = inputBuffer.ReadLeInt();
218                        size           = inputBuffer.ReadLeInt();
219                        int nameLen    = inputBuffer.ReadLeShort();
220                        int extraLen   = inputBuffer.ReadLeShort();
221                       
222                        bool isCrypted = (flags & 1) == 1;
223                       
224                        byte[] buffer = new byte[nameLen];
225                        inputBuffer.ReadRawBuffer(buffer);
226                       
227                        string name = ZipConstants.ConvertToStringExt(flags, buffer);
228                       
229                        entry = new ZipEntry(name, versionRequiredToExtract);
230                        entry.Flags = flags;
231                       
232                        entry.CompressionMethod = (CompressionMethod)method;
233                       
234                        if ((flags & 8) == 0) {
235                                entry.Crc  = crc2 & 0xFFFFFFFFL;
236                                entry.Size = size & 0xFFFFFFFFL;
237                                entry.CompressedSize = csize & 0xFFFFFFFFL;
238
239                                entry.CryptoCheckValue = (byte)((crc2 >> 24) & 0xff);
240
241                        } else {
242                               
243                                // This allows for GNU, WinZip and possibly other archives, the PKZIP spec
244                                // says these values are zero under these circumstances.
245                                if (crc2 != 0) {
246                                        entry.Crc = crc2 & 0xFFFFFFFFL;
247                                }
248                               
249                                if (size != 0) {
250                                        entry.Size = size & 0xFFFFFFFFL;
251                                }
252
253                                if (csize != 0) {
254                                        entry.CompressedSize = csize & 0xFFFFFFFFL;
255                                }
256
257                                entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
258                        }
259                       
260                        entry.DosTime = dostime;
261
262                        // If local header requires Zip64 is true then the extended header should contain
263                        // both values.
264
265                        // Handle extra data if present.  This can set/alter some fields of the entry.
266                        if (extraLen > 0) {
267                                byte[] extra = new byte[extraLen];
268                                inputBuffer.ReadRawBuffer(extra);
269                                entry.ExtraData = extra;
270                        }
271
272                        entry.ProcessExtraData(true);
273                        if ( entry.CompressedSize >= 0 ) {
274                                csize = entry.CompressedSize;
275                        }
276
277                        if ( entry.Size >= 0 ) {
278                                size = entry.Size;
279                        }
280                       
281                        if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))) {
282                                throw new ZipException("Stored, but compressed != uncompressed");
283                        }
284
285                        // Determine how to handle reading of data if this is attempted.
286                        if (entry.IsCompressionMethodSupported()) {
287                                internalReader = new ReadDataHandler(InitialRead);
288                        } else {
289                                internalReader = new ReadDataHandler(ReadingNotSupported);
290                        }
291                       
292                        return entry;
293                }
294               
295                /// <summary>
296                /// Read data descriptor at the end of compressed data.
297                /// </summary>
298                void ReadDataDescriptor()
299                {
300                        if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature) {
301                                throw new ZipException("Data descriptor signature not found");
302                        }
303                       
304                        entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
305                       
306                        if ( entry.LocalHeaderRequiresZip64 ) {
307                                csize = inputBuffer.ReadLeLong();
308                                size = inputBuffer.ReadLeLong();
309                        } else {
310                                csize = inputBuffer.ReadLeInt();
311                                size = inputBuffer.ReadLeInt();
312                        }
313                        entry.CompressedSize = csize;
314                        entry.Size = size;
315                }
316
317                /// <summary>
318                /// Complete cleanup as the final part of closing.
319                /// </summary>
320                /// <param name="testCrc">True if the crc value should be tested</param>
321                void CompleteCloseEntry(bool testCrc)
322                {
323                        StopDecrypting();
324                               
325                        if ((flags & 8) != 0) {
326                                ReadDataDescriptor();
327                        }
328                               
329                        size = 0;
330
331                        if ( testCrc &&
332                                ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) {
333                                throw new ZipException("CRC mismatch");
334                        }
335
336                        crc.Reset();
337
338                        if (method == (int)CompressionMethod.Deflated) {
339                                inf.Reset();
340                        }
341                        entry = null;
342                }
343
344                /// <summary>
345                /// Closes the current zip entry and moves to the next one.
346                /// </summary>
347                /// <exception cref="InvalidOperationException">
348                /// The stream is closed
349                /// </exception>
350                /// <exception cref="ZipException">
351                /// The Zip stream ends early
352                /// </exception>
353                public void CloseEntry()
354                {
355                        if (crc == null) {
356                                throw new InvalidOperationException("Closed");
357                        }
358                       
359                        if (entry == null) {
360                                return;
361                        }
362                       
363                        if (method == (int)CompressionMethod.Deflated) {
364                                if ((flags & 8) != 0) {
365                                        // We don't know how much we must skip, read until end.
366                                        byte[] tmp = new byte[4096];
367
368                                        // Read will close this entry
369                                        while (Read(tmp, 0, tmp.Length) > 0) {
370                                        }
371                                        return;
372                                }
373
374                                csize -= inf.TotalIn;
375                                inputBuffer.Available += inf.RemainingInput;
376                        }
377               
378                        if ( (inputBuffer.Available > csize) && (csize >= 0) ) {
379                                inputBuffer.Available = (int)((long)inputBuffer.Available - csize);
380                        } else {
381                                csize -= inputBuffer.Available;
382                                inputBuffer.Available = 0;
383                                while (csize != 0) {
384                                        long skipped = base.Skip(csize);
385                               
386                                        if (skipped <= 0) {
387                                                throw new ZipException("Zip archive ends early.");
388                                        }
389                               
390                                        csize -= skipped;
391                                }
392                        }
393
394                        CompleteCloseEntry(false);
395                }
396               
397                /// <summary>
398                /// Returns 1 if there is an entry available
399                /// Otherwise returns 0.
400                /// </summary>
401                public override int Available {
402                        get {
403                                return entry != null ? 1 : 0;
404                        }
405                }
406               
407                /// <summary>
408                /// Returns the current size that can be read from the current entry if available
409                /// </summary>
410                /// <exception cref="ZipException">Thrown if the entry size is not known.</exception>
411                /// <exception cref="InvalidOperationException">Thrown if no entry is currently available.</exception>
412                public override long Length
413                {
414                        get {
415                                if ( entry != null ) {
416                                        if ( entry.Size >= 0 ) {
417                                                return entry.Size;
418                                        } else {
419                                                throw new ZipException("Length not available for the current entry");
420                                        }
421                                }
422                                else {
423                                        throw new InvalidOperationException("No current entry");
424                                }
425                        }
426                               
427                }
428               
429                /// <summary>
430                /// Reads a byte from the current zip entry.
431                /// </summary>
432                /// <returns>
433                /// The byte or -1 if end of stream is reached.
434                /// </returns>
435                public override int ReadByte()
436                {
437                        byte[] b = new byte[1];
438                        if (Read(b, 0, 1) <= 0) {
439                                return -1;
440                        }
441                        return b[0] & 0xff;
442                }
443               
444                /// <summary>
445                /// Handle attempts to read by throwing an <see cref="InvalidOperationException"/>.
446                /// </summary>
447                /// <param name="destination">The destination array to store data in.</param>
448                /// <param name="offset">The offset at which data read should be stored.</param>
449                /// <param name="count">The maximum number of bytes to read.</param>
450                /// <returns>Returns the number of bytes actually read.</returns>
451                int ReadingNotAvailable(byte[] destination, int offset, int count)
452                {
453                        throw new InvalidOperationException("Unable to read from this stream");
454                }
455               
456                /// <summary>
457                /// Handle attempts to read from this entry by throwing an exception
458                /// </summary>
459                int ReadingNotSupported(byte[] destination, int offset, int count)
460                {
461                        throw new ZipException("The compression method for this entry is not supported");
462                }
463               
464                /// <summary>
465                /// Perform the initial read on an entry which may include
466                /// reading encryption headers and setting up inflation.
467                /// </summary>
468                /// <param name="destination">The destination to fill with data read.</param>
469                /// <param name="offset">The offset to start reading at.</param>
470                /// <param name="count">The maximum number of bytes to read.</param>
471                /// <returns>The actual number of bytes read.</returns>
472                int InitialRead(byte[] destination, int offset, int count)
473                {
474                        if ( !CanDecompressEntry ) {
475                                throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version.ToString() + ")");
476                        }
477                       
478                        // Handle encryption if required.
479                        if (entry.IsCrypted) {
480#if NETCF_1_0
481                                throw new ZipException("Encryption not supported for Compact Framework 1.0");
482#else
483                                if (password == null) {
484                                        throw new ZipException("No password set.");
485                                }
486                               
487                                // Generate and set crypto transform...
488                                PkzipClassicManaged managed = new PkzipClassicManaged();
489                                byte[] key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password));
490                               
491                                inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
492                               
493                                byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
494                                inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
495
496                                if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) {
497                                        throw new ZipException("Invalid password");
498                                }
499
500                                if (csize >= ZipConstants.CryptoHeaderSize) {
501                                        csize -= ZipConstants.CryptoHeaderSize;
502                                }
503                                else if ( (entry.Flags & (int)GeneralBitFlags.Descriptor) == 0 ) {
504                                        throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
505                                }
506#endif                         
507                        } else {
508#if !NETCF_1_0
509                                inputBuffer.CryptoTransform = null;
510#endif                         
511                        }
512
513                        if ((csize > 0) || ((flags & (int)GeneralBitFlags.Descriptor) != 0)) {
514                                if ((method == (int)CompressionMethod.Deflated) && (inputBuffer.Available > 0)) {
515                                        inputBuffer.SetInflaterInput(inf);
516                                }
517
518                                internalReader = new ReadDataHandler(BodyRead);
519                                return BodyRead(destination, offset, count);
520                        }
521                        else {
522                                internalReader = new ReadDataHandler(ReadingNotAvailable);
523                                return 0;
524                        }
525                }
526               
527                /// <summary>
528                /// Read a block of bytes from the stream.
529                /// </summary>
530                /// <param name="buffer">The destination for the bytes.</param>
531                /// <param name="offset">The index to start storing data.</param>
532                /// <param name="count">The number of bytes to attempt to read.</param>
533                /// <returns>Returns the number of bytes read.</returns>
534                /// <remarks>Zero bytes read means end of stream.</remarks>
535                public override int Read(byte[] buffer, int offset, int count)
536                {
537                        if ( buffer == null ) {
538                                throw new ArgumentNullException("buffer");
539                        }
540
541                        if ( offset < 0 ) {
542#if NETCF_1_0
543                                throw new ArgumentOutOfRangeException("offset");
544#else
545                                throw new ArgumentOutOfRangeException("offset", "Cannot be negative");
546#endif                         
547                        }
548
549                        if ( count < 0 ) {
550#if NETCF_1_0
551                                throw new ArgumentOutOfRangeException("count");
552#else
553                                throw new ArgumentOutOfRangeException("count", "Cannot be negative");
554#endif
555                        }
556
557                        if ( (buffer.Length - offset) < count ) {
558                                throw new ArgumentException("Invalid offset/count combination");
559                        }
560
561                        return internalReader(buffer, offset, count);
562                }
563               
564                /// <summary>
565                /// Reads a block of bytes from the current zip entry.
566                /// </summary>
567                /// <returns>
568                /// The number of bytes read (this may be less than the length requested, even before the end of stream), or 0 on end of stream.
569                /// </returns>
570                /// <exception name="IOException">
571                /// An i/o error occured.
572                /// </exception>
573                /// <exception cref="ZipException">
574                /// The deflated stream is corrupted.
575                /// </exception>
576                /// <exception cref="InvalidOperationException">
577                /// The stream is not open.
578                /// </exception>
579                int BodyRead(byte[] buffer, int offset, int count)
580                {
581                        if ( crc == null ) {
582                                throw new InvalidOperationException("Closed");
583                        }
584                       
585                        if ( (entry == null) || (count <= 0) ) {
586                                return 0;
587                        }
588
589                        if ( offset + count > buffer.Length ) {
590                                throw new ArgumentException("Offset + count exceeds buffer size");
591                        }
592                       
593                        bool finished = false;
594                       
595                        switch (method) {
596                                case (int)CompressionMethod.Deflated:
597                                        count = base.Read(buffer, offset, count);
598                                        if (count <= 0) {
599                                                if (!inf.IsFinished) {
600                                                        throw new ZipException("Inflater not finished!");
601                                                }
602                                                inputBuffer.Available = inf.RemainingInput;
603                                               
604                                                if ((flags & 8) == 0 && (inf.TotalIn != csize || inf.TotalOut != size)) {
605                                                        throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut);
606                                                }
607                                                inf.Reset();
608                                                finished = true;
609                                        }
610                                        break;
611                                       
612                                case (int)CompressionMethod.Stored:
613                                        if ( (count > csize) && (csize >= 0) ) {
614                                                count = (int)csize;
615                                        }
616                                       
617                                        if ( count > 0 ) {
618                                                count = inputBuffer.ReadClearTextBuffer(buffer, offset, count);
619                                                if (count > 0) {
620                                                        csize -= count;
621                                                        size -= count;
622                                                }
623                                        }
624                                       
625                                        if (csize == 0) {
626                                                finished = true;
627                                        } else {
628                                                if (count < 0) {
629                                                        throw new ZipException("EOF in stored block");
630                                                }
631                                        }
632                                        break;
633                        }
634                       
635                        if (count > 0) {
636                                crc.Update(buffer, offset, count);
637                        }
638                       
639                        if (finished) {
640                                CompleteCloseEntry(true);
641                        }
642
643                        return count;
644                }
645               
646                /// <summary>
647                /// Closes the zip input stream
648                /// </summary>
649                public override void Close()
650                {
651                        internalReader = new ReadDataHandler(ReadingNotAvailable);
652                        crc = null;
653                        entry = null;
654
655                        base.Close();
656                }
657        }
658}
Notatka: Zobacz TracBrowser aby uzyskać więcej informacji.