|
//-----------------------------------------------------------------------------
//------------- *** WARNING ***
//------------- This file is part of a legally monitored development project.
//------------- Do not check in changes to this project. Do not raid bugs on this
//------------- code in the main PS database. Do not contact the owner of this
//------------- code directly. Contact the legal team at ‘ZSLegal’ for assistance.
//------------- *** WARNING ***
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//
// <copyright file="ZipIOZip64EndOfCentralDirectoryBlock.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description:
// This is an internal class that enables interactions with Zip archives
// for OPC scenarios (Zip 64 bit support)
//
// History:
// 01/26/2005: IgorBel: Initial creation.
//
//-----------------------------------------------------------------------------
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Windows;
using MS.Internal.WindowsBase;
namespace MS.Internal.IO.Zip
{
internal class ZipIOZip64EndOfCentralDirectoryBlock : IZipIOBlock
{
// standard IZipIOBlock functionality
public long Offset
{
get
{
return _offset;
}
}
public long Size
{
get
{
return _size;
}
}
// This property will only return reliable result if UpdateReferences is called prior
public bool GetDirtyFlag(bool closingFlag)
{
return _dirtyFlag;
}
public void Move(long shiftSize)
{
if (shiftSize != 0)
{
checked{_offset +=shiftSize;}
if (_size > 0)
{
_dirtyFlag = true;
}
Debug.Assert(_offset >=0);
}
}
public void Save()
{
// this record is optional and shouldn't be saved if size is 0
if (GetDirtyFlag(true) && (Size > 0))
{
BinaryWriter writer = _blockManager.BinaryWriter;
if (_blockManager.Stream.Position != _offset)
{
// we need to seek , as current position isn't accurate
_blockManager.Stream.Seek(_offset, SeekOrigin.Begin);
}
writer.Write(_signatureConstant);
writer.Write(_sizeOfZip64EndOfCentralDirectory);
writer.Write(_versionMadeBy);
writer.Write(_versionNeededToExtract);
writer.Write(_numberOfThisDisk);
writer.Write(_numberOfTheDiskWithTheStartOfTheCentralDirectory);
writer.Write(_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk);
writer.Write(_totalNumberOfEntriesInTheCentralDirectory);
writer.Write(_sizeOfTheCentralDirectory);
writer.Write(_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber);
if (_sizeOfZip64EndOfCentralDirectory > _fixedMinimalValueOfSizeOfZip64EOCD)
{
Debug.Assert(_zip64ExtensibleDataSector != null);
Debug.Assert(_zip64ExtensibleDataSector.Length ==
checked((int)(_sizeOfZip64EndOfCentralDirectory -_fixedMinimalValueOfSizeOfZip64EOCD)));
writer.Write(_zip64ExtensibleDataSector,
0,
checked((int)(_sizeOfZip64EndOfCentralDirectory -_fixedMinimalValueOfSizeOfZip64EOCD)));
}
writer.Flush();
}
_dirtyFlag = false;
}
public void UpdateReferences(bool closingFlag)
{
checked
{
// check whether Central directory is loaded and update references accordingly
// if one or more of the following conditions are true
// 1. Central Directory is dirty
// 2. streaming mode
// if Central Directory isn't loaded or none of the relevant structure is dirty,
// there is nothing to update for Zip64 End Of Central directory record
if (_blockManager.IsCentralDirectoryBlockLoaded
&& (_blockManager.Streaming
|| _blockManager.CentralDirectoryBlock.GetDirtyFlag(closingFlag)))
{
if (_blockManager.CentralDirectoryBlock.IsZip64BitRequiredForStoring)
{
UInt64 centralDirCount = (UInt64)_blockManager.CentralDirectoryBlock.Count;
UInt64 centralDirBlockSize = (UInt64)_blockManager.CentralDirectoryBlock.Size;
UInt64 centralDirOffset = (UInt64)_blockManager.CentralDirectoryBlock.Offset;
// Here is a diagram of the record
//----------------------------------------------------------------------------------------------------------------------
//|SignatureConst (4 bytes)|sizeOfZip64Eocd (8 bytes)|misc fixed fields (44 bytes)|Variable Size Extensible Data sector|
//A------------------------B-------------------------C----------------------------D------------------------------------E
//
// in order to calculate the actual record size we subtract _fixedMinimalValueOfSizeOfZip64EOCD (This is a chunk marked
// (C,D) in thre diagram above) from _fixedMinimalRecordSize (This is a chunk marked (A,D) in the diagram above).
// Then we add the resulting value (which would be chunked marked (A,C) to the value of sizeOfZip64Eocd field which
// contains the size of the record starting at point (C) and going to the end (E). So we get the total size as
// (A,C) + (C,E) = (A,E)
//
long size = checked((long)(
// value that was either parsed from a file or initialized to the _fixedMinimalValueOfSizeOfZip64EOCD
_sizeOfZip64EndOfCentralDirectory +
// const (value indicating minimal whole record size, how many bytes on disk it needs) 56
_fixedMinimalRecordSize -
// const (value indicating minimal value for the SizeOfZip64EOCD field as it is contains
// the whole size without record signature(4), and the itself (8) it is 56 - 12 = 44
_fixedMinimalValueOfSizeOfZip64EOCD));
// update value and mark record dirty if either it is already dirty or there is a mismatch
if ((_dirtyFlag) ||
(_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk != centralDirCount) ||
(_totalNumberOfEntriesInTheCentralDirectory != centralDirCount ) ||
(_sizeOfTheCentralDirectory != centralDirBlockSize) ||
(_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber != centralDirOffset) ||
(_size != size))
{
_versionMadeBy = (ushort)ZipIOVersionNeededToExtract.Zip64FileFormat;
_versionNeededToExtract = (ushort)ZipIOVersionNeededToExtract.Zip64FileFormat;
_numberOfThisDisk = 0;
_numberOfTheDiskWithTheStartOfTheCentralDirectory = 0;
_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk = centralDirCount;
_totalNumberOfEntriesInTheCentralDirectory = centralDirCount;
_sizeOfTheCentralDirectory = centralDirBlockSize;
_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = centralDirOffset;
_size = size;
_dirtyFlag = true;
}
}
else
{
// we do not need zip 64 structures
if (_size != 0)
{
_dirtyFlag = true;
_size = 0;
}
}
}
}
}
public PreSaveNotificationScanControlInstruction PreSaveNotification(long offset, long size)
{
// we can safely ignore this notification as we do not keep any data on disk
// after parsing on disk. Everything is in memory, it is ok to override
// original Zip64 EndOf Central Directory Block without any additional backups
// we can also safely state that there is no need to continue the PreSafeNotification loop
// as the blocks after the Zip64 Eocd (EOCD, Zip64 locator ) do not have
// data that is buffered on disk
return PreSaveNotificationScanControlInstruction.Stop;
}
internal static ZipIOZip64EndOfCentralDirectoryBlock SeekableLoad (ZipIOBlockManager blockManager)
{
ZipIOZip64EndOfCentralDirectoryLocatorBlock zip64endOfCentralDirectoryLocator =
blockManager.Zip64EndOfCentralDirectoryLocatorBlock;
long zip64EndOfCentralDirectoryOffset =
zip64endOfCentralDirectoryLocator.OffsetOfZip64EndOfCentralDirectoryRecord;
ZipIOZip64EndOfCentralDirectoryBlock block = new ZipIOZip64EndOfCentralDirectoryBlock(blockManager);
blockManager.Stream.Seek(zip64EndOfCentralDirectoryOffset, SeekOrigin.Begin);
block.ParseRecord(blockManager.BinaryReader, zip64EndOfCentralDirectoryOffset);
return block;
}
internal static ZipIOZip64EndOfCentralDirectoryBlock CreateNew(ZipIOBlockManager blockManager)
{
ZipIOZip64EndOfCentralDirectoryBlock block = new ZipIOZip64EndOfCentralDirectoryBlock(blockManager);
block._size = 0; // brand new created records are optional by definition untill UpdateReferences is called, so size must be 0
block._offset = 0;
block._dirtyFlag = false;
// initialize fields with ythe data from the EOCD
block.InitializeFromEndOfCentralDirectory(blockManager.EndOfCentralDirectoryBlock);
return block;
}
internal long OffsetOfStartOfCentralDirectory
{
get
{
return (long)_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
}
}
internal int TotalNumberOfEntriesInTheCentralDirectory
{
get
{
return (int)_totalNumberOfEntriesInTheCentralDirectory; // checked isn't required as we do validation during parsing
}
}
internal long SizeOfCentralDirectory
{
get
{
return (long)_sizeOfTheCentralDirectory;
}
}
private ZipIOZip64EndOfCentralDirectoryBlock(ZipIOBlockManager blockManager)
{
Debug.Assert(blockManager != null);
_blockManager= blockManager;
}
private void ParseRecord (BinaryReader reader, long position)
{
_signature = reader.ReadUInt32();
_sizeOfZip64EndOfCentralDirectory = reader.ReadUInt64();
_versionMadeBy = reader.ReadUInt16();
_versionNeededToExtract = reader.ReadUInt16();
_numberOfThisDisk = reader.ReadUInt32();
_numberOfTheDiskWithTheStartOfTheCentralDirectory = reader.ReadUInt32();
_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk = reader.ReadUInt64();
_totalNumberOfEntriesInTheCentralDirectory = reader.ReadUInt64();
_sizeOfTheCentralDirectory = reader.ReadUInt64();
_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = reader.ReadUInt64();
// pre validate before reading data based on parsed values
if ((_sizeOfZip64EndOfCentralDirectory < _fixedMinimalValueOfSizeOfZip64EOCD) ||
// we are refusing to buffer large extended areas
(_sizeOfZip64EndOfCentralDirectory > UInt16.MaxValue))
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if (_sizeOfZip64EndOfCentralDirectory > _fixedMinimalValueOfSizeOfZip64EOCD)
{
_zip64ExtensibleDataSector = reader.ReadBytes((int)(_sizeOfZip64EndOfCentralDirectory -_fixedMinimalValueOfSizeOfZip64EOCD));
}
// override some numbers bvased on the EOCD data according to the apnote
// even in presence of Zip64Eocd we still need to use the regular EOCD data
OverrideValuesBasedOnEndOfCentralDirectory(_blockManager.EndOfCentralDirectoryBlock);
_size = checked((long)( // value that was either parsed from a file or initialized to the _fixedMinimalValueOfSizeOfZip64EOCD
_sizeOfZip64EndOfCentralDirectory +
// const (value indicating minimal whole record size, how many bytes on disk it needs) 56
_fixedMinimalRecordSize -
// const (value indicating minimal value for the SizeOfZip64EOCD field as it is contains
// the whole size without record signature(4), and the itself (8) it is 56 - 12 = 44
_fixedMinimalValueOfSizeOfZip64EOCD));
Debug.Assert(_size >= _fixedMinimalRecordSize);
_offset = position;
_dirtyFlag = false;
Validate();
}
/// <summary>
/// This function is called from the Create New routine. The purpose of this exercise , is to copy data from 32 bit EOCD into this record,
/// for scenarios when ZIP64 EOCD wasn't parsed from a file, but was just made up.
/// This is done so that Central Dir parsing code can ask the ZIP64 EOCD for this data, and regardless of whether it is real zip 64 file or
/// not a zip 64 file it will get the right CD offset , size and so on
/// </summary>
private void InitializeFromEndOfCentralDirectory(ZipIOEndOfCentralDirectoryBlock zipIoEocd)
{
_numberOfThisDisk = zipIoEocd.NumberOfThisDisk;
_numberOfTheDiskWithTheStartOfTheCentralDirectory = zipIoEocd.NumberOfTheDiskWithTheStartOfTheCentralDirectory;
_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk = zipIoEocd.TotalNumberOfEntriesInTheCentralDirectoryOnThisDisk;
_totalNumberOfEntriesInTheCentralDirectory = zipIoEocd.TotalNumberOfEntriesInTheCentralDirectory;
_sizeOfTheCentralDirectory = zipIoEocd.SizeOfTheCentralDirectory;
_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = zipIoEocd.OffsetOfStartOfCentralDirectory;
}
/// <summary>
/// This function is called from the Parse routine. The purpose of this exercise , is to figure out the escape
/// values in the regular 32 bit EOCD. We shouldn't be using values from the 64 bit structure if it wasn't
/// escaped in the 32 bit structure.
/// </summary>
private void OverrideValuesBasedOnEndOfCentralDirectory(ZipIOEndOfCentralDirectoryBlock zipIoEocd)
{
// 16 bit numbers
if (zipIoEocd.NumberOfThisDisk < UInt16.MaxValue)
{_numberOfThisDisk = zipIoEocd.NumberOfThisDisk;}
if (zipIoEocd.NumberOfTheDiskWithTheStartOfTheCentralDirectory < UInt16.MaxValue)
{_numberOfTheDiskWithTheStartOfTheCentralDirectory = zipIoEocd.NumberOfTheDiskWithTheStartOfTheCentralDirectory;}
if (zipIoEocd.TotalNumberOfEntriesInTheCentralDirectoryOnThisDisk < UInt16.MaxValue)
{_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk = zipIoEocd.TotalNumberOfEntriesInTheCentralDirectoryOnThisDisk;}
if (zipIoEocd.TotalNumberOfEntriesInTheCentralDirectory < UInt16.MaxValue)
{_totalNumberOfEntriesInTheCentralDirectory = zipIoEocd.TotalNumberOfEntriesInTheCentralDirectory;}
// 32 bit numbers
if (zipIoEocd.SizeOfTheCentralDirectory < UInt32.MaxValue)
{_sizeOfTheCentralDirectory = zipIoEocd.SizeOfTheCentralDirectory;}
if (zipIoEocd.OffsetOfStartOfCentralDirectory < UInt32.MaxValue)
{_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = zipIoEocd.OffsetOfStartOfCentralDirectory;}
}
private void Validate()
{
if (_signature != _signatureConstant)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if ((_numberOfThisDisk != 0) ||
(_numberOfTheDiskWithTheStartOfTheCentralDirectory != 0) ||
(_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk !=
_totalNumberOfEntriesInTheCentralDirectory))
{
throw new NotSupportedException(SR.Get(SRID.NotSupportedMultiDisk));
}
// this will throw an unsupported version exception if we see a version that we do not support
ZipArchive.VerifyVersionNeededToExtract(_versionNeededToExtract);
// if it is one of the supported version but it isn't a ZIP64, it is an indication of a corrupted file
if (_versionNeededToExtract != (UInt16)ZipIOVersionNeededToExtract.Zip64FileFormat)
{
// if version isn't equal to the 4.5 it is a corrupted file (as we)
// as appnote explicitly states that
// When using ZIP64 extensions, the corresponding value in the
// Zip64 end of central directory record should also be set.
// This field currently supports only the value 45 to indicate
// ZIP64 extensions are present.
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if ((_totalNumberOfEntriesInTheCentralDirectoryOnThisDisk > Int32.MaxValue) ||
(_totalNumberOfEntriesInTheCentralDirectory > Int32.MaxValue) ||
(_sizeOfTheCentralDirectory > Int64.MaxValue) ||
(_offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber > Int64.MaxValue))
{
// although we are trying to support 64 bit structures
// we are limited by the CLR model for collections (down to 32 bit collection size for
// _totalNumberOfEntriesInTheCentralDirectoryOnThisDisk )
// and streams (down to 63 bit size) for all the outher Uint64 fields
throw new NotSupportedException(SR.Get(SRID.Zip64StructuresTooLarge));
}
ulong sizeOfZip64ExtensibleDataSector = 0;
if (_zip64ExtensibleDataSector != null)
{
sizeOfZip64ExtensibleDataSector = (ulong)_zip64ExtensibleDataSector.Length;
}
// the subtraction below doesn't need to be checked as we have validation in the parse logic
// if (_sizeOfZip64EndOfCentralDirectory < _fixedMinimalValueOfSizeOfZip64EOCD) { throw .. }
if (_sizeOfZip64EndOfCentralDirectory - _fixedMinimalValueOfSizeOfZip64EOCD != sizeOfZip64ExtensibleDataSector)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
//calculated record size must be larger than the min value
// it could be 0 for newly created from scratch records, but we do not pass those records through validation
// we only validate parsed data
if (_size < _fixedMinimalRecordSize)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
}
private ZipIOBlockManager _blockManager;
private long _offset;
private long _size;
private bool _dirtyFlag;
private const UInt32 _signatureConstant = 0x06064b50;
private const uint _fixedMinimalRecordSize = 56;
private const uint _fixedMinimalValueOfSizeOfZip64EOCD = 44; // doesn't include the signature and the size itself
// data persisted on disk
private UInt32 _signature = _signatureConstant;
private UInt64 _sizeOfZip64EndOfCentralDirectory = _fixedMinimalValueOfSizeOfZip64EOCD;
private UInt16 _versionMadeBy = (ushort)ZipIOVersionNeededToExtract.Zip64FileFormat;
private UInt16 _versionNeededToExtract = (ushort)ZipIOVersionNeededToExtract.Zip64FileFormat;
private UInt32 _numberOfThisDisk;
private UInt32 _numberOfTheDiskWithTheStartOfTheCentralDirectory;
private UInt64 _totalNumberOfEntriesInTheCentralDirectoryOnThisDisk; // all int64s declared as signed values
private UInt64 _totalNumberOfEntriesInTheCentralDirectory; // as we can not suport true unsigned 64 bit sizes
private UInt64 _sizeOfTheCentralDirectory; // as a result of limitations in Stream interface
private UInt64 _offsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
private byte[] _zip64ExtensibleDataSector;
}
}
|