// Initializes, attaches it to archive internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd) { Archive = archive; _diskNumberStart = cd.DiskNumberStart; _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag; _compressionMethod = (CompressionMethodValues)cd.CompressionMethod; // Leave this as a uint and let the caller convert it if it wants (perf optimization) LastWriteTime = cd.LastModified; CompressedLength = cd.CompressedSize; Length = cd.UncompressedSize; ExternalAttributes = (int)cd.ExternalFileAttributes; _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader; // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldlength // but entryname/extra length could be different in LH _storedOffsetOfCompressedData = null; Crc32 = cd.Crc32; // Sacrifice a slight amount of time for safety. Zips entry names are emphatically NOT supposed to // have backslashes according to the spec, but they might anyway, so normalize them all to forward slashes. FullName = DecodeEntryName(cd.Filename)?.Replace('\\', '/') ?? throw new ArgumentNullException(nameof(FullName)); Name = ParseFileName(FullName, (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility); }
// Initializes, attaches it to archive internal ZipArchiveEntry(ZipArchiveFast archive, ZipCentralDirectoryFileHeader cd) { Archive = archive; _diskNumberStart = cd.DiskNumberStart; _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag; _compressionMethod = (CompressionMethodValues)cd.CompressionMethod; // Leave this as a uint and let the caller convert it if it wants (perf optimization) LastWriteTime = cd.LastModified; CompressedLength = cd.CompressedSize; Length = cd.UncompressedSize; _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader; // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldlength // but entryname/extra length could be different in LH _storedOffsetOfCompressedData = null; // Sacrifice a slight amount of time for safety. Zips entry names are emphatically NOT supposed to // have backslashes according to the spec, but they might anyway, so normalize them all to forward slashes. FullName = DecodeEntryName(cd.Filename).Replace('\\', '/'); // Lazy-load Name so we don't preemptively do a ton of Substring() calls when we don't need to. _versionMadeByCompatibility = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility; }
private void ReadCentralDirectory() { try { // assume ReadEndOfCentralDirectory has been called and has populated _centralDirectoryStart ArchiveStream.Seek(_centralDirectoryStart, SeekOrigin.Begin); long numberOfEntries = 0; //read the central directory while (ZipCentralDirectoryFileHeader.TryReadBlock(ArchiveReader, out var currentHeader)) { AddEntry(new ZipArchiveEntry(this, currentHeader)); numberOfEntries++; } if (numberOfEntries != _expectedNumberOfEntries) { throw new InvalidDataException(SR.NumEntriesWrong); } } catch (EndOfStreamException ex) { throw new InvalidDataException(SR.Format(SR.CentralDirectoryInvalid, ex)); } }
// if saveExtraFieldsAndComments is false, FileComment and ExtraFields will be null // in either case, the zip64 extra field info will be incorporated into other fields internal static bool TryReadBlock(BinaryReader reader, out ZipCentralDirectoryFileHeader header) { header = new ZipCentralDirectoryFileHeader(); if (reader.ReadUInt32() != SignatureConstant) { return(false); } reader.ReadByte(); // VersionMadeBySpecification header.VersionMadeByCompatibility = reader.ReadByte(); reader.ReadUInt16(); // VersionNeededToExtract header.GeneralPurposeBitFlag = reader.ReadUInt16(); header.CompressionMethod = reader.ReadUInt16(); header.LastModified = reader.ReadUInt32(); reader.ReadUInt32(); // Crc32 uint compressedSizeSmall = reader.ReadUInt32(); uint uncompressedSizeSmall = reader.ReadUInt32(); header.FilenameLength = reader.ReadUInt16(); header.ExtraFieldLength = reader.ReadUInt16(); header.FileCommentLength = reader.ReadUInt16(); ushort diskNumberStartSmall = reader.ReadUInt16(); reader.ReadUInt16(); // InternalFileAttributes reader.ReadUInt32(); // ExternalFileAttributes uint relativeOffsetOfLocalHeaderSmall = reader.ReadUInt32(); header.Filename = reader.ReadBytes(header.FilenameLength); bool uncompressedSizeInZip64 = uncompressedSizeSmall == ZipHelper.Mask32Bit; bool compressedSizeInZip64 = compressedSizeSmall == ZipHelper.Mask32Bit; bool relativeOffsetInZip64 = relativeOffsetOfLocalHeaderSmall == ZipHelper.Mask32Bit; bool diskNumberStartInZip64 = diskNumberStartSmall == ZipHelper.Mask16Bit; Zip64ExtraField zip64; long endExtraFields = reader.BaseStream.Position + header.ExtraFieldLength; using (Stream str = new SubReadStream(reader.BaseStream, reader.BaseStream.Position, header.ExtraFieldLength)) { zip64 = Zip64ExtraField.GetJustZip64Block(str, uncompressedSizeInZip64, compressedSizeInZip64, relativeOffsetInZip64, diskNumberStartInZip64); } // There are zip files that have malformed ExtraField blocks in which GetJustZip64Block() silently // bails out without reading all the way to the end of the ExtraField block. Thus we must force the // stream's position to the proper place. // Fen's note: Original did a seek here, which for some reason is like 300x slower than a read, and // also inexplicably causes ReadUInt32() to be 4x as slow and/or occur 4x as often(?!) // Buffer alignments...? I dunno. Anyway. Speed. // Also maybe not a good idea to use something that's faster when I don't know why it's faster. // But my results are the same as the old method, so herpaderp. reader.BaseStream.AdvanceToPosition(endExtraFields + header.FileCommentLength); header.UncompressedSize = zip64.UncompressedSize ?? uncompressedSizeSmall; header.CompressedSize = zip64.CompressedSize ?? compressedSizeSmall; header.RelativeOffsetOfLocalHeader = zip64.LocalHeaderOffset ?? relativeOffsetOfLocalHeaderSmall; header.DiskNumberStart = zip64.StartDiskNumber ?? diskNumberStartSmall; return(true); }