/// <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));
        }
Esempio n. 2
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);
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
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));
            }
        }
Esempio n. 5
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);
        }
Esempio n. 6
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));
 }
 /// <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));
 }