private static string GetString(CentralDirectoryHeader entry, Encoding encoding, byte[] bytes) { if (encoding == null) { encoding = Encoding.GetEncoding(codepage: 0); } else if (encoding != Encoding.UTF8 && (entry.Flags & (ushort)ZipEntryFlags.UTF8) != 0) { throw new ArgumentException(Strings.UTF8Mismatch); } return(encoding.GetString(bytes)); }
private List <Zip64CentralDirectoryDataField> ReadZip64DataFields(CentralDirectoryHeader entry, ushort maximumDataSize) { var zip64DataFields = new List <Zip64CentralDirectoryDataField>(); foreach (var dataField in entry.DataFields) { if (dataField.HeaderId != ZipConstants.Zip64DataFieldHeaderId) { continue; } if (dataField.DataSize > maximumDataSize) { throw new MiniZipException(Strings.InvalidZip64ExtendedInformationLength); } using (var reader = GetBinaryReader(dataField.Data)) { var field = new Zip64CentralDirectoryDataField(); if (entry.UncompressedSize == 0xffffffff) { field.UncompressedSize = reader.ReadUInt64(); } if (entry.CompressedSize == 0xffffffff) { field.CompressedSize = reader.ReadUInt64(); } if (entry.LocalHeaderOffset == 0xffffffff) { field.LocalHeaderOffset = reader.ReadUInt64(); } if (entry.DiskNumberStart == 0xffff) { field.DiskNumberStart = reader.ReadUInt32(); } if (reader.BaseStream.Position < reader.BaseStream.Length) { throw new MiniZipException(Strings.NotAllZip64ExtendedInformationWasRead); } zip64DataFields.Add(field); } } return(zip64DataFields); }
/// <summary> /// Determine the last modified time of the <see cref="CentralDirectoryHeader"/>. The encoded format of the last modified time /// is MS-DOS format. There is no timezone information associated with the output. Defaults to 1980-01-01 if the /// last modified time is invalid. /// </summary> /// <param name="entry">The ZIP entry.</param> public static DateTime GetLastModified(this CentralDirectoryHeader entry) { var year = 1980 + ((entry.LastModifiedDate & 0b1111_1110_0000_0000) >> 9); var month = ((entry.LastModifiedDate & 0b0000_0001_1110_0000) >> 5); var day = ((entry.LastModifiedDate & 0b0000_0000_0001_1111)); var hour = ((entry.LastModifiedTime & 0b1111_1000_0000_0000) >> 11); var minute = ((entry.LastModifiedTime & 0b0000_0111_1110_0000) >> 5); var second = 2 * ((entry.LastModifiedTime & 0b0000_0000_0001_1111)); try { return(new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified)); } catch { return(InvalidDateTime); } }
private async Task <CentralDirectoryHeader> ReadEntryAsync() { if (await ReadUInt32Async() != ZipConstants.CentralDirectoryEntryHeaderSignature) { throw new MiniZipException(Strings.InvalidCentralDirectorySignature); } var entry = new CentralDirectoryHeader(); using (var buffer = await LoadBinaryReaderAsync(_buffer, ZipConstants.CentralDirectoryEntryHeaderSizeWithoutSignature)) { entry.VersionMadeBy = buffer.ReadUInt16(); entry.VersionToExtract = buffer.ReadUInt16(); entry.Flags = buffer.ReadUInt16(); entry.CompressionMethod = buffer.ReadUInt16(); entry.LastModifiedTime = buffer.ReadUInt16(); entry.LastModifiedDate = buffer.ReadUInt16(); entry.Crc32 = buffer.ReadUInt32(); entry.CompressedSize = buffer.ReadUInt32(); entry.UncompressedSize = buffer.ReadUInt32(); entry.NameSize = buffer.ReadUInt16(); entry.ExtraFieldSize = buffer.ReadUInt16(); entry.CommentSize = buffer.ReadUInt16(); entry.DiskNumberStart = buffer.ReadUInt16(); entry.InternalAttributes = buffer.ReadUInt16(); entry.ExternalAttributes = buffer.ReadUInt32(); entry.LocalHeaderOffset = buffer.ReadUInt32(); } entry.Name = new byte[entry.NameSize]; await ReadFullyAsync(entry.Name); entry.ExtraField = new byte[entry.ExtraFieldSize]; await ReadFullyAsync(entry.ExtraField); entry.DataFields = ReadDataFields(entry.ExtraField); entry.Zip64DataFields = ReadZip64DataFields(entry); entry.Comment = new byte[entry.CommentSize]; await ReadFullyAsync(entry.Comment); return(entry); }
private List <Zip64LocalFileDataField> ReadZip64DataFields(LocalFileHeader entry) { // The Zip64 data field in the local file header is a subset of the information in the central directory // header, so use the central directory flow and map the input/output. var centralDirectoryHeader = new CentralDirectoryHeader { DataFields = entry.DataFields, UncompressedSize = entry.UncompressedSize, CompressedSize = entry.CompressedSize, }; var fields = ReadZip64DataFields(centralDirectoryHeader, ZipConstants.MaximumZip64LocalFileDataFieldSize); return(fields .Select(x => new Zip64LocalFileDataField { CompressedSize = x.CompressedSize, UncompressedSize = x.UncompressedSize, }) .ToList()); }
/// <summary> /// Determines the comment of the <see cref="CentralDirectoryHeader"/> by decoding the comment bytes with the provided /// encoding. /// </summary> /// <param name="entry">The ZIP entry.</param> /// <param name="encoding">The encoding to decode the bytes with.</param> public static string GetComment(this CentralDirectoryHeader entry, Encoding encoding) { return(GetString(entry, encoding, entry.Comment)); }
/// <summary> /// Determines the comment of the <see cref="CentralDirectoryHeader"/> by decoding the comment bytes. Uses UTF-8 /// encoding if the <see cref="FileData.Flags"/> indicate as such, otherwise uses the default code page. /// </summary> /// <param name="entry">The ZIP entry.</param> public static string GetComment(this CentralDirectoryHeader entry) { return(entry.GetComment(encoding: null)); }
/// <summary> /// Determines the full path name of the <see cref="CentralDirectoryHeader"/> by decoding the name bytes with the provided /// encoding. /// </summary> /// <param name="entry">The ZIP entry.</param> /// <param name="encoding">The encoding to decode the bytes with.</param> public static string GetName(this CentralDirectoryHeader entry, Encoding encoding) { return(GetString(entry, encoding, entry.Name)); }
/// <summary> /// Determine the uncompressed size of the <see cref="CentralDirectoryHeader"/>, in bytes. This method takes all relevant /// details in the entry into account. /// </summary> /// <param name="entry">The ZIP entry.</param> public static ulong GetUncompressedSize(this CentralDirectoryHeader entry) { return(entry.Zip64DataFields.SingleOrDefault()?.UncompressedSize ?? entry.UncompressedSize); }
private List <Zip64CentralDirectoryDataField> ReadZip64DataFields(CentralDirectoryHeader entry) { return(ReadZip64DataFields(entry, ZipConstants.MaximumZip64CentralDirectoryDataFieldSize)); }
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 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> /// 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)); }