|
//-----------------------------------------------------------------------------
//------------- *** 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="ZipIOCentralDirectoryFileHeader.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
//
// History:
// 11/19/2004: IgorBel: Initial creation.
//
//-----------------------------------------------------------------------------
using System;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections;
using System.Runtime.Serialization;
using System.Windows;
using MS.Internal.WindowsBase;
namespace MS.Internal.IO.Zip
{
internal class ZipIOCentralDirectoryFileHeader
{
internal static ZipIOCentralDirectoryFileHeader CreateNew(Encoding encoding, ZipIOLocalFileBlock fileBlock)
{
ZipIOCentralDirectoryFileHeader header = new ZipIOCentralDirectoryFileHeader(encoding);
// initialize fields that are not duplicated in the local file block(header)
header._fileCommentLength =0;
header._fileComment = null;
header._diskNumberStart = 0;
header._internalFileAttributes = 0;
header._externalFileAttributes = 0;
header._versionMadeBy = (ushort)ZipIOVersionNeededToExtract.Zip64FileFormat;
header._extraField = ZipIOExtraField.CreateNew(false /* no padding */);
// update the rest of the fields based on the local file header
header.UpdateFromLocalFileBlock(fileBlock);
return header;
}
internal static ZipIOCentralDirectoryFileHeader ParseRecord(BinaryReader reader, Encoding encoding)
{
ZipIOCentralDirectoryFileHeader header = new ZipIOCentralDirectoryFileHeader(encoding);
header._signature = reader.ReadUInt32();
header._versionMadeBy = reader.ReadUInt16();
header._versionNeededToExtract = reader.ReadUInt16();
header._generalPurposeBitFlag = reader.ReadUInt16();
header._compressionMethod = reader.ReadUInt16();
header._lastModFileDateTime = reader.ReadUInt32();
header._crc32 = reader.ReadUInt32();
header._compressedSize = reader.ReadUInt32();
header._uncompressedSize = reader.ReadUInt32();
header._fileNameLength = reader.ReadUInt16();
header._extraFieldLength = reader.ReadUInt16();
header._fileCommentLength = reader.ReadUInt16();
header._diskNumberStart = reader.ReadUInt16();
header._internalFileAttributes = reader.ReadUInt16();
header._externalFileAttributes = reader.ReadUInt32();
header._relativeOffsetOfLocalHeader = reader.ReadUInt32();
header._fileName = reader.ReadBytes(header._fileNameLength);
// check for the ZIP 64 version and escaped values
ZipIOZip64ExtraFieldUsage zip64extraFieldUsage = ZipIOZip64ExtraFieldUsage.None;
if (header._versionNeededToExtract >= (ushort)ZipIOVersionNeededToExtract.Zip64FileFormat)
{
if (header._compressedSize == UInt32.MaxValue)
{
zip64extraFieldUsage |= ZipIOZip64ExtraFieldUsage.CompressedSize;
}
if (header._uncompressedSize == UInt32.MaxValue)
{
zip64extraFieldUsage |= ZipIOZip64ExtraFieldUsage.UncompressedSize;
}
if (header._relativeOffsetOfLocalHeader == UInt32.MaxValue)
{
zip64extraFieldUsage |= ZipIOZip64ExtraFieldUsage.OffsetOfLocalHeader;
}
if (header._diskNumberStart == UInt16.MaxValue)
{
zip64extraFieldUsage |= ZipIOZip64ExtraFieldUsage.DiskNumber;
}
}
// if the ZIP 64 record is missing the zip64extraFieldUsage value will be ignored
header._extraField = ZipIOExtraField.ParseRecord(reader,
zip64extraFieldUsage,
header._extraFieldLength);
header._fileComment = reader.ReadBytes(header._fileCommentLength);
//populate frequently used field with user friendly data representations
header._stringFileName = ZipIOBlockManager.ValidateNormalizeFileName(encoding.GetString(header._fileName));
header.Validate();
return header;
}
internal void Save(BinaryWriter writer)
{
writer.Write(_signatureConstant);
writer.Write(_versionMadeBy);
writer.Write(_versionNeededToExtract);
writer.Write(_generalPurposeBitFlag);
writer.Write(_compressionMethod);
writer.Write(_lastModFileDateTime);
writer.Write(_crc32);
writer.Write(_compressedSize);
writer.Write(_uncompressedSize);
writer.Write(_fileNameLength);
writer.Write(_extraField.Size);
writer.Write(_fileCommentLength);
writer.Write(_diskNumberStart);
writer.Write(_internalFileAttributes);
writer.Write(_externalFileAttributes);
writer.Write(_relativeOffsetOfLocalHeader);
Debug.Assert(_fileNameLength > 0); // we validate this for both parsing and API entry points
writer.Write(_fileName, 0, _fileNameLength);
_extraField.Save(writer);
if (_fileCommentLength > 0)
{
writer.Write(_fileComment , 0, _fileCommentLength);
}
}
internal bool UpdateIfNeeded(ZipIOLocalFileBlock fileBlock)
{
if (CheckIfUpdateNeeded(fileBlock))
{
UpdateFromLocalFileBlock(fileBlock);
return true;
}
else
{
return false;
}
}
internal string FileName
{
get
{
return _stringFileName;
}
// set method if needed will have to update both the _stringFileName and
// _fileName
}
internal UInt16 VersionNeededToExtract
{
get
{
return _versionNeededToExtract;
}
}
internal UInt16 GeneralPurposeBitFlag
{
get
{
return _generalPurposeBitFlag;
}
}
internal CompressionMethodEnum CompressionMethod
{
get
{
// cast is safe because the value is validated in Validate()
return (CompressionMethodEnum)_compressionMethod;
}
}
internal long Size
{
get
{
return checked(_fixedMinimalRecordSize + _fileNameLength + _extraField.Size + _fileCommentLength);
}
}
internal long OffsetOfLocalHeader
{
get
{
if ((_extraField.Zip64ExtraFieldUsage & ZipIOZip64ExtraFieldUsage.OffsetOfLocalHeader) != 0)
{
// zip 64 extra field is there
return _extraField.OffsetOfLocalHeader;
}
else
{
// 32 bit case
return _relativeOffsetOfLocalHeader;
}
}
}
internal long CompressedSize
{
get
{
if ((_extraField.Zip64ExtraFieldUsage & ZipIOZip64ExtraFieldUsage.CompressedSize) != 0)
{
// zip 64 extra field is there
return _extraField.CompressedSize;
}
else
{
// 32 bit case
return _compressedSize;
}
}
}
internal long UncompressedSize
{
get
{
if ((_extraField.Zip64ExtraFieldUsage & ZipIOZip64ExtraFieldUsage.UncompressedSize) != 0)
{
// zip 64 extra field is there
return _extraField.UncompressedSize;
}
else
{
// 32 bit case
return _uncompressedSize;
}
}
}
internal UInt32 Crc32
{
get
{
return _crc32;
}
}
internal UInt32 DiskNumberStart
{
get
{
if ((_extraField.Zip64ExtraFieldUsage & ZipIOZip64ExtraFieldUsage.DiskNumber) != 0)
{
// zip 64 extra field is there (32 bit value returned)
return _extraField.DiskNumberOfFileStart;
}
else
{
// 16 bit case
return _diskNumberStart;;
}
}
}
internal bool FolderFlag
{
get
{
// The upper byte of version made by indicates the compatibility of the file attribute information.
// If the external file attributes are compatible with MS-DOS then this value
// will be zero.
// lower byte of the external file attribute is the the MS-DOS directory attribute byte
//
// 0x20 5 file has been changed since last backup
// 0x10 4 entry represents a subdirectory XXXXXXXXX
// 0x08 3 entry represents a volume label
// 0x04 2 system file
// 0x02 1 hidden file
// 0x01 0 read-only
return ((_versionMadeBy & 0xFF00) == _constantUpperVersionMadeByMsDos)
&&
((_externalFileAttributes & 0x10) != 0);
}
}
internal bool VolumeLabelFlag
{
get
{
// The upper byte of version made by indicates the compatibility of the file attribute information.
// If the external file attributes are compatible with MS-DOS then this value
// will be zero.
// lower byte of the external file attribute is the the MS-DOS directory attribute byte
//
// 0x20 5 file has been changed since last backup
// 0x10 4 entry represents a subdirectory
// 0x08 3 entry represents a volume label XXXXXXXXX
// 0x04 2 system file
// 0x02 1 hidden file
// 0x01 0 read-only
return ((_versionMadeBy & 0xFF00) == _constantUpperVersionMadeByMsDos)
&&
((_externalFileAttributes & 0x08) != 0);
}
}
// this function is called by the Central Dir in order to notify us that
// the appropriate file item was shifted (as detected by the shift in the Raw Data Block)
// holding given file item.
// for us it means that although all the size characteristics are preserved (local file header
// wasn't even parsed if it still in the Raw). But the offset could have changed which
// might result in Zip64 struicture.
internal void MoveReference(long shiftSize)
{
UpdateZip64Structures(CompressedSize,
UncompressedSize,
checked(OffsetOfLocalHeader +shiftSize));
}
// this function is sets the sizes into the either 64 or 32 bit structures based on values of the fields
// It used in 2 places by the MoveReference and by the UpdateFromLocalFileBlock
private void UpdateZip64Structures
(long compressedSize, long uncompressedSize, long offset)
{
Debug.Assert((compressedSize >= 0) && (uncompressedSize>=0) && (offset >=0));
// according to the appnote central directory extra field might be a mix of any values based on escaping
// we will fully (without disk number) use it every time we are building a ZIP 64 arhichive
// we also trying to stay on the safe side and treeat the boundary case of 32 escape values
// as a zip 64 scenrio
if ((compressedSize >= UInt32.MaxValue) ||
(uncompressedSize >= UInt32.MaxValue) ||
(offset >= UInt32.MaxValue))
{
// Zip 64 case
_extraField.CompressedSize = compressedSize;
_extraField.UncompressedSize = uncompressedSize;
_extraField.OffsetOfLocalHeader = offset;
//set proper escape values
_compressedSize = UInt32.MaxValue;
_uncompressedSize = UInt32.MaxValue;
_relativeOffsetOfLocalHeader = UInt32.MaxValue;
// update version needed to extract to 4.5
_versionNeededToExtract = (UInt16)ZipIOVersionNeededToExtract.Zip64FileFormat;
}
else
{
// 32 bit case
_compressedSize = checked((UInt32)compressedSize);
_uncompressedSize = checked((UInt32)uncompressedSize);
_relativeOffsetOfLocalHeader = checked((UInt32)offset);
// reset the extra ZIP 64 field to empty
_extraField.Zip64ExtraFieldUsage = ZipIOZip64ExtraFieldUsage.None;
// version needed to extract needs to be recalculated from scratch based on compression
_versionNeededToExtract = (UInt16)ZipIOBlockManager.CalcVersionNeededToExtractFromCompression
((CompressionMethodEnum)_compressionMethod);
}
}
private void UpdateFromLocalFileBlock(ZipIOLocalFileBlock fileBlock)
{
Debug.Assert(DiskNumberStart == 0);
_signature = _signatureConstant;
_generalPurposeBitFlag = fileBlock.GeneralPurposeBitFlag;
_compressionMethod = (UInt16)fileBlock.CompressionMethod;
_lastModFileDateTime = fileBlock.LastModFileDateTime;
_crc32 = fileBlock.Crc32;
// file name is easy to copy
_fileNameLength = (UInt16)fileBlock.FileName.Length; // this is safe cast as file name is always validate for size
_fileName = _encoding.GetBytes(fileBlock.FileName);
_stringFileName = fileBlock.FileName;
// this will properly update the 32 or zip 64 fields
UpdateZip64Structures(fileBlock.CompressedSize,
fileBlock.UncompressedSize,
fileBlock.Offset);
// Previous instruction may determine that we don't really need 4.5, but we
// want to ensure that the version is identical with what is stored in the local file header.
Debug.Assert(_versionNeededToExtract <= fileBlock.VersionNeededToExtract, "Should never be making this smaller");
_versionNeededToExtract = fileBlock.VersionNeededToExtract;
// These fields are intentionally ignored, as they are not present in the local header
//_fileCommentLength;
//_fileComment;
//_diskNumberStart;
//_internalFileAttributes;
//_externalFileAttributes;
}
private bool CheckIfUpdateNeeded(ZipIOLocalFileBlock fileBlock)
{
// there is a special case for the _generalPurposeBitFlag.Bit #3
// it could be set in the local file header indicating streaming
// creation, while it doesn't need to be set in the Central directory
// so having
// (fileBlock.GeneralPurposeBitFlag == 8 && and _generalPurposeBitFlag == 0)
// is a valid case when update is not required
// let's compare the 3rd bit of the general purpose bit flag
bool localFileHeaderStreamingFlag = (0 != (fileBlock.GeneralPurposeBitFlag & _streamingBitMask));
bool centralDirStreamingFlag = (0 != (_generalPurposeBitFlag & _streamingBitMask));
if (!localFileHeaderStreamingFlag && centralDirStreamingFlag)
{
// the mismatch if local file header in non streaming but the central directory is in streaming mode
// all the other combinations do not require an update and valid as is
// this includes scenario when local file header is in streaming and central dir is not
return true;
}
Debug.Assert(String.CompareOrdinal(_stringFileName, fileBlock.FileName) == 0);
return
(_signature != _signatureConstant) ||
(_versionNeededToExtract != fileBlock.VersionNeededToExtract) ||
(_generalPurposeBitFlag != fileBlock.GeneralPurposeBitFlag) ||
(_compressionMethod != (UInt16)fileBlock.CompressionMethod) ||
(_crc32 != fileBlock.Crc32) ||
(CompressedSize != fileBlock.CompressedSize) ||
(UncompressedSize != fileBlock.UncompressedSize) ||
(OffsetOfLocalHeader != fileBlock.Offset);
// These fields are intentionally ignored, as they are not present in the local header
//_fileCommentLength;
//_fileComment;
//_diskNumberStart;
//_internalFileAttributes;
//_externalFileAttributes;
}
private ZipIOCentralDirectoryFileHeader(Encoding encoding)
{
_encoding = encoding;
}
private void Validate ()
{
if (_signature != _signatureConstant)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if (DiskNumberStart != 0)
{
throw new NotSupportedException(SR.Get(SRID.NotSupportedMultiDisk));
}
if (_fileNameLength != _fileName.Length)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if (_extraFieldLength != _extraField.Size)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
ZipArchive.VerifyVersionNeededToExtract(_versionNeededToExtract);
// if verson is below 4.5 make sure that ZIP 64 extra filed isn't present
// if it is it might be a security concern
if ((_versionNeededToExtract < (UInt16)ZipIOVersionNeededToExtract.Zip64FileFormat) &&
(_extraField.Zip64ExtraFieldUsage != ZipIOZip64ExtraFieldUsage.None))
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if (_fileCommentLength != _fileComment.Length)
{
throw new FileFormatException(SR.Get(SRID.CorruptedData));
}
if ((_compressionMethod != (UInt16)CompressionMethodEnum.Stored) &&
(_compressionMethod != (UInt16)CompressionMethodEnum.Deflated))
{
throw new NotSupportedException(SR.Get(SRID.ZipNotSupportedCompressionMethod));
}
}
private Encoding _encoding;
private const int _fixedMinimalRecordSize = 46;
private const byte _constantUpperVersionMadeByMsDos = 0x0;
private const UInt16 _streamingBitMask = 0x08; // bit #3
private const UInt32 _signatureConstant = 0x02014b50;
private UInt32 _signature = _signatureConstant;
// we expect all variables to be initialized to 0
private UInt16 _versionMadeBy;
private UInt16 _versionNeededToExtract;
private UInt16 _generalPurposeBitFlag;
private UInt16 _compressionMethod;
private UInt32 _lastModFileDateTime;
private UInt32 _crc32;
private UInt32 _compressedSize;
private UInt32 _uncompressedSize;
private UInt16 _fileNameLength;
private UInt16 _extraFieldLength;
private UInt16 _fileCommentLength;
private UInt16 _diskNumberStart;
private UInt16 _internalFileAttributes;
private UInt32 _externalFileAttributes;
private UInt32 _relativeOffsetOfLocalHeader;
private byte[] _fileName;
private ZipIOExtraField _extraField;
private byte[] _fileComment;
//duplicate dat for fast access
private string _stringFileName;
}
}
|