/// <summary> /// Closes the current entry, updating header and footer information as required /// </summary> /// <exception cref="System.IO.IOException"> /// An I/O error occurs. /// </exception> /// <exception cref="System.InvalidOperationException"> /// No entry is active. /// </exception> public void CloseEntry() { if (curEntry == null) { throw new InvalidOperationException("No open entry"); } // First finish the deflater, if appropriate if (curMethod == CompressionMethod.Deflated) { base.Finish(); } long csize = curMethod == CompressionMethod.Deflated ? def.TotalOut : size; if (curEntry.Size < 0) { curEntry.Size = size; } else if (curEntry.Size != size) { throw new ZipException("size was " + size + ", but I expected " + curEntry.Size); } if (curEntry.CompressedSize < 0) { curEntry.CompressedSize = csize; } else if (curEntry.CompressedSize != csize) { throw new ZipException("compressed size was " + csize + ", but I expected " + curEntry.CompressedSize); } if (curEntry.Crc < 0) { curEntry.Crc = crc.Value; } else if (curEntry.Crc != crc.Value) { throw new ZipException("crc was " + crc.Value + ", but I expected " + curEntry.Crc); } offset += csize; if (offset > 0xffffffff) { throw new ZipException("Maximum Zip file size exceeded"); } if (curEntry.IsCrypted == true) { curEntry.CompressedSize += ZipConstants.CRYPTO_HEADER_SIZE; } // Patch the header if possible if (patchEntryHeader == true) { long curPos = baseOutputStream.Position; baseOutputStream.Seek(headerPatchPos, SeekOrigin.Begin); WriteLeInt((int)curEntry.Crc); WriteLeInt((int)curEntry.CompressedSize); WriteLeInt((int)curEntry.Size); baseOutputStream.Seek(curPos, SeekOrigin.Begin); patchEntryHeader = false; } // Add data descriptor if flagged as required if ((curEntry.Flags & 8) != 0) { WriteLeInt(ZipConstants.EXTSIG); WriteLeInt((int)curEntry.Crc); WriteLeInt((int)curEntry.CompressedSize); WriteLeInt((int)curEntry.Size); offset += ZipConstants.EXTHDR; } entries.Add(curEntry); curEntry = null; }
/// <summary> /// Search for and read the central directory of a zip file filling the entries /// array. This is called exactly once by the constructors. /// </summary> /// <exception cref="System.IO.IOException"> /// An i/o error occurs. /// </exception> /// <exception cref="AdHocDesktop.SharpZipLib.Zip.ZipException"> /// The central directory is malformed or cannot be found /// </exception> void ReadEntries() { // Search for the End Of Central Directory. When a zip comment is // present the directory may start earlier. // // TODO: The search is limited to 64K which is the maximum size of a trailing comment field to aid speed. // This should be compatible with both SFX and ZIP files but has only been tested for Zip files // Need to confirm this is valid in all cases. // Could also speed this up by reading memory in larger blocks? if (baseStream.CanSeek == false) { throw new ZipException("ZipFile stream must be seekable"); } long pos = baseStream.Length - ZipConstants.ENDHDR; if (pos <= 0) { throw new ZipException("File is too small to be a Zip file"); } long giveUpMarker = Math.Max(pos - 0x10000, 0); do { if (pos < giveUpMarker) { throw new ZipException("central directory not found, probably not a zip file"); } baseStream.Seek(pos--, SeekOrigin.Begin); } while (ReadLeInt() != ZipConstants.ENDSIG); int thisDiskNumber = ReadLeShort(); int startCentralDirDisk = ReadLeShort(); int entriesForThisDisk = ReadLeShort(); int entriesForWholeCentralDir = ReadLeShort(); int centralDirSize = ReadLeInt(); int offsetOfCentralDir = ReadLeInt(); int commentSize = ReadLeShort(); byte[] zipComment = new byte[commentSize]; baseStream.Read(zipComment, 0, zipComment.Length); comment = ZipConstants.ConvertToString(zipComment); /* Its seems possible that this is too strict, more digging required. * if (thisDiskNumber != 0 || startCentralDirDisk != 0 || entriesForThisDisk != entriesForWholeCentralDir) { * throw new ZipException("Spanned archives are not currently handled"); * } */ entries = new ZipEntry[entriesForWholeCentralDir]; baseStream.Seek(offsetOfCentralDir, SeekOrigin.Begin); for (int i = 0; i < entriesForWholeCentralDir; i++) { if (ReadLeInt() != ZipConstants.CENSIG) { throw new ZipException("Wrong Central Directory signature"); } int versionMadeBy = ReadLeShort(); int versionToExtract = ReadLeShort(); int bitFlags = ReadLeShort(); int method = ReadLeShort(); int dostime = ReadLeInt(); int crc = ReadLeInt(); int csize = ReadLeInt(); int size = ReadLeInt(); int nameLen = ReadLeShort(); int extraLen = ReadLeShort(); int commentLen = ReadLeShort(); int diskStartNo = ReadLeShort(); // Not currently used int internalAttributes = ReadLeShort(); // Not currently used int externalAttributes = ReadLeInt(); int offset = ReadLeInt(); byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; baseStream.Read(buffer, 0, nameLen); string name = ZipConstants.ConvertToString(buffer, nameLen); ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy); entry.CompressionMethod = (CompressionMethod)method; entry.Crc = crc & 0xffffffffL; entry.Size = size & 0xffffffffL; entry.CompressedSize = csize & 0xffffffffL; entry.Flags = bitFlags; entry.DosTime = (uint)dostime; if (extraLen > 0) { byte[] extra = new byte[extraLen]; baseStream.Read(extra, 0, extraLen); entry.ExtraData = extra; } if (commentLen > 0) { baseStream.Read(buffer, 0, commentLen); entry.Comment = ZipConstants.ConvertToString(buffer, commentLen); } entry.ZipFileIndex = i; entry.Offset = offset; entry.ExternalFileAttributes = externalAttributes; entries[i] = entry; } }
/// <summary> /// Starts a new Zip entry. It automatically closes the previous /// entry if present. /// All entry elements bar name are optional, but must be correct if present. /// If the compression method is stored and the output is not patchable /// the compression for that entry is automatically changed to deflate level 0 /// </summary> /// <param name="entry"> /// the entry. /// </param> /// <exception cref="System.IO.IOException"> /// if an I/O error occured. /// </exception> /// <exception cref="System.InvalidOperationException"> /// if stream was finished /// </exception> /// <exception cref="ZipException"> /// Too many entries in the Zip file<br/> /// Entry name is too long<br/> /// Finish has already been called<br/> /// </exception> public void PutNextEntry(ZipEntry entry) { if (entries == null) { throw new InvalidOperationException("ZipOutputStream was finished"); } if (curEntry != null) { CloseEntry(); } if (entries.Count >= 0xffff) { throw new ZipException("Too many entries for Zip file"); } CompressionMethod method = entry.CompressionMethod; int compressionLevel = defaultCompressionLevel; entry.Flags = 0; patchEntryHeader = false; bool headerInfoAvailable = true; if (method == CompressionMethod.Stored) { if (entry.CompressedSize >= 0) { if (entry.Size < 0) { entry.Size = entry.CompressedSize; } else if (entry.Size != entry.CompressedSize) { throw new ZipException("Method STORED, but compressed size != size"); } } else { if (entry.Size >= 0) { entry.CompressedSize = entry.Size; } } if (entry.Size < 0 || entry.Crc < 0) { if (CanPatchEntries == true) { headerInfoAvailable = false; } else { method = CompressionMethod.Deflated; compressionLevel = 0; } } } if (method == CompressionMethod.Deflated) { if (entry.Size == 0) { entry.CompressedSize = entry.Size; entry.Crc = 0; method = CompressionMethod.Stored; } else if (entry.CompressedSize < 0 || entry.Size < 0 || entry.Crc < 0) { headerInfoAvailable = false; } } if (headerInfoAvailable == false) { if (CanPatchEntries == false) { entry.Flags |= 8; } else { patchEntryHeader = true; } } if (Password != null) { entry.IsCrypted = true; if (entry.Crc < 0) { entry.Flags |= 8; } } entry.Offset = (int)offset; entry.CompressionMethod = (CompressionMethod)method; curMethod = method; // Write the local file header WriteLeInt(ZipConstants.LOCSIG); WriteLeShort(entry.Version); WriteLeShort(entry.Flags); WriteLeShort((byte)method); WriteLeInt((int)entry.DosTime); if (headerInfoAvailable == true) { WriteLeInt((int)entry.Crc); WriteLeInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CRYPTO_HEADER_SIZE : (int)entry.CompressedSize); WriteLeInt((int)entry.Size); } else { if (patchEntryHeader == true) { headerPatchPos = baseOutputStream.Position; } WriteLeInt(0); // Crc WriteLeInt(0); // Compressed size WriteLeInt(0); // Uncompressed size } byte[] name = ZipConstants.ConvertToArray(entry.Name); if (name.Length > 0xFFFF) { throw new ZipException("Entry name too long."); } byte[] extra = entry.ExtraData; if (extra == null) { extra = new byte[0]; } if (extra.Length > 0xFFFF) { throw new ZipException("Extra data too long."); } WriteLeShort(name.Length); WriteLeShort(extra.Length); baseOutputStream.Write(name, 0, name.Length); baseOutputStream.Write(extra, 0, extra.Length); offset += ZipConstants.LOCHDR + name.Length + extra.Length; // Activate the entry. curEntry = entry; crc.Reset(); if (method == CompressionMethod.Deflated) { def.Reset(); def.SetLevel(compressionLevel); } size = 0; if (entry.IsCrypted == true) { if (entry.Crc < 0) { // so testing Zip will says its ok WriteEncryptionHeader(entry.DosTime << 16); } else { WriteEncryptionHeader(entry.Crc); } } }