Exemple #1
0
        /// <summary>
        /// Reads the contents of the archive's Central Directory and returns an ArchiveInformation record with the
        /// CompressedSize and UncompressedSize fields populated from the contents of the Central Directory file header
        /// records.
        /// </summary>
        /// <param name="eocdRecord">The End of Central Directory record which gives us information about the Central Directory's location</param>
        /// <returns>The pre-populated ArchiveInformation record</returns>
        /// <exception cref="InvalidArchiveException"></exception>
        private ArchiveInformation ReadCentralDirectoryContents(ZipEndOfCentralDirectoryRecord eocdRecord)
        {
            // -- Get a new info record
            ArchiveInformation info = new ArchiveInformation();

            info.UncompressedSize = 0;
            info.CompressedSize   = 0;

            // -- Open the correct archive part and seek to the first Central Directory record
            Open(eocdRecord.CDDisk + 1);
            InputStream.Seek((long)eocdRecord.CDOffset, SeekOrigin.Begin);

            // -- Loop all entries
            for (int i = 0; i < eocdRecord.NumFilesInCD; i++)
            {
                ZipCentralDirectoryFileHeader cdHeader = new ZipCentralDirectoryFileHeader();

                cdHeader.Signature = ReadULong();

                if (cdHeader.Signature != BitConverter.ToUInt32(new byte[] { 0x50, 0x4b, 0x01, 0x02 }, 0))
                {
                    throw new InvalidArchiveException(String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_CD_HEADER_AT_POSITION"), CurrentPartNumber, InputStream.Position - 4));
                }

                cdHeader.VersionMadeBy          = ReadUShort();
                cdHeader.VersionToExtract       = ReadUShort();
                cdHeader.Flags                  = ReadUShort();
                cdHeader.CompressionMethod      = ReadUShort();
                cdHeader.LastModTime            = ReadUShort();
                cdHeader.LastModDate            = ReadUShort();
                cdHeader.CRC32                  = ReadULong();
                cdHeader.CompressedSize         = ReadULong();
                cdHeader.UncompressedSize       = ReadULong();
                cdHeader.FileNameLength         = ReadUShort();
                cdHeader.ExtraFieldLength       = ReadUShort();
                cdHeader.FileCommentLength      = ReadUShort();
                cdHeader.DiskNumberStart        = ReadUShort();
                cdHeader.InternalFileAttributes = ReadUShort();
                cdHeader.ExternalFileAttributes = ReadULong();
                cdHeader.RelativeOffset         = ReadULong();
                cdHeader.Filename               = ReadUtf8String(cdHeader.FileNameLength);
                cdHeader.Comment                = "";

                if (cdHeader.FileCommentLength > 0)
                {
                    cdHeader.Comment = ReadUtf8String(cdHeader.FileCommentLength);
                }

                info.CompressedSize   += cdHeader.CompressedSize;
                info.UncompressedSize += cdHeader.UncompressedSize;
            }
            return(info);
        }
Exemple #2
0
    // すべてのファイルの書き込みが完了したのでセントラルディレクトリヘッダ等を集中的に書き出す
    protected override async Task FinishAsyncImpl(CancellationToken cancel = default)
    {
        checked
        {
            long sizeOfCentralDirectory   = this.FooterBuffer.LongLength;
            long offsetOfCentralDirectory = this.Writer.CurrentPosition;

            // セントラルディレクトリヘッダ (一時バッファに溜めていたものを今こそ全部一気に書き出す)
            this.FooterBuffer.Seek(0, SeekOrigin.Begin);
            await this.FooterBuffer.CopyToSequentialWritableAsync(this.Writer, cancel);

            var memory = Sync(() =>
            {
                checked
                {
                    bool useZip64 = false;

                    // ZIP64 形式のヘッダが必要かどうか判定する
                    if (NumTotalFiles > ushort.MaxValue)
                    {
                        useZip64 = true;
                    }
                    if (sizeOfCentralDirectory > uint.MaxValue)
                    {
                        useZip64 = true;
                    }
                    if (offsetOfCentralDirectory > uint.MaxValue)
                    {
                        useZip64 = true;
                    }

                    Packet p = new Packet();

                    long offsetStartZip64CentralDirectoryRecord = this.Writer.CurrentPosition;

                    if (useZip64)
                    {
                        // ZIP64 エンドオブセントラルディレクトリレコードの追記
                        ref Zip64EndOfCentralDirectoryRecord endZip64Record = ref p.AppendSpan <Zip64EndOfCentralDirectoryRecord>();

                        endZip64Record.Signature = ZipConsts.Zip64EndOfCentralDirectorySignature._LE_Endian32_U();

                        endZip64Record.SizeOfZip64EndOfCentralDirectoryRecord = ((ulong)(Util.SizeOfStruct <Zip64EndOfCentralDirectoryRecord>() - 12))._LE_Endian64_U();

                        endZip64Record.MadeVersion                            = ZipFileVersion.Ver4_5;
                        endZip64Record.MadeFileSystemType                     = ZipFileSystemType.Ntfs;
                        endZip64Record.NeedVersion                            = ZipFileVersion.Ver2_0;
                        endZip64Record.Reserved                               = 0;
                        endZip64Record.NumberOfThisDisk                       = 0;
                        endZip64Record.DiskNumberStart                        = 0;
                        endZip64Record.TotalNumberOfCentralDirectory          = ((ulong)NumTotalFiles)._LE_Endian64_U();
                        endZip64Record.TotalNumberOfEntriesOnCentralDirectory = ((ulong)NumTotalFiles)._LE_Endian64_U();
                        endZip64Record.SizeOfCentralDirectory                 = ((ulong)sizeOfCentralDirectory)._LE_Endian64_U();
                        endZip64Record.OffsetStartCentralDirectory            = ((ulong)offsetOfCentralDirectory)._LE_Endian64_U();

                        // ZIP64 エンドオブセントラルディレクトリロケータの追記
                        ref Zip64EndOfCentralDirectoryLocator zip64Locator = ref p.AppendSpan <Zip64EndOfCentralDirectoryLocator>();

                        zip64Locator.Signature        = ZipConsts.Zip64EndOfCentralDirectoryLocatorSignature;
                        zip64Locator.NumberOfThisDisk = 0;
                        zip64Locator.OffsetStartZip64CentralDirectoryRecord = offsetStartZip64CentralDirectoryRecord._LE_Endian64_U();
                        zip64Locator.TotalNumberOfDisk = 1._LE_Endian32_U();
                    }

                    // エンドオブセントラルディレクトリレコード
                    ref ZipEndOfCentralDirectoryRecord endRecord = ref p.AppendSpan <ZipEndOfCentralDirectoryRecord>();

                    endRecord.Signature = ZipConsts.EndOfCentralDirectorySignature._LE_Endian32_U();

                    if (useZip64 == false)
                    {
                        endRecord.NumberOfThisDisk = 0;
                        endRecord.NumberOfCentralDirectoryOnThisDisk = NumTotalFiles._LE_Endian16_U();
                        endRecord.TotalNumberOfCentralDirectory      = NumTotalFiles._LE_Endian16_U();
                        endRecord.SizeOfCentralDirectory             = ((uint)sizeOfCentralDirectory)._LE_Endian32_U();
                        endRecord.OffsetStartCentralDirectory        = ((uint)offsetOfCentralDirectory)._LE_Endian32_U();
                    }
                    else
                    {
                        endRecord.NumberOfThisDisk = 0xFFFF;
                        endRecord.NumberOfCentralDirectoryOnThisDisk = 0xFFFF;
                        endRecord.TotalNumberOfCentralDirectory      = 0xFFFF;
                        endRecord.SizeOfCentralDirectory             = 0xFFFFFFFF;
                        endRecord.OffsetStartCentralDirectory        = 0xFFFFFFFF;
                    }

                    endRecord.CommentLength = 0;

                    return(p.ToMemory());
                }
            });
Exemple #3
0
        /// <summary>
        /// Locates and reads the End of Central Directory record. It also reads through the entire central directory.
        /// This must be called at the beginning of extraction.
        /// </summary>
        /// <returns>The EOCD record of the archive</returns>
        /// <exception cref="InvalidArchiveException"></exception>
        private ZipEndOfCentralDirectoryRecord ReadEndOfCentralDirectory()
        {
            Open(Parts);
            long localOffset = InputStream.Length - 22;

            /**
             * The EOCD record is 22 to infinity bytes long. Its first 22 bytes are a pre-defined data record, whereas
             * the rest are the ZIP file comment. In order to determine its location relative to the archive's EOF I
             * chose to implement an inneficient backwards sliding window algorithm. We start by reading the last 22
             * bytes of the archive. If the header is not found, we keep sliding backwards, one byte at a time until
             * we either locate the header or reach the BOF. The latter case means we don't have a valid archive. This
             * shouldn't happen, unless the archive was truncated in transit.
             */
            try
            {
                do
                {
                    InputStream.Seek(localOffset, SeekOrigin.Begin);

                    byte[] buffer = ReadBytes(4);

                    if (isEOCD(buffer))
                    {
                        break;
                    }

                    localOffset--;
                } while (localOffset > 0);
            }
            catch (Exception)
            {
                throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_ZIP_EOCD_NOT_FOUND"));
            }

            // EOCD not found within the last part. That's a violation of the ZIP standard.
            if (localOffset < 0)
            {
                throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_ZIP_EOCD_NOT_FOUND"));
            }

            // Go back to the EOCD offset and let's read the contents
            ZipEndOfCentralDirectoryRecord eocdRecord = new ZipEndOfCentralDirectoryRecord();

            InputStream.Seek(localOffset, SeekOrigin.Begin);

            eocdRecord.Signature     = ReadULong();
            eocdRecord.DiskNumber    = ReadUShort();
            eocdRecord.CDDisk        = ReadUShort();
            eocdRecord.DiskCDEntries = ReadUShort();
            eocdRecord.NumFilesInCD  = ReadUShort();
            eocdRecord.CDLength      = ReadULong();
            eocdRecord.CDOffset      = ReadULong();
            eocdRecord.CommentLength = ReadUShort();
            eocdRecord.Comment       = "";

            if (eocdRecord.CommentLength > 0)
            {
                eocdRecord.Comment = ReadUtf8String(eocdRecord.CommentLength);
            }

            // Now we can go to the beginning of the Central Directory and read its contents. We need to do that to get
            // the comrpessed and uncompressed size counts.
            var info = ReadCentralDirectoryContents(eocdRecord);

            // Invoke the archiveInformation event. We need to do some work to get there, through...

            // -- Get the total archive size by looping all of its parts
            info.ArchiveSize = 0;

            for (int i = 1; i <= Parts; i++)
            {
                FileInfo fi = new FileInfo(ArchivePath);
                info.ArchiveSize += (ulong)fi.Length;
            }

            eocdRecord.TotalSize = info.ArchiveSize;

            // -- Incorporate bits from the file header
            info.FileCount = eocdRecord.NumFilesInCD;
            // -- Create the event arguments object
            ArchiveInformationEventArgs args = new ArchiveInformationEventArgs(info);

            // -- Finally, invoke the event
            OnArchiveInformationEvent(args);

            return(eocdRecord);
        }