internal static Zip64ExtraField GetJustZip64Block(Stream extraFieldStream, bool readUncompressedSize, bool readCompressedSize, bool readLocalHeaderOffset, bool readStartDiskNumber) { Zip64ExtraField zip64Field; using (var reader = new BinaryReader(extraFieldStream, _utf8EncodingNoBOM)) { while (ZipGenericExtraField.TryReadBlock(reader, extraFieldStream.Length, out var currentExtraField)) { if (TryGetZip64BlockFromGenericExtraField(currentExtraField, readUncompressedSize, readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field)) { return(zip64Field); } } } zip64Field = new Zip64ExtraField { CompressedSize = null, UncompressedSize = null, LocalHeaderOffset = null, StartDiskNumber = null }; return(zip64Field); }
// 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); }
private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField extraField, bool readUncompressedSize, bool readCompressedSize, bool readLocalHeaderOffset, bool readStartDiskNumber, out Zip64ExtraField zip64Block) { zip64Block = new Zip64ExtraField { CompressedSize = null, UncompressedSize = null, LocalHeaderOffset = null, StartDiskNumber = null }; if (extraField.Tag != TagConstant) { return(false); } // this pattern needed because nested using blocks trigger CA2202 MemoryStream?ms = null; try { ms = new MemoryStream(extraField.Data); using var reader = new BinaryReader(ms); // Why did they do this and how does it still work?! ms = null; zip64Block._size = extraField.Size; ushort expectedSize = 0; if (readUncompressedSize) { expectedSize += 8; } if (readCompressedSize) { expectedSize += 8; } if (readLocalHeaderOffset) { expectedSize += 8; } if (readStartDiskNumber) { expectedSize += 4; } // if it is not the expected size, perhaps there is another extra field that matches if (expectedSize != zip64Block._size) { return(false); } if (readUncompressedSize) { zip64Block.UncompressedSize = reader.ReadInt64(); } if (readCompressedSize) { zip64Block.CompressedSize = reader.ReadInt64(); } if (readLocalHeaderOffset) { zip64Block.LocalHeaderOffset = reader.ReadInt64(); } if (readStartDiskNumber) { zip64Block.StartDiskNumber = reader.ReadInt32(); } // original values are unsigned, so implies value is too big to fit in signed integer if (zip64Block.UncompressedSize < 0) { throw new InvalidDataException(SR.FieldTooBigUncompressedSize); } if (zip64Block.CompressedSize < 0) { throw new InvalidDataException(SR.FieldTooBigCompressedSize); } if (zip64Block.LocalHeaderOffset < 0) { throw new InvalidDataException(SR.FieldTooBigLocalHeaderOffset); } if (zip64Block.StartDiskNumber < 0) { throw new InvalidDataException(SR.FieldTooBigStartDiskNumber); } return(true); } finally { ms?.Dispose(); } }