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.Text; 42 using System.IO; 43 44 using ICSharpCode.SharpZipLib.Checksums; 45 using ICSharpCode.SharpZipLib.Zip.Compression; 46 using ICSharpCode.SharpZipLib.Zip.Compression.Streams; 47 using ICSharpCode.SharpZipLib.Encryption; 48 49 namespace ICSharpCode.SharpZipLib.Zip 50 { 51 /// <summary> 52 /// This is an InflaterInputStream that reads the files baseInputStream an zip archive 53 /// one after another. It has a special method to get the zip entry of 54 /// the next file. The zip entry contains information about the file name 55 /// size, compressed size, Crc, etc. 56 /// It includes support for Stored and Deflated entries. 57 /// <br/> 58 /// <br/>Author of the original java version : Jochen Hoenicke 59 /// </summary> 60 /// 61 /// <example> This sample shows how to read a zip file 62 /// <code lang="C#"> 63 /// using System; 64 /// using System.Text; 65 /// using System.IO; 66 /// 67 /// using ICSharpCode.SharpZipLib.Zip; 68 /// 69 /// class MainClass 70 /// { 71 /// public static void Main(string[] args) 72 /// { 73 /// ZipInputStream s = new ZipInputStream(File.OpenRead(args[0])); 74 /// 75 /// ZipEntry theEntry; 76 /// while ((theEntry = s.GetNextEntry()) != null) { 77 /// int size = 2048; 78 /// byte[] data = new byte[2048]; 79 /// 80 /// Console.Write("Show contents (y/n) ?"); 81 /// if (Console.ReadLine() == "y") { 82 /// while (true) { 83 /// size = s.Read(data, 0, data.Length); 84 /// if (size > 0) { 85 /// Console.Write(new ASCIIEncoding().GetString(data, 0, size)); 86 /// } else { 87 /// break; 88 /// } 89 /// } 90 /// } 91 /// } 92 /// s.Close(); 93 /// } 94 /// } 95 /// </code> 96 /// </example> 97 [System.ObsoleteAttribute("This assembly has been deprecated. Please use https://www.nuget.org/packages/SharpZipLib/ instead.")] 98 public class ZipInputStream : InflaterInputStream 99 { 100 // Delegate for reading bytes from a stream. ReaderDelegate(byte[] b, int offset, int length)101 delegate int ReaderDelegate(byte[] b, int offset, int length); 102 103 /// <summary> 104 /// The current reader this instance. 105 /// </summary> 106 ReaderDelegate internalReader; 107 108 Crc32 crc = new Crc32(); 109 ZipEntry entry = null; 110 111 long size; 112 int method; 113 int flags; 114 string password = null; 115 116 /// <summary> 117 /// Creates a new Zip input stream, for reading a zip archive. 118 /// </summary> ZipInputStream(Stream baseInputStream)119 public ZipInputStream(Stream baseInputStream) : base(baseInputStream, new Inflater(true)) 120 { 121 internalReader = new ReaderDelegate(InitialRead); 122 } 123 124 125 /// <summary> 126 /// Optional password used for encryption when non-null 127 /// </summary> 128 public string Password 129 { 130 get { 131 return password; 132 } 133 set { 134 password = value; 135 } 136 } 137 138 139 /// <summary> 140 /// Gets a value indicating if the entry can be decompressed 141 /// </summary> 142 /// <remarks> 143 /// The entry can only be decompressed if the library supports the zip features required to extract it. 144 /// See the <see cref="ZipEntry.Version">ZipEntry Version</see> property for more details. 145 /// </remarks> 146 public bool CanDecompressEntry { 147 get { 148 return entry != null && entry.Version <= ZipConstants.VERSION_MADE_BY; 149 } 150 } 151 152 /// <summary> 153 /// Advances to the next entry in the archive 154 /// </summary> 155 /// <returns> 156 /// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries. 157 /// </returns> 158 /// <remarks> 159 /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called. 160 /// </remarks> 161 /// <exception cref="InvalidOperationException"> 162 /// Input stream is closed 163 /// </exception> 164 /// <exception cref="ZipException"> 165 /// Password is not set, password is invalid, compression method is invalid, 166 /// version required to extract is not supported 167 /// </exception> GetNextEntry()168 public ZipEntry GetNextEntry() 169 { 170 if (crc == null) { 171 throw new InvalidOperationException("Closed."); 172 } 173 174 if (entry != null) { 175 CloseEntry(); 176 } 177 178 int header = inputBuffer.ReadLeInt(); 179 180 if (header == ZipConstants.CENSIG || 181 header == ZipConstants.ENDSIG || 182 header == ZipConstants.CENDIGITALSIG || 183 header == ZipConstants.CENSIG64) { 184 // No more individual entries exist 185 Close(); 186 return null; 187 } 188 189 // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found 190 // SPANNINGSIG is same as descriptor signature and is untested as yet. 191 if (header == ZipConstants.SPANTEMPSIG || header == ZipConstants.SPANNINGSIG) { 192 header = inputBuffer.ReadLeInt(); 193 } 194 195 if (header != ZipConstants.LOCSIG) { 196 throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header)); 197 } 198 199 short versionRequiredToExtract = (short)inputBuffer.ReadLeShort(); 200 201 flags = inputBuffer.ReadLeShort(); 202 method = inputBuffer.ReadLeShort(); 203 uint dostime = (uint)inputBuffer.ReadLeInt(); 204 int crc2 = inputBuffer.ReadLeInt(); 205 csize = inputBuffer.ReadLeInt(); 206 size = inputBuffer.ReadLeInt(); 207 int nameLen = inputBuffer.ReadLeShort(); 208 int extraLen = inputBuffer.ReadLeShort(); 209 210 bool isCrypted = (flags & 1) == 1; 211 212 byte[] buffer = new byte[nameLen]; 213 inputBuffer.ReadRawBuffer(buffer); 214 215 string name = ZipConstants.ConvertToString(buffer); 216 217 entry = new ZipEntry(name, versionRequiredToExtract); 218 entry.Flags = flags; 219 220 if (method == (int)CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CRYPTO_HEADER_SIZE != size))) { 221 throw new ZipException("Stored, but compressed != uncompressed"); 222 } 223 224 if (method != (int)CompressionMethod.Stored && method != (int)CompressionMethod.Deflated) { 225 throw new ZipException("Unknown compression method " + method); 226 } 227 228 entry.CompressionMethod = (CompressionMethod)method; 229 230 if ((flags & 8) == 0) { 231 entry.Crc = crc2 & 0xFFFFFFFFL; 232 entry.Size = size & 0xFFFFFFFFL; 233 entry.CompressedSize = csize & 0xFFFFFFFFL; 234 } else { 235 236 // This allows for GNU, WinZip and possibly other archives, the PKZIP spec says these are zero 237 // under these circumstances. 238 if (crc2 != 0) { 239 entry.Crc = crc2 & 0xFFFFFFFFL; 240 } 241 242 if (size != 0) { 243 entry.Size = size & 0xFFFFFFFFL; 244 } 245 if (csize != 0) { 246 entry.CompressedSize = csize & 0xFFFFFFFFL; 247 } 248 } 249 250 entry.DosTime = dostime; 251 252 if (extraLen > 0) { 253 byte[] extra = new byte[extraLen]; 254 inputBuffer.ReadRawBuffer(extra); 255 entry.ExtraData = extra; 256 } 257 258 internalReader = new ReaderDelegate(InitialRead); 259 return entry; 260 } 261 262 // Read data descriptor at the end of compressed data. ReadDataDescriptor()263 void ReadDataDescriptor() 264 { 265 if (inputBuffer.ReadLeInt() != ZipConstants.EXTSIG) { 266 throw new ZipException("Data descriptor signature not found"); 267 } 268 269 entry.Crc = inputBuffer.ReadLeInt() & 0xFFFFFFFFL; 270 csize = inputBuffer.ReadLeInt(); 271 size = inputBuffer.ReadLeInt(); 272 273 entry.Size = size & 0xFFFFFFFFL; 274 entry.CompressedSize = csize & 0xFFFFFFFFL; 275 } 276 277 /// <summary> 278 /// Closes the current zip entry and moves to the next one. 279 /// </summary> 280 /// <exception cref="InvalidOperationException"> 281 /// The stream is closed 282 /// </exception> 283 /// <exception cref="ZipException"> 284 /// The Zip stream ends early 285 /// </exception> CloseEntry()286 public void CloseEntry() 287 { 288 if (crc == null) { 289 throw new InvalidOperationException("Closed."); 290 } 291 292 if (entry == null) { 293 return; 294 } 295 296 if (method == (int)CompressionMethod.Deflated) { 297 if ((flags & 8) != 0) { 298 // We don't know how much we must skip, read until end. 299 byte[] tmp = new byte[2048]; 300 while (Read(tmp, 0, tmp.Length) > 0) 301 ; 302 // read will close this entry 303 return; 304 } 305 csize -= inf.TotalIn; 306 inputBuffer.Available -= inf.RemainingInput; 307 } 308 309 if (inputBuffer.Available > csize && csize >= 0) { 310 inputBuffer.Available = (int)((long)inputBuffer.Available - csize); 311 } else { 312 csize -= inputBuffer.Available; 313 inputBuffer.Available = 0; 314 while (csize != 0) { 315 int skipped = (int)base.Skip(csize & 0xFFFFFFFFL); 316 317 if (skipped <= 0) { 318 throw new ZipException("Zip archive ends early."); 319 } 320 321 csize -= skipped; 322 } 323 } 324 325 size = 0; 326 crc.Reset(); 327 if (method == (int)CompressionMethod.Deflated) { 328 inf.Reset(); 329 } 330 entry = null; 331 } 332 333 /// <summary> 334 /// Returns 1 if there is an entry available 335 /// Otherwise returns 0. 336 /// </summary> 337 public override int Available { 338 get { 339 return entry != null ? 1 : 0; 340 } 341 } 342 343 /// <summary> 344 /// Reads a byte from the current zip entry. 345 /// </summary> 346 /// <returns> 347 /// The byte or -1 if end of stream is reached. 348 /// </returns> 349 /// <exception name="System.IO.IOException"> 350 /// An i/o error occured. 351 /// </exception> 352 /// <exception name="ICSharpCode.SharpZipLib.ZipException"> 353 /// The deflated stream is corrupted. 354 /// </exception> ReadByte()355 public override int ReadByte() 356 { 357 byte[] b = new byte[1]; 358 if (Read(b, 0, 1) <= 0) { 359 return -1; 360 } 361 return b[0] & 0xff; 362 } 363 364 // Perform the initial read on an entry which may include 365 // reading encryption headers and setting up inflation. InitialRead(byte[] destination, int offset, int count)366 int InitialRead(byte[] destination, int offset, int count) 367 { 368 if (entry.Version > ZipConstants.VERSION_MADE_BY) { 369 throw new ZipException("Libray cannot extract this entry version required (" + entry.Version.ToString() + ")"); 370 } 371 372 // test for encryption 373 if (entry.IsCrypted) { 374 375 if (password == null) { 376 throw new ZipException("No password set."); 377 } 378 379 // Generate and set crypto transform... 380 PkzipClassicManaged managed = new PkzipClassicManaged(); 381 byte[] key = PkzipClassic.GenerateKeys(Encoding.ASCII.GetBytes(password)); 382 383 inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null); 384 385 byte[] cryptbuffer = new byte[ZipConstants.CRYPTO_HEADER_SIZE]; 386 inputBuffer.ReadClearTextBuffer(cryptbuffer, 0, ZipConstants.CRYPTO_HEADER_SIZE); 387 388 if ((flags & 8) == 0) { 389 if (cryptbuffer[ZipConstants.CRYPTO_HEADER_SIZE - 1] != (byte)(entry.Crc >> 24)) { 390 throw new ZipException("Invalid password"); 391 } 392 } 393 else { 394 if (cryptbuffer[ZipConstants.CRYPTO_HEADER_SIZE - 1] != (byte)((entry.DosTime >> 8) & 0xff)) { 395 throw new ZipException("Invalid password"); 396 } 397 } 398 399 if (csize >= ZipConstants.CRYPTO_HEADER_SIZE) { 400 csize -= ZipConstants.CRYPTO_HEADER_SIZE; 401 } 402 } 403 else { 404 inputBuffer.CryptoTransform = null; 405 } 406 407 if (method == (int)CompressionMethod.Deflated && inputBuffer.Available > 0) { 408 inputBuffer.SetInflaterInput(inf); 409 } 410 411 internalReader = new ReaderDelegate(BodyRead); 412 return BodyRead(destination, offset, count); 413 } 414 415 416 /// <summary> 417 /// Read a block of bytes from the stream. 418 /// </summary> 419 /// <param name="destination">The destination for the bytes.</param> 420 /// <param name="index">The index to start storing data.</param> 421 /// <param name="count">The number of bytes to attempt to read.</param> 422 /// <returns>Returns the number of bytes read.</returns> 423 /// <remarks>Zero bytes read means end of stream.</remarks> Read(byte[] destination, int index, int count)424 public override int Read(byte[] destination, int index, int count) 425 { 426 return internalReader(destination, index, count); 427 } 428 429 /// <summary> 430 /// Reads a block of bytes from the current zip entry. 431 /// </summary> 432 /// <returns> 433 /// 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. 434 /// </returns> 435 /// <exception name="IOException"> 436 /// An i/o error occured. 437 /// </exception> 438 /// <exception cref="ZipException"> 439 /// The deflated stream is corrupted. 440 /// </exception> 441 /// <exception cref="InvalidOperationException"> 442 /// The stream is not open. 443 /// </exception> BodyRead(byte[] b, int off, int len)444 public int BodyRead(byte[] b, int off, int len) 445 { 446 if (crc == null) { 447 throw new InvalidOperationException("Closed."); 448 } 449 450 if (entry == null || len <= 0 ) { 451 return 0; 452 } 453 454 bool finished = false; 455 456 switch (method) { 457 case (int)CompressionMethod.Deflated: 458 len = base.Read(b, off, len); 459 if (len <= 0) { 460 if (!inf.IsFinished) { 461 throw new ZipException("Inflater not finished!?"); 462 } 463 inputBuffer.Available = inf.RemainingInput; 464 465 if ((flags & 8) == 0 && (inf.TotalIn != csize || inf.TotalOut != size)) { 466 throw new ZipException("size mismatch: " + csize + ";" + size + " <-> " + inf.TotalIn + ";" + inf.TotalOut); 467 } 468 inf.Reset(); 469 finished = true; 470 } 471 break; 472 473 case (int)CompressionMethod.Stored: 474 if (len > csize && csize >= 0) { 475 len = (int)csize; 476 } 477 len = inputBuffer.ReadClearTextBuffer(b, off, len); 478 if (len > 0) { 479 csize -= len; 480 size -= len; 481 } 482 483 if (csize == 0) { 484 finished = true; 485 } else { 486 if (len < 0) { 487 throw new ZipException("EOF in stored block"); 488 } 489 } 490 break; 491 } 492 493 if (len > 0) { 494 crc.Update(b, off, len); 495 } 496 497 if (finished) { 498 StopDecrypting(); 499 500 if ((flags & 8) != 0) { 501 ReadDataDescriptor(); 502 } 503 504 if ((crc.Value & 0xFFFFFFFFL) != entry.Crc && entry.Crc != -1) { 505 throw new ZipException("CRC mismatch"); 506 } 507 crc.Reset(); 508 entry = null; 509 } 510 return len; 511 } 512 513 /// <summary> 514 /// Closes the zip input stream 515 /// </summary> Close()516 public override void Close() 517 { 518 base.Close(); 519 crc = null; 520 entry = null; 521 } 522 } 523 } 524