/// <summary> /// Test a local header against that provided from the central directory /// </summary> /// <param name="entry"> /// The entry to test against /// </param> /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param> /// <returns>The offset of the entries data in the file</returns> private long TestLocalHeader(ZipEntry entry, HeaderTest tests) { lock (baseStream_) { bool testHeader = (tests & HeaderTest.Header) != 0; bool testData = (tests & HeaderTest.Extract) != 0; baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin); if ((int) ReadLEUint() != ZipConstants.LocalHeaderSignature) { throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset)); } short extractVersion = (short) ReadLEUshort(); short localFlags = (short) ReadLEUshort(); short compressionMethod = (short) ReadLEUshort(); short fileTime = (short) ReadLEUshort(); short fileDate = (short) ReadLEUshort(); uint crcValue = ReadLEUint(); long compressedSize = ReadLEUint(); long size = ReadLEUint(); int storedNameLength = ReadLEUshort(); int extraDataLength = ReadLEUshort(); byte[] nameData = new byte[storedNameLength]; StreamUtils.ReadFully(baseStream_, nameData); byte[] extraData = new byte[extraDataLength]; StreamUtils.ReadFully(baseStream_, extraData); ZipExtraData ed = new ZipExtraData(extraData); // Extra data / zip64 checks if (ed.Find(1)) { // TODO Check for tag values being distinct.. Multiple zip64 tags means what? // Zip64 extra data but 'extract version' is too low if (extractVersion < ZipConstants.VersionZip64) { throw new ZipException( string.Format("Extra data contains Zip64 information but version {0}.{1} is not high enough", extractVersion/10, extractVersion%10)); } // Zip64 extra data but size fields dont indicate its required. if (((uint) size != uint.MaxValue) && ((uint) compressedSize != uint.MaxValue)) { throw new ZipException("Entry sizes not correct for Zip64"); } size = ed.ReadLong(); compressedSize = ed.ReadLong(); } else { // No zip64 extra data but entry requires it. if ((extractVersion >= ZipConstants.VersionZip64) && (((uint) size == uint.MaxValue) || ((uint) compressedSize == uint.MaxValue))) { throw new ZipException("Required Zip64 extended information missing"); } } if (testData) { if (entry.IsFile) { if (!entry.IsCompressionMethodSupported()) { throw new ZipException("Compression method not supported"); } if ((extractVersion > ZipConstants.VersionMadeBy) || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64))) { throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion)); } if ((localFlags & (int) (GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0) { throw new ZipException("The library does not support the zip version required to extract this entry"); } } } if (testHeader) { if ((extractVersion <= 63) && // Ignore later versions as we dont know about them.. (extractVersion != 10) && (extractVersion != 11) && (extractVersion != 20) && (extractVersion != 21) && (extractVersion != 25) && (extractVersion != 27) && (extractVersion != 45) && (extractVersion != 46) && (extractVersion != 50) && (extractVersion != 51) && (extractVersion != 52) && (extractVersion != 61) && (extractVersion != 62) && (extractVersion != 63) ) { throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion)); } // Local entry flags dont have reserved bit set on. if ((localFlags & (int) (GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0) { throw new ZipException("Reserved bit flags cannot be set."); } // Encryption requires extract version >= 20 if (((localFlags & (int) GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20)) { throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); } // Strong encryption requires encryption flag to be set and extract version >= 50. if ((localFlags & (int) GeneralBitFlags.StrongEncryption) != 0) { if ((localFlags & (int) GeneralBitFlags.Encrypted) == 0) { throw new ZipException("Strong encryption flag set but encryption flag is not set"); } if (extractVersion < 50) { throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion)); } } // Patched entries require extract version >= 27 if (((localFlags & (int) GeneralBitFlags.Patched) != 0) && (extractVersion < 27)) { throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion)); } // Central header flags match local entry flags. if (localFlags != entry.Flags) { throw new ZipException("Central header/local header flags mismatch"); } // Central header compression method matches local entry if (entry.CompressionMethod != (CompressionMethod) compressionMethod) { throw new ZipException("Central header/local header compression method mismatch"); } // Strong encryption and extract version match if ((localFlags & (int) GeneralBitFlags.StrongEncryption) != 0) { if (extractVersion < 62) { throw new ZipException("Strong encryption flag set but version not high enough"); } } if ((localFlags & (int) GeneralBitFlags.HeaderMasked) != 0) { if ((fileTime != 0) || (fileDate != 0)) { throw new ZipException("Header masked set but date/time values non-zero"); } } if ((localFlags & (int) GeneralBitFlags.Descriptor) == 0) { if (crcValue != (uint) entry.Crc) { throw new ZipException("Central header/local header crc mismatch"); } } // Crc valid for empty entry. // This will also apply to streamed entries where size isnt known and the header cant be patched if ((size == 0) && (compressedSize == 0)) { if (crcValue != 0) { throw new ZipException("Invalid CRC for empty entry"); } } // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably if (entry.Name.Length > storedNameLength) { throw new ZipException("File name length mismatch"); } // Name data has already been read convert it and compare. string localName = ZipConstants.ConvertToStringExt(localFlags, nameData); // Central directory and local entry name match if (localName != entry.Name) { throw new ZipException("Central header and local header file name mismatch"); } // Directories have zero size. if (entry.IsDirectory) { if ((compressedSize != 0) || (size != 0)) { throw new ZipException("Directory cannot have size"); } } if (!ZipNameTransform.IsValidName(localName, true)) { throw new ZipException("Name is invalid"); } } // Tests that apply to both data and header. // Size can be verified only if it is known in the local header. // it will always be known in the central header. if ((localFlags & (int) GeneralBitFlags.Descriptor) == 0 || (size != 0 || compressedSize != 0)) { if (size != entry.Size) { throw new ZipException( string.Format("Size mismatch between central header({0}) and local header({1})", entry.Size, size)); } if (compressedSize != entry.CompressedSize) { throw new ZipException( string.Format("Compressed size mismatch between central header({0}) and local header({1})", entry.CompressedSize, compressedSize)); } } int extraLength = storedNameLength + extraDataLength; return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength; } }
/// <summary> /// Complete cleanup as the final part of closing. /// </summary> /// <param name="testCrc">True if the crc value should be tested</param> private void CompleteCloseEntry(bool testCrc) { StopDecrypting(); if ((flags & 8) != 0) { ReadDataDescriptor(); } size = 0; if (testCrc && ((crc.Value & 0xFFFFFFFFL) != entry.Crc) && (entry.Crc != -1)) { throw new ZipException("CRC mismatch"); } crc.Reset(); if (method == (int) CompressionMethod.Deflated) { inf.Reset(); } entry = null; }
/// <summary> /// Make a new <see cref="ZipEntry"/> from a name. /// </summary> /// <param name="fileName">The name of the file to create a new entry for.</param> /// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param> /// <returns>Returns a new <see cref="ZipEntry"/> based on the <paramref name="fileName"/>.</returns> public ZipEntry MakeFileEntry(string fileName, bool useFileSystem) { ZipEntry result = new ZipEntry(nameTransform_.TransformFile(fileName)); result.IsUnicodeText = isUnicodeText_; int externalAttributes = 0; bool useAttributes = (setAttributes_ != 0); FileInfo fi = null; if (useFileSystem) { fi = new FileInfo(fileName); } if ((fi != null) && fi.Exists) { switch (timeSetting_) { case TimeSetting.CreateTime: result.DateTime = fi.CreationTime; break; case TimeSetting.CreateTimeUtc: #if NETCF_1_0 || NETCF_2_0 result.DateTime = fi.CreationTime.ToUniversalTime(); #else result.DateTime = fi.CreationTimeUtc; #endif break; case TimeSetting.LastAccessTime: result.DateTime = fi.LastAccessTime; break; case TimeSetting.LastAccessTimeUtc: #if NETCF_1_0 || NETCF_2_0 result.DateTime = fi.LastAccessTime.ToUniversalTime(); #else result.DateTime = fi.LastAccessTimeUtc; #endif break; case TimeSetting.LastWriteTime: result.DateTime = fi.LastWriteTime; break; case TimeSetting.LastWriteTimeUtc: #if NETCF_1_0 || NETCF_2_0 result.DateTime = fi.LastWriteTime.ToUniversalTime(); #else result.DateTime = fi.LastWriteTimeUtc; #endif break; case TimeSetting.Fixed: result.DateTime = fixedDateTime_; break; default: throw new ZipException("Unhandled time setting in MakeFileEntry"); } result.Size = fi.Length; useAttributes = true; externalAttributes = ((int) fi.Attributes & getAttributes_); } else { if (timeSetting_ == TimeSetting.Fixed) { result.DateTime = fixedDateTime_; } } if (useAttributes) { externalAttributes |= setAttributes_; result.ExternalFileAttributes = externalAttributes; } return result; }
/// <summary> /// Opens a Zip file reading the given <see cref="Stream"/>. /// </summary> /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param> /// <exception cref="IOException"> /// An i/o error occurs /// </exception> /// <exception cref="ZipException"> /// The file doesn't contain a valid zip archive.<br/> /// The stream provided cannot seek /// </exception> public ZipFile(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } if (!stream.CanSeek) { throw new ArgumentException("Stream is not seekable", "stream"); } baseStream_ = stream; isStreamOwner = true; if (baseStream_.Length > 0) { try { ReadEntries(); } catch { DisposeInternal(true); throw; } } else { entries_ = new ZipEntry[0]; isNewArchive_ = true; } }
/// <summary> /// Gets an input stream for reading the given zip entry data in an uncompressed form. /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry(). /// </summary> /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param> /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns> /// <exception cref="InvalidOperationException"> /// The ZipFile has already been closed /// </exception> /// <exception cref="ZipException"> /// The compression method for the entry is unknown /// </exception> /// <exception cref="IndexOutOfRangeException"> /// The entry is not found in the ZipFile /// </exception> public Stream GetInputStream(ZipEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } if (entries_ == null) { throw new InvalidOperationException("ZipFile has closed"); } long index = entry.ZipFileIndex; if ((index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name)) { index = FindEntry(entry.Name, true); if (index < 0) { throw new ZipException("Entry cannot be found"); } } return GetInputStream(index); }
private Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry) { CryptoStream result = null; if ((entry.Version < ZipConstants.VersionStrongEncryption) || (entry.Flags & (int) GeneralBitFlags.StrongEncryption) == 0) { PkzipClassicManaged classicManaged = new PkzipClassicManaged(); OnKeysRequired(entry.Name); if (HaveKeys == false) { throw new ZipException("No password available for encrypted stream"); } // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream // which doesnt do this. result = new CryptoStream(new UncompressedStream(baseStream), classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write); if ((entry.Crc < 0) || (entry.Flags & 8) != 0) { WriteEncryptionHeader(result, entry.DosTime << 16); } else { WriteEncryptionHeader(result, entry.Crc); } } return result; }
public ZipEntryEnumerator(ZipEntry[] entries) { array = entries; }
/// <summary> /// Get an output stream for the specified <see cref="ZipEntry"/> /// </summary> /// <param name="entry">The entry to get an output stream for.</param> /// <returns>The output stream obtained for the entry.</returns> private Stream GetOutputStream(ZipEntry entry) { Stream result = baseStream_; if (entry.IsCrypted) { #if NETCF_1_0 throw new ZipException("Encryption not supported for Compact Framework 1.0"); #else result = CreateAndInitEncryptionStream(result, entry); #endif } switch (entry.CompressionMethod) { case CompressionMethod.Stored: result = new UncompressedStream(result); break; case CompressionMethod.Deflated: DeflaterOutputStream dos = new DeflaterOutputStream(result, new Deflater(9, true)); dos.IsStreamOwner = false; result = dos; break; default: throw new ZipException("Unknown compression method " + entry.CompressionMethod); } return result; }
public ZipUpdate(string fileName, ZipEntry entry) { command_ = UpdateCommand.Add; entry_ = entry; filename_ = fileName; }
private int FindExistingUpdate(ZipEntry entry) { int result = -1; string convertedName = GetTransformedFileName(entry.Name); if (updateIndex_.ContainsKey(convertedName)) { result = (int) updateIndex_[convertedName]; } /* // This is slow like the coming of the next ice age but takes less storage and may be useful // for CF? for (int index = 0; index < updates_.Count; ++index) { ZipUpdate zu = ( ZipUpdate )updates_[index]; if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) && (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) { result = index; break; } } */ return result; }
internal void SetEntry(ZipEntry entry) { entry_ = entry; entryValid_ = true; bytesTested_ = 0; }
private int WriteCentralDirectoryHeader(ZipEntry entry) { if (entry.CompressedSize < 0) { throw new ZipException("Attempt to write central directory entry with unknown csize"); } if (entry.Size < 0) { throw new ZipException("Attempt to write central directory entry with unknown size"); } if (entry.Crc < 0) { throw new ZipException("Attempt to write central directory entry with unknown crc"); } // Write the central file header WriteLEInt(ZipConstants.CentralHeaderSignature); // Version made by WriteLEShort(ZipConstants.VersionMadeBy); // Version required to extract WriteLEShort(entry.Version); WriteLEShort(entry.Flags); unchecked { WriteLEShort((byte) entry.CompressionMethod); WriteLEInt((int) entry.DosTime); WriteLEInt((int) entry.Crc); } if ((entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff)) { WriteLEInt(-1); } else { WriteLEInt((int) (entry.CompressedSize & 0xffffffff)); } if ((entry.IsZip64Forced()) || (entry.Size >= 0xffffffff)) { WriteLEInt(-1); } else { WriteLEInt((int) entry.Size); } byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); if (name.Length > 0xFFFF) { throw new ZipException("Entry name is too long."); } WriteLEShort(name.Length); // Central header extra data is different to local header version so regenerate. ZipExtraData ed = new ZipExtraData(entry.ExtraData); if (entry.CentralHeaderRequiresZip64) { ed.StartNewEntry(); if ((entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On)) { ed.AddLeLong(entry.Size); } if ((entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On)) { ed.AddLeLong(entry.CompressedSize); } if (entry.Offset >= 0xffffffff) { ed.AddLeLong(entry.Offset); } // Number of disk on which this file starts isnt supported and is never written here. ed.AddNewEntry(1); } else { // Should have already be done when local header was added. ed.Delete(1); } byte[] centralExtraData = ed.GetEntryData(); WriteLEShort(centralExtraData.Length); WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0); WriteLEShort(0); // disk number WriteLEShort(0); // internal file attributes // External file attributes... if (entry.ExternalFileAttributes != -1) { WriteLEInt(entry.ExternalFileAttributes); } else { if (entry.IsDirectory) { WriteLEUint(16); } else { WriteLEUint(0); } } if (entry.Offset >= 0xffffffff) { WriteLEUint(0xffffffff); } else { WriteLEUint((uint) (int) entry.Offset); } if (name.Length > 0) { baseStream_.Write(name, 0, name.Length); } if (centralExtraData.Length > 0) { baseStream_.Write(centralExtraData, 0, centralExtraData.Length); } byte[] rawComment = (entry.Comment != null) ? Encoding.ASCII.GetBytes(entry.Comment) : new byte[0]; if (rawComment.Length > 0) { baseStream_.Write(rawComment, 0, rawComment.Length); } return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length; }
/// <summary> /// Delete a <see cref="ZipEntry"/> from the archive. /// </summary> /// <param name="entry">The entry to delete.</param> public void Delete(ZipEntry entry) { CheckUpdating(); int index = FindExistingUpdate(entry); if (index >= 0) { contentsEdited_ = true; updates_[index] = null; updateCount_ -= 1; } else { throw new ZipException("Cannot find entry to delete"); } }
/// <summary> /// Add a <see cref="ZipEntry"/> that contains no data. /// </summary> /// <param name="entry">The entry to add.</param> /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks> public void Add(ZipEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } CheckUpdating(); if ((entry.Size != 0) || (entry.CompressedSize != 0)) { throw new ZipException("Entry cannot have any data"); } AddUpdate(new ZipUpdate(UpdateCommand.Add, entry)); }
/// <summary> /// Locate the data for a given entry. /// </summary> /// <returns> /// The start offset of the data. /// </returns> /// <exception cref="System.IO.EndOfStreamException"> /// The stream ends prematurely /// </exception> /// <exception cref="ZipException"> /// The local header signature is invalid, the entry and central header file name lengths are different /// or the local and entry compression methods dont match /// </exception> private long LocateEntry(ZipEntry entry) { return TestLocalHeader(entry, HeaderTest.Extract); }
public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod) { command_ = UpdateCommand.Add; entry_ = new ZipEntry(entryName); entry_.CompressionMethod = compressionMethod; filename_ = fileName; }
private Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry) { CryptoStream result = null; if ((entry.Version < ZipConstants.VersionStrongEncryption) || (entry.Flags & (int) GeneralBitFlags.StrongEncryption) == 0) { PkzipClassicManaged classicManaged = new PkzipClassicManaged(); OnKeysRequired(entry.Name); if (HaveKeys == false) { throw new ZipException("No password available for encrypted stream"); } result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read); CheckClassicPassword(result, entry); } else { throw new ZipException("Decryption method not supported"); } return result; }
public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod) { command_ = UpdateCommand.Add; entry_ = new ZipEntry(entryName); entry_.CompressionMethod = compressionMethod; dataSource_ = dataSource; }
private static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry) { byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize]; StreamUtils.ReadFully(classicCryptoStream, cryptbuffer); if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) { throw new ZipException("Invalid password"); } }
public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry) { command_ = UpdateCommand.Add; entry_ = entry; dataSource_ = dataSource; }
/// <summary> /// Get a <see cref="Stream"/> providing data for an entry. /// </summary> /// <param name="entry">The entry to provide data for.</param> /// <param name="name">The file name for data if known.</param> /// <returns>Returns a stream providing data; or null if not available</returns> public Stream GetSource(ZipEntry entry, string name) { Stream result = null; if (name != null) { result = File.OpenRead(name); } return result; }
public ZipUpdate(ZipEntry original, ZipEntry updated) { throw new ZipException("Modify not currently supported"); /* command_ = UpdateCommand.Modify; entry_ = ( ZipEntry )original.Clone(); outEntry_ = ( ZipEntry )updated.Clone(); */ }
/// <summary> /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage. /// </summary> internal ZipFile() { entries_ = new ZipEntry[0]; isNewArchive_ = true; }
public ZipUpdate(UpdateCommand command, ZipEntry entry) { command_ = command; entry_ = (ZipEntry) entry.Clone(); }
/// <summary> /// Advances to the next entry in the archive /// </summary> /// <returns> /// The next <see cref="ZipEntry">entry</see> in the archive or null if there are no more entries. /// </returns> /// <remarks> /// If the previous entry is still open <see cref="CloseEntry">CloseEntry</see> is called. /// </remarks> /// <exception cref="InvalidOperationException"> /// Input stream is closed /// </exception> /// <exception cref="ZipException"> /// Password is not set, password is invalid, compression method is invalid, /// version required to extract is not supported /// </exception> public ZipEntry GetNextEntry() { if (crc == null) { throw new InvalidOperationException("Closed."); } if (entry != null) { CloseEntry(); } int header = inputBuffer.ReadLeInt(); if (header == ZipConstants.CentralHeaderSignature || header == ZipConstants.EndOfCentralDirectorySignature || header == ZipConstants.CentralHeaderDigitalSignature || header == ZipConstants.ArchiveExtraDataSignature || header == ZipConstants.Zip64CentralFileHeaderSignature) { // No more individual entries exist Close(); return null; } // -jr- 07-Dec-2003 Ignore spanning temporary signatures if found // Spanning signature is same as descriptor signature and is untested as yet. if ((header == ZipConstants.SpanningTempSignature) || (header == ZipConstants.SpanningSignature)) { header = inputBuffer.ReadLeInt(); } if (header != ZipConstants.LocalHeaderSignature) { throw new ZipException("Wrong Local header signature: 0x" + String.Format("{0:X}", header)); } short versionRequiredToExtract = (short) inputBuffer.ReadLeShort(); flags = inputBuffer.ReadLeShort(); method = inputBuffer.ReadLeShort(); uint dostime = (uint) inputBuffer.ReadLeInt(); int crc2 = inputBuffer.ReadLeInt(); csize = inputBuffer.ReadLeInt(); size = inputBuffer.ReadLeInt(); int nameLen = inputBuffer.ReadLeShort(); int extraLen = inputBuffer.ReadLeShort(); bool isCrypted = (flags & 1) == 1; byte[] buffer = new byte[nameLen]; inputBuffer.ReadRawBuffer(buffer); string name = ZipConstants.ConvertToStringExt(flags, buffer); entry = new ZipEntry(name, versionRequiredToExtract); entry.Flags = flags; entry.CompressionMethod = (CompressionMethod) method; if ((flags & 8) == 0) { entry.Crc = crc2 & 0xFFFFFFFFL; entry.Size = size & 0xFFFFFFFFL; entry.CompressedSize = csize & 0xFFFFFFFFL; entry.CryptoCheckValue = (byte) ((crc2 >> 24) & 0xff); } else { // This allows for GNU, WinZip and possibly other archives, the PKZIP spec // says these values are zero under these circumstances. if (crc2 != 0) { entry.Crc = crc2 & 0xFFFFFFFFL; } if (size != 0) { entry.Size = size & 0xFFFFFFFFL; } if (csize != 0) { entry.CompressedSize = csize & 0xFFFFFFFFL; } entry.CryptoCheckValue = (byte) ((dostime >> 8) & 0xff); } entry.DosTime = dostime; // If local header requires Zip64 is true then the extended header should contain // both values. // Handle extra data if present. This can set/alter some fields of the entry. if (extraLen > 0) { byte[] extra = new byte[extraLen]; inputBuffer.ReadRawBuffer(extra); entry.ExtraData = extra; } entry.ProcessExtraData(true); if (entry.CompressedSize >= 0) { csize = entry.CompressedSize; } if (entry.Size >= 0) { size = entry.Size; } if (method == (int) CompressionMethod.Stored && (!isCrypted && csize != size || (isCrypted && csize - ZipConstants.CryptoHeaderSize != size))) { throw new ZipException("Stored, but compressed != uncompressed"); } // Determine how to handle reading of data if this is attempted. if (entry.IsCompressionMethodSupported()) { internalReader = InitialRead; } else { internalReader = ReadingNotSupported; } return entry; }
/// <summary> /// Copy an existing entry. /// </summary> /// <param name="entry">The existing entry to copy.</param> public ZipUpdate(ZipEntry entry) : this(UpdateCommand.Copy, entry) { // Do nothing. }
/// <summary> /// Closes the zip input stream /// </summary> public override void Close() { internalReader = ReadingNotAvailable; crc = null; entry = null; base.Close(); }
/// <summary> /// Search for and read the central directory of a zip file filling the entries array. /// </summary> /// <exception cref="System.IO.IOException"> /// An i/o error occurs. /// </exception> /// <exception cref="ZipException"> /// The central directory is malformed or cannot be found /// </exception> private void ReadEntries() { // Search for the End Of Central Directory. When a zip comment is // present the directory will start earlier // // 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 // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then // this could be invalid. // Could also speed this up by reading memory in larger blocks. if (baseStream_.CanSeek == false) { throw new ZipException("ZipFile stream must be seekable"); } long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature, baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff); if (locatedEndOfCentralDir < 0) { throw new ZipException("Cannot find central directory"); } // Read end of central directory record ushort thisDiskNumber = ReadLEUshort(); ushort startCentralDirDisk = ReadLEUshort(); ulong entriesForThisDisk = ReadLEUshort(); ulong entriesForWholeCentralDir = ReadLEUshort(); ulong centralDirSize = ReadLEUint(); long offsetOfCentralDir = ReadLEUint(); uint commentSize = ReadLEUshort(); if (commentSize > 0) { byte[] comment = new byte[commentSize]; StreamUtils.ReadFully(baseStream_, comment); comment_ = ZipConstants.ConvertToString(comment); } else { comment_ = string.Empty; } bool isZip64 = false; // Check if zip64 header information is required. if ((thisDiskNumber == 0xffff) || (startCentralDirDisk == 0xffff) || (entriesForThisDisk == 0xffff) || (entriesForWholeCentralDir == 0xffff) || (centralDirSize == 0xffffffff) || (offsetOfCentralDir == 0xffffffff)) { isZip64 = true; long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, 0x1000); if (offset < 0) { throw new ZipException("Cannot find Zip64 locator"); } // number of the disk with the start of the zip64 end of central directory 4 bytes // relative offset of the zip64 end of central directory record 8 bytes // total number of disks 4 bytes ReadLEUint(); // startDisk64 is not currently used ulong offset64 = ReadLEUlong(); uint totalDisks = ReadLEUint(); baseStream_.Position = (long) offset64; long sig64 = ReadLEUint(); if (sig64 != ZipConstants.Zip64CentralFileHeaderSignature) { throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64)); } // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12. ulong recordSize = ReadLEUlong(); int versionMadeBy = ReadLEUshort(); int versionToExtract = ReadLEUshort(); uint thisDisk = ReadLEUint(); uint centralDirDisk = ReadLEUint(); entriesForThisDisk = ReadLEUlong(); entriesForWholeCentralDir = ReadLEUlong(); centralDirSize = ReadLEUlong(); offsetOfCentralDir = (long) ReadLEUlong(); // NOTE: zip64 extensible data sector (variable size) is ignored. } entries_ = new ZipEntry[entriesForThisDisk]; // SFX/embedded support, find the offset of the first entry vis the start of the stream // This applies to Zip files that are appended to the end of an SFX stub. // Or are appended as a resource to an executable. // Zip files created by some archivers have the offsets altered to reflect the true offsets // and so dont require any adjustment here... // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths? if (!isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long) centralDirSize))) { offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long) centralDirSize + offsetOfCentralDir); if (offsetOfFirstEntry <= 0) { throw new ZipException("Invalid embedded zip archive"); } } baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin); for (ulong i = 0; i < entriesForThisDisk; i++) { if (ReadLEUint() != ZipConstants.CentralHeaderSignature) { throw new ZipException("Wrong Central Directory signature"); } int versionMadeBy = ReadLEUshort(); int versionToExtract = ReadLEUshort(); int bitFlags = ReadLEUshort(); int method = ReadLEUshort(); uint dostime = ReadLEUint(); uint crc = ReadLEUint(); long csize = ReadLEUint(); long size = ReadLEUint(); int nameLen = ReadLEUshort(); int extraLen = ReadLEUshort(); int commentLen = ReadLEUshort(); int diskStartNo = ReadLEUshort(); // Not currently used int internalAttributes = ReadLEUshort(); // Not currently used uint externalAttributes = ReadLEUint(); long offset = ReadLEUint(); byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen); string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen); ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod) method); entry.Crc = crc & 0xffffffffL; entry.Size = size & 0xffffffffL; entry.CompressedSize = csize & 0xffffffffL; entry.Flags = bitFlags; entry.DosTime = dostime; entry.ZipFileIndex = (long) i; entry.Offset = offset; entry.ExternalFileAttributes = (int) externalAttributes; if ((bitFlags & 8) == 0) { entry.CryptoCheckValue = (byte) (crc >> 24); } else { entry.CryptoCheckValue = (byte) ((dostime >> 8) & 0xff); } if (extraLen > 0) { byte[] extra = new byte[extraLen]; StreamUtils.ReadFully(baseStream_, extra); entry.ExtraData = extra; } entry.ProcessExtraData(false); if (commentLen > 0) { StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen); entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen); } entries_[i] = entry; } }
/// <summary> /// Make a new <see cref="ZipEntry"></see> for a directory. /// </summary> /// <param name="directoryName">The raw untransformed name for the new directory</param> /// <param name="useFileSystem">If true entry detail is retrieved from the file system if the file exists.</param> /// <returns>Returns a new <see cref="ZipEntry"></see> representing a directory.</returns> public ZipEntry MakeDirectoryEntry(string directoryName, bool useFileSystem) { ZipEntry result = new ZipEntry(nameTransform_.TransformDirectory(directoryName)); result.Size = 0; int externalAttributes = 0; DirectoryInfo di = null; if (useFileSystem) { di = new DirectoryInfo(directoryName); } if ((di != null) && di.Exists) { switch (timeSetting_) { case TimeSetting.CreateTime: result.DateTime = di.CreationTime; break; case TimeSetting.CreateTimeUtc: #if NETCF_1_0 || NETCF_2_0 result.DateTime = di.CreationTime.ToUniversalTime(); #else result.DateTime = di.CreationTimeUtc; #endif break; case TimeSetting.LastAccessTime: result.DateTime = di.LastAccessTime; break; case TimeSetting.LastAccessTimeUtc: #if NETCF_1_0 || NETCF_2_0 result.DateTime = di.LastAccessTime.ToUniversalTime(); #else result.DateTime = di.LastAccessTimeUtc; #endif break; case TimeSetting.LastWriteTime: result.DateTime = di.LastWriteTime; break; case TimeSetting.LastWriteTimeUtc: #if NETCF_1_0 || NETCF_2_0 result.DateTime = di.LastWriteTime.ToUniversalTime(); #else result.DateTime = di.LastWriteTimeUtc; #endif break; case TimeSetting.Fixed: result.DateTime = fixedDateTime_; break; default: throw new ZipException("Unhandled time setting in MakeDirectoryEntry"); } externalAttributes = ((int) di.Attributes & getAttributes_); } else { if (timeSetting_ == TimeSetting.Fixed) { result.DateTime = fixedDateTime_; } } // Always set directory attribute on. externalAttributes |= (setAttributes_ | 16); result.ExternalFileAttributes = externalAttributes; return result; }
private void ExtractEntry(ZipEntry entry) { bool doExtraction = false; string nameText = entry.Name; if (entry.IsFile) { // TODO: Translate invalid names allowing extraction still. doExtraction = NameIsValid(nameText) && entry.IsCompressionMethodSupported(); } else if (entry.IsDirectory) { doExtraction = NameIsValid(nameText); } // TODO: Fire delegate were compression method not supported, or name is invalid? string dirName = null; string targetName = null; if (doExtraction) { // Handle invalid entry names by chopping of path root. if (Path.IsPathRooted(nameText)) { string workName = Path.GetPathRoot(nameText); nameText = nameText.Substring(workName.Length); } if (nameText.Length > 0) { targetName = Path.Combine(targetDirectory_, nameText); if (entry.IsDirectory) { dirName = targetName; } else { dirName = Path.GetDirectoryName(Path.GetFullPath(targetName)); } } else { doExtraction = false; } } if (doExtraction && !Directory.Exists(dirName)) { if (!entry.IsDirectory || CreateEmptyDirectories) { try { Directory.CreateDirectory(dirName); } catch (Exception ex) { doExtraction = false; if (events_ != null) { if (entry.IsDirectory) { continueRunning_ = events_.OnDirectoryFailure(targetName, ex); } else { continueRunning_ = events_.OnFileFailure(targetName, ex); } } else { continueRunning_ = false; } } } } if (doExtraction && entry.IsFile) { ExtractFileEntry(entry, targetName); } }