Statistics
| Branch: | Revision:

root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Zip / ZipInputStream.cs @ 0eea575a

History | View | Annotate | Download (23.4 kB)

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

    
40
using System;
41
using System.IO;
42
using ICSharpCode.SharpZipLib.Silverlight.Checksums;
43
using ICSharpCode.SharpZipLib.Silverlight.Encryption;
44
using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression;
45
using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression.Streams;
46

    
47
namespace ICSharpCode.SharpZipLib.Silverlight.Zip
48
{
49
    /// <summary>
50
    /// This is an InflaterInputStream that reads the files baseInputStream an zip archive
51
    /// one after another.  It has a special method to get the zip entry of
52
    /// the next file.  The zip entry contains information about the file name
53
    /// size, compressed size, Crc, etc.
54
    /// It includes support for Stored and Deflated entries.
55
    /// <br/>
56
    /// <br/>Author of the original java version : Jochen Hoenicke
57
    /// </summary>
58
    /// 
59
    /// <example> This sample shows how to read a zip file
60
    /// <code lang="C#">
61
    /// using System;
62
    /// using System.Text;
63
    /// using System.IO;
64
    /// 
65
    /// using ICSharpCode.SharpZipLib.Zip;
66
    /// 
67
    /// class MainClass
68
    /// {
69
    /// 	public static void Main(string[] args)
70
    /// 	{
71
    /// 		using ( ZipInputStream s = new ZipInputStream(File.OpenRead(args[0]))) {
72
    ///
73
    /// 			ZipEntry theEntry;
74
    /// 			while ((theEntry = s.GetNextEntry()) != null) {
75
    /// 				int size = 2048;
76
    /// 				byte[] data = new byte[2048];
77
    ///
78
    /// 				Console.Write("Show contents (y/n) ?");
79
    /// 				if (Console.ReadLine() == "y") {
80
    /// 					while (true) {
81
    /// 						size = s.Read(data, 0, data.Length);
82
    /// 						if (size > 0) {
83
    /// 							Console.Write(new ASCIIEncoding().GetString(data, 0, size));
84
    /// 						} else {
85
    /// 							break;
86
    /// 						}
87
    /// 					}
88
    /// 				}
89
    /// 			}
90
    /// 		}
91
    /// 	}
92
    /// }
93
    /// </code>
94
    /// </example>
95
    public class ZipInputStream : InflaterInputStream
96
    {
97
        #region Instance Fields
98

    
99
        // Delegate for reading bytes from a stream.
100

    
101
        private Crc32 crc = new Crc32();
102
        private ZipEntry entry;
103

    
104
        private int flags;
105

    
106
        /// <summary>
107
        /// The current reader this instance.
108
        /// </summary>
109
        private ReaderDelegate internalReader;
110

    
111
        private int method;
112

    
113
        private string password;
114
        private long size;
115

    
116
        private delegate int ReaderDelegate(byte[] b, int offset, int length);
117

    
118
        #endregion
119

    
120
        #region Constructors
121

    
122
        /// <summary>
123
        /// Creates a new Zip input stream, for reading a zip archive.
124
        /// </summary>
125
        /// <param name="baseInputStream">The underlying <see cref="Stream"/> providing data.</param>
126
        public ZipInputStream(Stream baseInputStream)
127
            : base(baseInputStream, new Inflater(true))
128
        {
129
            internalReader = ReadingNotAvailable;
130
        }
131

    
132
        #endregion
133

    
134
        /// <summary>
135
        /// Optional password used for encryption when non-null
136
        /// </summary>
137
        /// <value>A password for all encrypted <see cref="ZipEntry">entries </see> in this <see cref="ZipInputStream"/></value>
138
        public string Password
139
        {
140
            get { return password; }
141
            set { password = value; }
142
        }
143

    
144

    
145
        /// <summary>
146
        /// Gets a value indicating if there is a current entry and it can be decompressed
147
        /// </summary>
148
        /// <remarks>
149
        /// The entry can only be decompressed if the library supports the zip features required to extract it.
150
        /// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details.
151
        /// </remarks>
152
        public bool CanDecompressEntry
153
        {
154
            get { return (entry != null) && entry.CanDecompress; }
155
        }
156

    
157
        /// <summary>
158
        /// Returns 1 if there is an entry available
159
        /// Otherwise returns 0.
160
        /// </summary>
161
        public override int Available
162
        {
163
            get { return entry != null ? 1 : 0; }
164
        }
165

    
166
        /// <summary>
167
        /// Returns the current size that can be read from the current entry if available
168
        /// </summary>
169
        /// <exception cref="ZipException">Thrown if the entry size is not known.</exception>
170
        /// <exception cref="InvalidOperationException">Thrown if no entry is currently available.</exception>
171
        public override long Length
172
        {
173
            get
174
            {
175
                if (entry != null)
176
                {
177
                    if (entry.Size >= 0)
178
                    {
179
                        return entry.Size;
180
                    }
181
                    else
182
                    {
183
                        throw new ZipException("Length not available for the current entry");
184
                    }
185
                }
186
                else
187
                {
188
                    throw new InvalidOperationException("No current entry");
189
                }
190
            }
191
        }
192

    
193
        /// <summary>
194
        /// Advances to the next entry in the archive
195
        /// </summary>
196
        /// <returns>
197
        /// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries.
198
        /// </returns>
199
        /// <remarks>
200
        /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called.
201
        /// </remarks>
202
        /// <exception cref="InvalidOperationException">
203
        /// Input stream is closed
204
        /// </exception>
205
        /// <exception cref="ZipException">
206
        /// Password is not set, password is invalid, compression method is invalid,
207
        /// version required to extract is not supported
208
        /// </exception>
209
        public ZipEntry GetNextEntry()
210
        {
211
            if (crc == null)
212
            {
213
                throw new InvalidOperationException("Closed.");
214
            }
215

    
216
            if (entry != null)
217
            {
218
                CloseEntry();
219
            }
220

    
221
            var header = inputBuffer.ReadLeInt();
222

    
223
            if (header == ZipConstants.CentralHeaderSignature ||
224
                header == ZipConstants.EndOfCentralDirectorySignature ||
225
                header == ZipConstants.CentralHeaderDigitalSignature ||
226
                header == ZipConstants.ArchiveExtraDataSignature ||
227
                header == ZipConstants.Zip64CentralFileHeaderSignature)
228
            {
229
                // No more individual entries exist
230
                Close();
231
                return null;
232
            }
233

    
234
            // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found
235
            // Spanning signature is same as descriptor signature and is untested as yet.
236
            if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature))
237
            {
238
                header = inputBuffer.ReadLeInt();
239
            }
240

    
241
            if (header != ZipConstants.LocalHeaderSignature)
242
            {
243
                throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header));
244
            }
245

    
246
            var versionRequiredToExtract = (short) inputBuffer.ReadLeShort();
247

    
248
            flags = inputBuffer.ReadLeShort();
249
            method = inputBuffer.ReadLeShort();
250
            var dostime = (uint) inputBuffer.ReadLeInt();
251
            var crc2 = inputBuffer.ReadLeInt();
252
            csize = inputBuffer.ReadLeInt();
253
            size = inputBuffer.ReadLeInt();
254
            var nameLen = inputBuffer.ReadLeShort();
255
            var extraLen = inputBuffer.ReadLeShort();
256

    
257
            var isCrypted = (flags & 1) == 1;
258

    
259
            var buffer = new byte[nameLen];
260
            inputBuffer.ReadRawBuffer(buffer);
261

    
262
            var name = ZipConstants.ConvertToStringExt(flags, buffer);
263

    
264
            entry = new ZipEntry(name, versionRequiredToExtract);
265
            entry.Flags = flags;
266

    
267
            entry.CompressionMethod = (CompressionMethod) method;
268

    
269
            if ((flags & 8) == 0)
270
            {
271
                entry.Crc = crc2 & 0xFFFFFFFFL;
272
                entry.Size = size & 0xFFFFFFFFL;
273
                entry.CompressedSize = csize & 0xFFFFFFFFL;
274

    
275
                entry.CryptoCheckValue = (byte) ((crc2 >> 24) & 0xff);
276
            }
277
            else
278
            {
279
                // This allows for GNU, WinZip and possibly other archives, the PKZIP spec
280
                // says these values are zero under these circumstances.
281
                if (crc2 != 0)
282
                {
283
                    entry.Crc = crc2 & 0xFFFFFFFFL;
284
                }
285

    
286
                if (size != 0)
287
                {
288
                    entry.Size = size & 0xFFFFFFFFL;
289
                }
290

    
291
                if (csize != 0)
292
                {
293
                    entry.CompressedSize = csize & 0xFFFFFFFFL;
294
                }
295

    
296
                entry.CryptoCheckValue = (byte) ((dostime >> 8) & 0xff);
297
            }
298

    
299
            entry.DosTime = dostime;
300

    
301
            // If local header requires Zip64 is true then the extended header should contain
302
            // both values.
303

    
304
            // Handle extra data if present.  This can set/alter some fields of the entry.
305
            if (extraLen > 0)
306
            {
307
                var extra = new byte[extraLen];
308
                inputBuffer.ReadRawBuffer(extra);
309
                entry.ExtraData = extra;
310
            }
311

    
312
            entry.ProcessExtraData(true);
313
            if (entry.CompressedSize >= 0)
314
            {
315
                csize = entry.CompressedSize;
316
            }
317

    
318
            if (entry.Size >= 0)
319
            {
320
                size = entry.Size;
321
            }
322

    
323
            if (method == (int) CompressionMethod.Stored &&
324
                (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size)))
325
            {
326
                throw new ZipException("Stored, but compressed != uncompressed");
327
            }
328

    
329
            // Determine how to handle reading of data if this is attempted.
330
            if (entry.IsCompressionMethodSupported())
331
            {
332
                internalReader = InitialRead;
333
            }
334
            else
335
            {
336
                internalReader = ReadingNotSupported;
337
            }
338

    
339
            return entry;
340
        }
341

    
342
        /// <summary>
343
        /// Read data descriptor at the end of compressed data. 
344
        /// </summary>
345
        private void ReadDataDescriptor()
346
        {
347
            if (inputBuffer.ReadLeInt() != ZipConstants.DataDescriptorSignature)
348
            {
349
                throw new ZipException("Data descriptor signature not found");
350
            }
351

    
352
            entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL;
353

    
354
            if (entry.LocalHeaderRequiresZip64)
355
            {
356
                csize = inputBuffer.ReadLeLong();
357
                size = inputBuffer.ReadLeLong();
358
            }
359
            else
360
            {
361
                csize = inputBuffer.ReadLeInt();
362
                size = inputBuffer.ReadLeInt();
363
            }
364
            entry.CompressedSize = csize;
365
            entry.Size = size;
366
        }
367

    
368
        /// <summary>
369
        /// Complete cleanup as the final part of closing.
370
        /// </summary>
371
        /// <param name="testCrc">True if the crc value should be tested</param>
372
        private void CompleteCloseEntry(bool testCrc)
373
        {
374
            StopDecrypting();
375

    
376
            if ((flags & 8) != 0)
377
            {
378
                ReadDataDescriptor();
379
            }
380

    
381
            size = 0;
382

    
383
            if (testCrc &&
384
                ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1))
385
            {
386
                throw new ZipException("CRC mismatch");
387
            }
388

    
389
            crc.Reset();
390

    
391
            if (method == (int) CompressionMethod.Deflated)
392
            {
393
                inf.Reset();
394
            }
395
            entry = null;
396
        }
397

    
398
        /// <summary>
399
        /// Closes the current zip entry and moves to the next one.
400
        /// </summary>
401
        /// <exception cref="InvalidOperationException">
402
        /// The stream is closed
403
        /// </exception>
404
        /// <exception cref="ZipException">
405
        /// The Zip stream ends early
406
        /// </exception>
407
        public void CloseEntry()
408
        {
409
            if (crc == null)
410
            {
411
                throw new InvalidOperationException("Closed");
412
            }
413

    
414
            if (entry == null)
415
            {
416
                return;
417
            }
418

    
419
            if (method == (int) CompressionMethod.Deflated)
420
            {
421
                if ((flags & 8) != 0)
422
                {
423
                    // We don't know how much we must skip, read until end.
424
                    var tmp = new byte[2048];
425

    
426
                    // Read will close this entry
427
                    while (Read(tmp, 0, tmp.Length) > 0)
428
                    {
429
                    }
430
                    return;
431
                }
432

    
433
                csize -= inf.TotalIn;
434
                inputBuffer.Available += inf.RemainingInput;
435
            }
436

    
437
            if ((inputBuffer.Available > csize) && (csize >= 0))
438
            {
439
                inputBuffer.Available = (int) (inputBuffer.Available - csize);
440
            }
441
            else
442
            {
443
                csize -= inputBuffer.Available;
444
                inputBuffer.Available = 0;
445
                while (csize != 0)
446
                {
447
                    var skipped = (int) base.Skip(csize & 0xFFFFFFFFL);
448

    
449
                    if (skipped <= 0)
450
                    {
451
                        throw new ZipException("Zip archive ends early.");
452
                    }
453

    
454
                    csize -= skipped;
455
                }
456
            }
457

    
458
            CompleteCloseEntry(false);
459
        }
460

    
461
        /// <summary>
462
        /// Reads a byte from the current zip entry.
463
        /// </summary>
464
        /// <returns>
465
        /// The byte or -1 if end of stream is reached.
466
        /// </returns>
467
        public override int ReadByte()
468
        {
469
            var b = new byte[1];
470
            if (Read(b, 0, 1) <= 0)
471
            {
472
                return -1;
473
            }
474
            return b[0] & 0xff;
475
        }
476

    
477
        /// <summary>
478
        /// Handle attempts to read by throwing an <see cref="InvalidOperationException"/>.
479
        /// </summary>
480
        /// <param name="destination">The destination array to store data in.</param>
481
        /// <param name="offset">The offset at which data read should be stored.</param>
482
        /// <param name="count">The maximum number of bytes to read.</param>
483
        /// <returns>Returns the number of bytes actually read.</returns>
484
        private int ReadingNotAvailable(byte[] destination, int offset, int count)
485
        {
486
            throw new InvalidOperationException("Unable to read from this stream");
487
        }
488

    
489
        /// <summary>
490
        /// Handle attempts to read from this entry by throwing an exception
491
        /// </summary>
492
        private int ReadingNotSupported(byte[] destination, int offset, int count)
493
        {
494
            throw new ZipException("The compression method for this entry is not supported");
495
        }
496

    
497
        /// <summary>
498
        /// Perform the initial read on an entry which may include 
499
        /// reading encryption headers and setting up inflation.
500
        /// </summary>
501
        /// <param name="destination">The destination to fill with data read.</param>
502
        /// <param name="offset">The offset to start reading at.</param>
503
        /// <param name="count">The maximum number of bytes to read.</param>
504
        /// <returns>The actual number of bytes read.</returns>
505
        private int InitialRead(byte[] destination, int offset, int count)
506
        {
507
            if (!CanDecompressEntry)
508
            {
509
                throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version + ")");
510
            }
511

    
512
            // Handle encryption if required.
513
            if (entry.IsCrypted)
514
            {
515
                if (password == null)
516
                {
517
                    throw new ZipException("No password set.");
518
                }
519

    
520
                // Generate and set crypto transform...
521
                var managed = new PkzipClassicManaged();
522
                var key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(password));
523

    
524
                inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null);
525

    
526
                var cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
527
                inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CryptoHeaderSize);
528

    
529
                if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue)
530
                {
531
                    throw new ZipException("Invalid password");
532
                }
533

    
534
                if (csize >= ZipConstants.CryptoHeaderSize)
535
                {
536
                    csize -= ZipConstants.CryptoHeaderSize;
537
                }
538
                else if ((entry.Flags & (int) GeneralBitFlags.Descriptor) == 0)
539
                {
540
                    throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize));
541
                }
542
            }
543
            else
544
            {
545
                inputBuffer.CryptoTransform = null;
546
            }
547

    
548
            if ((method == (int) CompressionMethod.Deflated) && (inputBuffer.Available > 0))
549
            {
550
                inputBuffer.SetInflaterInput(inf);
551
            }
552

    
553
            internalReader = BodyRead;
554
            return BodyRead(destination, offset, count);
555
        }
556

    
557
        /// <summary>
558
        /// Read a block of bytes from the stream.
559
        /// </summary>
560
        /// <param name="buffer">The destination for the bytes.</param>
561
        /// <param name="offset">The index to start storing data.</param>
562
        /// <param name="count">The number of bytes to attempt to read.</param>
563
        /// <returns>Returns the number of bytes read.</returns>
564
        /// <remarks>Zero bytes read means end of stream.</remarks>
565
        public override int Read(byte[] buffer, int offset, int count)
566
        {
567
            if (buffer == null)
568
            {
569
                throw new ArgumentNullException("buffer");
570
            }
571

    
572
            if (offset < 0)
573
            {
574
                throw new ArgumentOutOfRangeException("offset", "Cannot be negative");
575
            }
576

    
577
            if (count < 0)
578
            {
579
                throw new ArgumentOutOfRangeException("count", "Cannot be negative");
580
            }
581

    
582
            if ((buffer.Length - offset) < count)
583
            {
584
                throw new ArgumentException("Invalid offset/count combination");
585
            }
586

    
587
            return internalReader(buffer, offset, count);
588
        }
589

    
590
        /// <summary>
591
        /// Reads a block of bytes from the current zip entry.
592
        /// </summary>
593
        /// <returns>
594
        /// 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.
595
        /// </returns>
596
        /// <exception name="IOException">
597
        /// An i/o error occured.
598
        /// </exception>
599
        /// <exception cref="ZipException">
600
        /// The deflated stream is corrupted.
601
        /// </exception>
602
        /// <exception cref="InvalidOperationException">
603
        /// The stream is not open.
604
        /// </exception>
605
        private int BodyRead(byte[] buffer, int offset, int count)
606
        {
607
            if (crc == null)
608
            {
609
                throw new InvalidOperationException("Closed");
610
            }
611

    
612
            if ((entry == null) || (count <= 0))
613
            {
614
                return 0;
615
            }
616

    
617
            if (offset + count > buffer.Length)
618
            {
619
                throw new ArgumentException("Offset + count exceeds buffer size");
620
            }
621

    
622
            var finished = false;
623

    
624
            switch (method)
625
            {
626
                case (int) CompressionMethod.Deflated:
627
                    count = base.Read(buffer, offset, count);
628
                    if (count <= 0)
629
                    {
630
                        if (!inf.IsFinished)
631
                        {
632
                            throw new ZipException("Inflater not finished!");
633
                        }
634
                        inputBuffer.Available = inf.RemainingInput;
635

    
636
                        if ((flags & 8) == 0 && (inf.TotalIn != csize || inf.TotalOut != size))
637
                        {
638
                            throw new ZipException("Size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" +
639
                                                   inf.TotalOut);
640
                        }
641
                        inf.Reset();
642
                        finished = true;
643
                    }
644
                    break;
645

    
646
                case (int) CompressionMethod.Stored:
647
                    if ((count > csize) && (csize >= 0))
648
                    {
649
                        count = (int) csize;
650
                    }
651

    
652
                    if (count > 0)
653
                    {
654
                        count = inputBuffer.ReadClearTextBuffer(buffer, offset, count);
655
                        if (count > 0)
656
                        {
657
                            csize -= count;
658
                            size -= count;
659
                        }
660
                    }
661

    
662
                    if (csize == 0)
663
                    {
664
                        finished = true;
665
                    }
666
                    else
667
                    {
668
                        if (count < 0)
669
                        {
670
                            throw new ZipException("EOF in stored block");
671
                        }
672
                    }
673
                    break;
674
            }
675

    
676
            if (count > 0)
677
            {
678
                crc.Update(buffer, offset, count);
679
            }
680

    
681
            if (finished)
682
            {
683
                CompleteCloseEntry(true);
684
            }
685

    
686
            return count;
687
        }
688

    
689
        /// <summary>
690
        /// Closes the zip input stream
691
        /// </summary>
692
        public override void Close()
693
        {
694
            internalReader = ReadingNotAvailable;
695
            crc = null;
696
            entry = null;
697

    
698
            base.Close();
699
        }
700
    }
701
}