/// <summary> /// Determines the full path name of the <see cref="CentralDirectoryHeader"/> by decoding the name bytes with the provided /// encoding. /// </summary> /// <param name="directory">The ZIP driectory.</param> /// <param name="encoding">The encoding to decode the bytes with.</param> public static string GetComment(this ZipDirectory directory, Encoding encoding) { if (encoding == null) { encoding = Encoding.GetEncoding(codepage: 0); } return(encoding.GetString(directory.Comment)); }
/// <summary> /// Reads the data for an uncompressed file entry. This method fails if the entry is compressed. /// </summary> /// <param name="directory">The ZIP directory instance containing the provided <paramref name="entry"/>.</param> /// <param name="entry">The central directory header to read the uncompressed file data for.</param> /// <returns>The uncompressed file data.</returns> public async Task <byte[]> ReadUncompressedFileDataAsync(ZipDirectory directory, CentralDirectoryHeader entry) { if (entry.CompressionMethod != (ushort)ZipCompressionMethod.Store) { throw new ArgumentException(Strings.OnlyUncompressedSupported, nameof(entry)); } if (entry.CompressedSize != entry.UncompressedSize) { throw new MiniZipException(Strings.CentralDirectoryUncompressedDoesNotMatchCompressed); } if (entry.Zip64DataFields.Any()) { throw new MiniZipException(Strings.ReadingFileDataNotSupportedZip64); } var localEntry = await ReadLocalFileHeaderAsync(directory, entry, readDataDescriptor : false); if (localEntry.CompressionMethod != (ushort)ZipCompressionMethod.Store) { throw new MiniZipException(Strings.CompressionMethodDoesNotMatchCentralDirectory); } if ((localEntry.Flags & (ushort)ZipEntryFlags.DataDescriptor) != 0) { throw new MiniZipException(Strings.ReadingFileDataNotSupportedFileDescriptor); } if (localEntry.CompressedSize != entry.CompressedSize) { throw new MiniZipException(Strings.CentralDirectoryFileSizeDoesNotMatchLocalSize); } var data = new byte[localEntry.CompressedSize]; await ReadFullyAsync(data); return(data); }
/// <summary> /// Read the stream and gather all of the ZIP directory metadata. This extracts ZIP entry information and /// relative offsets. This method does not read the file entry contents or decompress anything. This method /// also does not decrypt anything. /// </summary> /// <returns>The ZIP directory metadata.</returns> public async Task <ZipDirectory> ReadAsync() { if (IsDisposed) { throw new ObjectDisposedException(nameof(ZipDirectoryReader)); } var zip = new ZipDirectory(); var lazyBuffer = new Lazy <byte[]>(() => new byte[0xffff]); zip.OffsetAfterEndOfCentralDirectory = await LocateBlockWithSignatureAsync( ZipConstants.EndOfCentralDirectorySignature, _stream.Length, ZipConstants.EndOfCentralDirectorySize, 0xffff, lazyBuffer); if (zip.OffsetAfterEndOfCentralDirectory < 0) { throw new MiniZipException(Strings.CannotFindCentralDirectory); } using (var reader = await LoadBinaryReaderAsync(_buffer, ZipConstants.EndOfCentralDirectorySizeWithoutSignature)) { zip.NumberOfThisDisk = reader.ReadUInt16(); zip.DiskWithStartOfCentralDirectory = reader.ReadUInt16(); zip.EntriesInThisDisk = reader.ReadUInt16(); zip.EntriesForWholeCentralDirectory = reader.ReadUInt16(); zip.CentralDirectorySize = reader.ReadUInt32(); zip.OffsetOfCentralDirectory = reader.ReadUInt32(); zip.CommentSize = reader.ReadUInt16(); } zip.Comment = new byte[zip.CommentSize]; await ReadFullyAsync(zip.Comment); // Check if the archive is Zip64. ulong numberOfThisDisk; ulong diskWithStartOfCentralDirectory; ulong entriesInThisDisk; ulong entriesForWholeCentralDirectory; long offsetOfCentralDirectory; if (zip.NumberOfThisDisk == 0xffff || zip.DiskWithStartOfCentralDirectory == 0xffff || zip.EntriesInThisDisk == 0xffff || zip.EntriesForWholeCentralDirectory == 0xffff || zip.CentralDirectorySize == 0xffffffff || zip.OffsetOfCentralDirectory == 0xffffffff) { zip.Zip64 = new Zip64Directory(); zip.Zip64.OffsetAfterEndOfCentralDirectoryLocator = await LocateBlockWithSignatureAsync( ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature, zip.OffsetAfterEndOfCentralDirectory - sizeof(uint), ZipConstants.Zip64EndOfCentralDirectoryLocatorSize, 0x1000, lazyBuffer); if (zip.Zip64.OffsetAfterEndOfCentralDirectoryLocator < 0) { throw new MiniZipException(Strings.CannotFindZip64Locator); } using (var buffer = await LoadBinaryReaderAsync(_buffer, ZipConstants.Zip64EndOfCentralDirectoryLocatorSizeWithoutSignature)) { zip.Zip64.DiskWithStartOfEndOfCentralDirectory = buffer.ReadUInt32(); zip.Zip64.EndOfCentralDirectoryOffset = buffer.ReadUInt64(); zip.Zip64.TotalNumberOfDisks = buffer.ReadUInt32(); } Seek((long)zip.Zip64.EndOfCentralDirectoryOffset); if (await ReadUInt32Async() != ZipConstants.Zip64EndOfCentralDirectorySignature) { throw new MiniZipException(Strings.InvalidZip64CentralDirectorySignature); } using (var buffer = await LoadBinaryReaderAsync(_buffer, ZipConstants.Zip64EndOfCentralDirectorySizeWithoutSignature)) { zip.Zip64.SizeOfCentralDirectoryRecord = buffer.ReadUInt64(); zip.Zip64.VersionMadeBy = buffer.ReadUInt16(); zip.Zip64.VersionToExtract = buffer.ReadUInt16(); zip.Zip64.NumberOfThisDisk = buffer.ReadUInt32(); zip.Zip64.DiskWithStartOfCentralDirectory = buffer.ReadUInt32(); zip.Zip64.EntriesInThisDisk = buffer.ReadUInt64(); zip.Zip64.EntriesForWholeCentralDirectory = buffer.ReadUInt64(); zip.Zip64.CentralDirectorySize = buffer.ReadUInt64(); zip.Zip64.OffsetOfCentralDirectory = buffer.ReadUInt64(); } numberOfThisDisk = zip.Zip64.NumberOfThisDisk; diskWithStartOfCentralDirectory = zip.Zip64.DiskWithStartOfCentralDirectory; entriesInThisDisk = zip.Zip64.EntriesInThisDisk; entriesForWholeCentralDirectory = zip.Zip64.EntriesForWholeCentralDirectory; offsetOfCentralDirectory = (long)zip.Zip64.OffsetOfCentralDirectory; if ((zip.Zip64.NumberOfThisDisk != zip.NumberOfThisDisk && zip.NumberOfThisDisk != 0xffff) || (zip.Zip64.DiskWithStartOfCentralDirectory != zip.DiskWithStartOfCentralDirectory && zip.DiskWithStartOfCentralDirectory != 0xffff) || (zip.Zip64.EntriesInThisDisk != zip.EntriesInThisDisk && zip.EntriesInThisDisk != 0xffff) || (zip.Zip64.EntriesForWholeCentralDirectory != zip.EntriesForWholeCentralDirectory && zip.EntriesForWholeCentralDirectory != 0xffff) || (zip.Zip64.CentralDirectorySize != zip.CentralDirectorySize && zip.CentralDirectorySize != 0xffffffff) || (zip.Zip64.OffsetOfCentralDirectory != zip.OffsetOfCentralDirectory && zip.OffsetOfCentralDirectory != 0xffffffff)) { throw new MiniZipException(Strings.InconsistentZip64Metadata); } } else { numberOfThisDisk = zip.NumberOfThisDisk; diskWithStartOfCentralDirectory = zip.DiskWithStartOfCentralDirectory; entriesInThisDisk = zip.EntriesInThisDisk; entriesForWholeCentralDirectory = zip.EntriesForWholeCentralDirectory; offsetOfCentralDirectory = zip.OffsetOfCentralDirectory; } if (numberOfThisDisk != 0 || diskWithStartOfCentralDirectory != 0 || entriesInThisDisk != entriesForWholeCentralDirectory) { throw new MiniZipException(Strings.ArchivesSpanningMultipleDisksNotSupported); } Seek(offsetOfCentralDirectory); zip.Entries = new List <CentralDirectoryHeader>(); for (ulong i = 0; i < entriesInThisDisk; i++) { zip.Entries.Add(await ReadEntryAsync()); } return(zip); }
private async Task <DataDescriptor> ReadDataDescriptor( ZipDirectory directory, List <CentralDirectoryHeader> entries, CentralDirectoryHeader entry, Zip64DataField zip64, LocalFileHeader localEntry) { var compressedSize = zip64?.CompressedSize ?? entry.CompressedSize; var uncompressedSize = zip64?.UncompressedSize ?? entry.UncompressedSize; var dataDescriptorPosition = entry.LocalHeaderOffset + ZipConstants.LocalFileHeaderSize + localEntry.NameSize + localEntry.ExtraFieldSize + compressedSize; _stream.Position = (long)dataDescriptorPosition; var dataDescriptor = new DataDescriptor(); using (var buffer = await LoadBinaryReaderAsync(_buffer, ZipConstants.DataDescriptorSize)) { var fieldA = buffer.ReadUInt32(); var fieldB = buffer.ReadUInt32(); var fieldC = buffer.ReadUInt32(); var fieldD = buffer.ReadUInt32(); // Check the first field to see if is the signature. This is the most reliable check but can yield // false negatives. This is because it's possible for the CRC-32 to be the same as the optional // data descriptor signature. There is no possibility for false positive. That is, if the first // byte does not match signature, then the first byte is definitely the CRC-32. var firstFieldImpliesNoSignature = fieldA != ZipConstants.DataDescriptorSignature; // 1. Use the known field values from the central directory, given the first field matches the signature. var valuesImplySignature = !firstFieldImpliesNoSignature && fieldB == entry.Crc32 && fieldC == compressedSize && fieldD == uncompressedSize; if (valuesImplySignature) { return(CreateDataDescriptor(fieldB, fieldC, fieldD, hasSignature: true)); } // 2. Use the known field values from the central directory. var valuesImplyNoSignature = fieldA == entry.Crc32 && fieldB == compressedSize && fieldC == uncompressedSize; if (valuesImplyNoSignature) { return(CreateDataDescriptor(fieldA, fieldB, fieldC, hasSignature: false)); } // 3. Use just the signature. if (!firstFieldImpliesNoSignature) { return(CreateDataDescriptor(fieldB, fieldC, fieldD, hasSignature: true)); } return(CreateDataDescriptor(fieldA, fieldB, fieldC, hasSignature: false)); } }
/// <summary> /// Reads a specific local file header given a central directory header for a file. /// </summary> /// <param name="directory">The ZIP directory instance containing the provided <paramref name="entry"/>.</param> /// <param name="entry">The central directory header to read the local file header for.</param> /// <param name="readDataDescriptor">Whether or not to read the data descriptor, if present.</param> /// <returns>The local file header.</returns> public async Task <LocalFileHeader> ReadLocalFileHeaderAsync(ZipDirectory directory, CentralDirectoryHeader entry, bool readDataDescriptor) { if (directory == null) { throw new ArgumentNullException(nameof(directory)); } if (entry == null) { throw new ArgumentNullException(nameof(entry)); } var entries = directory.Entries ?? new List <CentralDirectoryHeader>(); if (!entries.Contains(entry)) { throw new ArgumentException(Strings.HeaderDoesNotMatchDirectory, nameof(entry)); } if ((entry.Flags & (ushort)ZipEntryFlags.Encrypted) != 0) { throw new MiniZipException(Strings.EncryptedFilesNotSupported); } var zip64 = entry.Zip64DataFields.FirstOrDefault(); var localHeaderOffset = zip64?.LocalHeaderOffset ?? entry.LocalHeaderOffset; _stream.Position = (long)localHeaderOffset; if (await ReadUInt32Async() != ZipConstants.LocalFileHeaderSignature) { throw new MiniZipException(Strings.InvalidLocalFileHeaderSignature); } var localEntry = new LocalFileHeader(); using (var buffer = await LoadBinaryReaderAsync(_buffer, ZipConstants.LocalFileHeaderSizeWithoutSignature)) { localEntry.VersionToExtract = buffer.ReadUInt16(); localEntry.Flags = buffer.ReadUInt16(); localEntry.CompressionMethod = buffer.ReadUInt16(); localEntry.LastModifiedTime = buffer.ReadUInt16(); localEntry.LastModifiedDate = buffer.ReadUInt16(); localEntry.Crc32 = buffer.ReadUInt32(); localEntry.CompressedSize = buffer.ReadUInt32(); localEntry.UncompressedSize = buffer.ReadUInt32(); localEntry.NameSize = buffer.ReadUInt16(); localEntry.ExtraFieldSize = buffer.ReadUInt16(); } localEntry.Name = new byte[localEntry.NameSize]; await ReadFullyAsync(localEntry.Name); localEntry.ExtraField = new byte[localEntry.ExtraFieldSize]; await ReadFullyAsync(localEntry.ExtraField); localEntry.DataFields = ReadDataFields(localEntry.ExtraField); localEntry.Zip64DataFields = ReadZip64DataFields(localEntry); // Try to read the data descriptor. if (readDataDescriptor && (localEntry.Flags & (ushort)ZipEntryFlags.DataDescriptor) != 0) { localEntry.DataDescriptor = await ReadDataDescriptor(directory, entries, entry, zip64, localEntry); } return(localEntry); }
/// <summary> /// Reads a specific local file header given a central directory header for a file. /// </summary> /// <param name="directory">The ZIP directory instance containing the provided <paramref name="entry"/>.</param> /// <param name="entry">The central directory header to read the local file header for.</param> /// <returns>The local file header.</returns> public async Task <LocalFileHeader> ReadLocalFileHeaderAsync(ZipDirectory directory, CentralDirectoryHeader entry) { return(await ReadLocalFileHeaderAsync(directory, entry, readDataDescriptor : true)); }
/// <summary> /// Determines the comment of the <see cref="ZipDirectory"/> by decoding the comment bytes. Uses the default code page. /// </summary> /// <param name="directory">The ZIP driectory.</param> public static string GetComment(this ZipDirectory directory) { return(directory.GetComment(encoding: null)); }