// There is a small chance that something very weird could happen here. The code calling into this function // will ask for a value from the extra field if the field was masked with FF's. It's theoretically possible // that a field was FF's legitimately, and the writer didn't decide to write the corresponding extra field. // Also, at the same time, other fields were masked with FF's to indicate looking in the zip64 record. // Then, the search for the zip64 record will fail because the expected size is wrong, // and a nulled out Zip64ExtraField will be returned. Thus, even though there was Zip64 data, // it will not be used. It is questionable whether this situation is possible to detect // unlike the other functions that have try-pattern semantics, these functions always return a // Zip64ExtraField. If a Zip64 extra field actually doesn't exist, all of the fields in the // returned struct will be null // // If there are more than one Zip64 extra fields, we take the first one that has the expected size // public static Zip64ExtraField GetJustZip64Block(Stream extraFieldStream, bool readUncompressedSize, bool readCompressedSize, bool readLocalHeaderOffset, bool readStartDiskNumber) { Zip64ExtraField zip64Field; using (BinaryReader reader = new BinaryReader(extraFieldStream)) { ZipGenericExtraField currentExtraField; while (ZipGenericExtraField.TryReadBlock(reader, extraFieldStream.Length, out currentExtraField)) { if (TryGetZip64BlockFromGenericExtraField(currentExtraField, readUncompressedSize, readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field)) { return(zip64Field); } } } zip64Field = new Zip64ExtraField(); zip64Field._compressedSize = null; zip64Field._uncompressedSize = null; zip64Field._localHeaderOffset = null; zip64Field._startDiskNumber = null; return(zip64Field); }
public static Zip64ExtraField GetAndRemoveZip64Block(List <ZipGenericExtraField> extraFields, bool readUncompressedSize, bool readCompressedSize, bool readLocalHeaderOffset, bool readStartDiskNumber) { Zip64ExtraField zip64Field = new Zip64ExtraField(); zip64Field._compressedSize = null; zip64Field._uncompressedSize = null; zip64Field._localHeaderOffset = null; zip64Field._startDiskNumber = null; List <ZipGenericExtraField> markedForDelete = new List <ZipGenericExtraField>(); bool zip64FieldFound = false; foreach (ZipGenericExtraField ef in extraFields) { if (ef.Tag == TagConstant) { markedForDelete.Add(ef); if (!zip64FieldFound) { if (TryGetZip64BlockFromGenericExtraField(ef, readUncompressedSize, readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field)) { zip64FieldFound = true; } } } } foreach (ZipGenericExtraField ef in markedForDelete) { extraFields.Remove(ef); } return(zip64Field); }
public static List <ZipGenericExtraField> GetExtraFields(BinaryReader reader) { // assumes that TrySkipBlock has already been called, so we don't have to validate twice List <ZipGenericExtraField> result; const int OffsetToFilenameLength = 26; // from the point before the signature reader.BaseStream.Seek(OffsetToFilenameLength, SeekOrigin.Current); ushort filenameLength = reader.ReadUInt16(); ushort extraFieldLength = reader.ReadUInt16(); reader.BaseStream.Seek(filenameLength, SeekOrigin.Current); using (Stream str = new SubReadStream(reader.BaseStream, reader.BaseStream.Position, extraFieldLength)) { result = ZipGenericExtraField.ParseExtraField(str); } Zip64ExtraField.RemoveZip64Blocks(result); return(result); }
// should only throw an exception in extremely exceptional cases because it is called from dispose internal void WriteCentralDirectoryFileHeader() { // This part is simple, because we should definitely know the sizes by this time BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream); // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and // reading in should not be able to produce an entryname longer than ushort.MaxValue Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue); // decide if we need the Zip64 extra field: Zip64ExtraField zip64ExtraField = new Zip64ExtraField(); uint compressedSizeTruncated, uncompressedSizeTruncated, offsetOfLocalHeaderTruncated; bool zip64Needed = false; if (SizesTooLarge() #if DEBUG_FORCE_ZIP64 || _archive._forceZip64 #endif ) { zip64Needed = true; compressedSizeTruncated = ZipHelper.Mask32Bit; uncompressedSizeTruncated = ZipHelper.Mask32Bit; // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH, but we do it anyways zip64ExtraField.CompressedSize = _compressedSize; zip64ExtraField.UncompressedSize = _uncompressedSize; } else { compressedSizeTruncated = ( uint )_compressedSize; uncompressedSizeTruncated = ( uint )_uncompressedSize; } if (_offsetOfLocalHeader > uint.MaxValue #if DEBUG_FORCE_ZIP64 || _archive._forceZip64 #endif ) { zip64Needed = true; offsetOfLocalHeaderTruncated = ZipHelper.Mask32Bit; // If we have one of the sizes, the other must go in there as speced for LH, but not necessarily for CH, but we do it anyways zip64ExtraField.LocalHeaderOffset = _offsetOfLocalHeader; } else { offsetOfLocalHeaderTruncated = ( uint )_offsetOfLocalHeader; } if (zip64Needed) { VersionToExtractAtLeast(ZipVersionNeededValues.Zip64); } // determine if we can fit zip64 extra field and original extra fields all in int bigExtraFieldLength = (zip64Needed ? zip64ExtraField.TotalSize : 0) + (_cdUnknownExtraFields != null ? ZipGenericExtraField.TotalSize(_cdUnknownExtraFields) : 0); ushort extraFieldLength; if (bigExtraFieldLength > ushort.MaxValue) { extraFieldLength = ( ushort )(zip64Needed ? zip64ExtraField.TotalSize : 0); _cdUnknownExtraFields = null; } else { extraFieldLength = ( ushort )bigExtraFieldLength; } writer.Write(ZipCentralDirectoryFileHeader.SignatureConstant); // Central directory file header signature (4 bytes) writer.Write(( byte )_versionMadeBySpecification); // Version made by Specification (version) (1 byte) writer.Write(( byte )CurrentZipPlatform); // Version made by Compatibility (type) (1 byte) writer.Write(( ushort )_versionToExtract); // Minimum version needed to extract (2 bytes) writer.Write(( ushort )_generalPurposeBitFlag); // General Purpose bit flag (2 bytes) writer.Write(( ushort )CompressionMethod); // The Compression method (2 bytes) writer.Write(ZipHelper.DateTimeToDosTime(_lastModified.DateTime)); // File last modification time and date (4 bytes) writer.Write(_crc32); // CRC-32 (4 bytes) writer.Write(compressedSizeTruncated); // Compressed Size (4 bytes) writer.Write(uncompressedSizeTruncated); // Uncompressed Size (4 bytes) writer.Write(( ushort )_storedEntryNameBytes.Length); // File Name Length (2 bytes) writer.Write(extraFieldLength); // Extra Field Length (2 bytes) // This should hold because of how we read it originally in ZipCentralDirectoryFileHeader: Debug.Assert((_fileComment == null) || (_fileComment.Length <= ushort.MaxValue)); writer.Write(_fileComment != null ? ( ushort )_fileComment.Length : ( ushort )0); // file comment length writer.Write(( ushort )0); // disk number start writer.Write(( ushort )0); // internal file attributes writer.Write(_externalFileAttr); // external file attributes writer.Write(offsetOfLocalHeaderTruncated); // offset of local header writer.Write(_storedEntryNameBytes); // write extra fields if (zip64Needed) { zip64ExtraField.WriteBlock(_archive.ArchiveStream); } if (_cdUnknownExtraFields != null) { ZipGenericExtraField.WriteAllBlocks(_cdUnknownExtraFields, _archive.ArchiveStream); } if (_fileComment != null) { writer.Write(_fileComment); } }
// if saveExtraFieldsAndComments is false, FileComment and ExtraFields will be null // in either case, the zip64 extra field info will be incorporated into other fields public static bool TryReadBlock(BinaryReader reader, bool saveExtraFieldsAndComments, out ZipCentralDirectoryFileHeader header) { header = new ZipCentralDirectoryFileHeader(); if (reader.ReadUInt32() != SignatureConstant) { return(false); } header.VersionMadeBySpecification = reader.ReadByte(); header.VersionMadeByCompatibility = reader.ReadByte(); header.VersionNeededToExtract = reader.ReadUInt16(); header.GeneralPurposeBitFlag = reader.ReadUInt16(); header.CompressionMethod = reader.ReadUInt16(); header.LastModified = reader.ReadUInt32(); header.Crc32 = reader.ReadUInt32(); uint compressedSizeSmall = reader.ReadUInt32(); uint uncompressedSizeSmall = reader.ReadUInt32(); header.FilenameLength = reader.ReadUInt16(); header.ExtraFieldLength = reader.ReadUInt16(); header.FileCommentLength = reader.ReadUInt16(); ushort diskNumberStartSmall = reader.ReadUInt16(); header.InternalFileAttributes = reader.ReadUInt16(); header.ExternalFileAttributes = reader.ReadUInt32(); 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)) { if (saveExtraFieldsAndComments) { header.ExtraFields = ZipGenericExtraField.ParseExtraField(str); zip64 = Zip64ExtraField.GetAndRemoveZip64Block(header.ExtraFields, uncompressedSizeInZip64, compressedSizeInZip64, relativeOffsetInZip64, diskNumberStartInZip64); } else { header.ExtraFields = null; 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. reader.BaseStream.AdvanceToPosition(endExtraFields); if (saveExtraFieldsAndComments) { header.FileComment = reader.ReadBytes(header.FileCommentLength); } else { reader.BaseStream.Position += header.FileCommentLength; header.FileComment = null; } header.UncompressedSize = zip64.UncompressedSize == null ? uncompressedSizeSmall : zip64.UncompressedSize.Value; header.CompressedSize = zip64.CompressedSize == null ? compressedSizeSmall : zip64.CompressedSize.Value; header.RelativeOffsetOfLocalHeader = zip64.LocalHeaderOffset == null ? relativeOffsetOfLocalHeaderSmall : zip64.LocalHeaderOffset.Value; header.DiskNumberStart = zip64.StartDiskNumber == null ? diskNumberStartSmall : zip64.StartDiskNumber.Value; return(true); }
private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField extraField, bool readUncompressedSize, bool readCompressedSize, bool readLocalHeaderOffset, bool readStartDiskNumber, out Zip64ExtraField zip64Block) { zip64Block = new Zip64ExtraField(); zip64Block._compressedSize = null; zip64Block._uncompressedSize = null; zip64Block._localHeaderOffset = null; zip64Block._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 (BinaryReader reader = new BinaryReader(ms)) { 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("file uncompressed size too big"); } if (zip64Block._compressedSize < 0) { throw new InvalidDataException("file compressed size too big"); } if (zip64Block._localHeaderOffset < 0) { throw new InvalidDataException("file local header offset too big"); } if (zip64Block._startDiskNumber < 0) { throw new InvalidDataException("file start dis number too big"); } return(true); } } finally { if (ms != null) { ms.Dispose(); } } }