Exemple #1
0
        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));
        }
Exemple #2
0
        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);
        }
Exemple #3
0
        /// <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);
            }
        }
Exemple #4
0
        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);
        }
Exemple #5
0
        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());
        }
Exemple #6
0
 /// <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));
 }
Exemple #7
0
 /// <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));
 }
Exemple #8
0
 /// <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));
 }
Exemple #9
0
 /// <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);
 }
Exemple #10
0
 private List <Zip64CentralDirectoryDataField> ReadZip64DataFields(CentralDirectoryHeader entry)
 {
     return(ReadZip64DataFields(entry, ZipConstants.MaximumZip64CentralDirectoryDataFieldSize));
 }
Exemple #11
0
        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));
            }
        }
Exemple #12
0
        /// <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);
        }
Exemple #13
0
        /// <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);
        }
Exemple #14
0
 /// <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));
 }