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