File: Base\MS\Internal\IO\Zip\ZipIOZip64EndOfCentralDirectoryLocatorBlock.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//-------------   *** 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="ZipIOZip64EndOfCentralDirectoryLocatorBlock.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 ZipIOZip64EndOfCentralDirectoryLocatorBlock : IZipIOBlock
    {
        // standard IZipIOBlock functionality
        public long Offset
        {
            get
            {
                return _offset;
            }
        }
 
        public long Size
        {
            get
            {
                return _size;
            }
        }
 
        // This property will only return reliable result if Update 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()
        {
            if (GetDirtyFlag(true) && (Size > 0))
            {
                BinaryWriter writer = _blockManager.BinaryWriter;
                if (_blockManager.Stream.Position != _offset)
                {
                    // we need to seek 
                    _blockManager.Stream.Seek(_offset, SeekOrigin.Begin);
 
                    // in non seekable streams we are expected to be at the right position, no seeking required 
                    // if this assumption is ever violated. Seek will throw on non-seekable stream, which would provide us 
                    // with a detection mechanism for such problems 
                }
                
                writer.Write(_signatureConstant);
                writer.Write(_numberOfTheDiskWithTheStartOfZip64EndOfCentralDirectory); 
                writer.Write(_offsetOfStartOfZip64EndOfCentralDirectoryRecord); 
                writer.Write(_totalNumberOfDisks); 
                
                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. Zip64 End of Central Directory is dirty
                //  3. streaming mode
                // if Central Directory isn't loded 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))
                                || _blockManager.Zip64EndOfCentralDirectoryBlock.GetDirtyFlag(closingFlag)))
                {
                    if (_blockManager.CentralDirectoryBlock.IsZip64BitRequiredForStoring)
                    {
                        UInt64 offsetOfStartOfZip64EndOfCentralDirectoryRecord = 
                                                (UInt64)_blockManager.Zip64EndOfCentralDirectoryBlock.Offset;
 
                        // update value and mark record dirty if either it is already dirty or there is a mismatch
                        if ((_dirtyFlag) || 
                            (offsetOfStartOfZip64EndOfCentralDirectoryRecord != _offsetOfStartOfZip64EndOfCentralDirectoryRecord) ||
                            (_fixedMinimalRecordSize != _size))
                        {
                            _offsetOfStartOfZip64EndOfCentralDirectoryRecord = offsetOfStartOfZip64EndOfCentralDirectoryRecord;
                            _size = _fixedMinimalRecordSize;
                            
                            _dirtyFlag = true;
                        }
                    }
                    else
                    {
                        // we do not need zip 64 structures
                        if (_size != 0)
                        {
                            _size = 0;
                            _dirtyFlag = true;
                        }
                    }
                }
            }
        }
 
        public PreSaveNotificationScanControlInstruction PreSaveNotification(long offset, long size)
        {
            // we can safely ignore this notification as we do not keep any data 
            // after parsing on disk. Everything is in memory, it is ok to override 
            // original End of Central directory without any additional backups
 
            // we can also safely state that there is no need to continue the PreSafeNotification loop 
            // as the only blocks after the Zip64 EOCD (EOCD) doesn't have 
            // data that is buffered on disk
            return PreSaveNotificationScanControlInstruction.Stop;
        }
        
        internal static ZipIOZip64EndOfCentralDirectoryLocatorBlock SeekableLoad (ZipIOBlockManager blockManager)  
        {
            // This Debug assert is a secondary check in debug builds, callers are responcible for verifying condition 
            // in both retail and debug builds
            Debug.Assert(SniffTheBlockSignature(blockManager));
            
            long blockPosition = checked(blockManager.EndOfCentralDirectoryBlock.Offset - _fixedMinimalRecordSize);
            
            blockManager.Stream.Seek(blockPosition, SeekOrigin.Begin);
 
            ZipIOZip64EndOfCentralDirectoryLocatorBlock block = new ZipIOZip64EndOfCentralDirectoryLocatorBlock(blockManager);
            
            block.ParseRecord(blockManager.BinaryReader, blockPosition);
            return block;
        }
        
        internal static ZipIOZip64EndOfCentralDirectoryLocatorBlock CreateNew(ZipIOBlockManager blockManager)          
        {
            // This Debug assert is a secondary check in debug builds, callers are responcible for verifying condition 
            // in both retail and debug builds
            Debug.Assert(!SniffTheBlockSignature(blockManager));
 
            ZipIOZip64EndOfCentralDirectoryLocatorBlock block = new ZipIOZip64EndOfCentralDirectoryLocatorBlock(blockManager);
 
            block._offset = 0;
            block._size = 0;
            block._dirtyFlag = false;
            
            return block;
        }
 
        internal static bool SniffTheBlockSignature(ZipIOBlockManager blockManager)
        {
            long suspectPos = checked(blockManager.EndOfCentralDirectoryBlock.Offset - _fixedMinimalRecordSize);
 
            // let's check that EndOfCentralDirectoryBlock.Offset is not too close to the start of the stream
            // for the record to fit there 
 
            // the second check isn't required, strictily speaking, as we are stepping back from the  EOCD.offset
            // however in some theoretical cases EOCD might not be trustable so to ensure that  ReadUInt32
            // isn't going to throw we do additional check
            if ((suspectPos < 0) ||
                (checked(suspectPos + sizeof(UInt32)) > blockManager.Stream.Length))  
            {
                return false;
            }
 
            blockManager.Stream.Seek(suspectPos, SeekOrigin.Begin);
                
            UInt32 signature = blockManager.BinaryReader.ReadUInt32();
            return (signature == _signatureConstant);
        }
    
        internal long OffsetOfZip64EndOfCentralDirectoryRecord
        {
            get
            {
                return (long)_offsetOfStartOfZip64EndOfCentralDirectoryRecord;
            }
        }
 
        private ZipIOZip64EndOfCentralDirectoryLocatorBlock(ZipIOBlockManager blockManager)
        {
            Debug.Assert(blockManager != null);
            _blockManager= blockManager;
        }
 
        private void ParseRecord (BinaryReader reader, long position)
        {
            _signature = reader.ReadUInt32();
            _numberOfTheDiskWithTheStartOfZip64EndOfCentralDirectory = reader.ReadUInt32();
            _offsetOfStartOfZip64EndOfCentralDirectoryRecord = reader.ReadUInt64();
            _totalNumberOfDisks = reader.ReadUInt32();
 
            _offset = position;
            _size = _fixedMinimalRecordSize;
            _dirtyFlag = false;
            
            Validate();
        }
 
        private void Validate() 
        {
            if (_offsetOfStartOfZip64EndOfCentralDirectoryRecord > Int64.MaxValue)  // C# does proper upcasting to ULONG of both operands
            {
                // although we are trying to support 64 bit structures 
                // we are limited by the CLR model for streams down to 63 
                // bit size for all the Uint64 fields 
 
                throw new NotSupportedException(SR.Get(SRID.Zip64StructuresTooLarge)); 
            }
            
            if (_signature != _signatureConstant)
            {
                throw new FileFormatException(SR.Get(SRID.CorruptedData));
            }
 
            if ((_totalNumberOfDisks != 1) ||
                (_numberOfTheDiskWithTheStartOfZip64EndOfCentralDirectory != 0))
            {
                throw new NotSupportedException(SR.Get(SRID.NotSupportedMultiDisk));
            }
 
            // The offset of the ZIP 64 EOCD must preceed the location of the ZIP64 EOCD locator 
            if ((UInt64)_offset <= _offsetOfStartOfZip64EndOfCentralDirectoryRecord) // we assume that _offset >=0
            {
                    throw new FileFormatException(SR.Get(SRID.CorruptedData));
            }            
 
            // this record is optional size must be either 0 or _fixedMinimalRecordSize
            if ((_size != _fixedMinimalRecordSize) && (_size != 0))
            {
                    throw new FileFormatException(SR.Get(SRID.CorruptedData));
            }
        }
        
        private ZipIOBlockManager _blockManager;
        
        private long _offset;
        private long _size;        
        private bool _dirtyFlag;        
 
        private  const UInt32 _signatureConstant  = 0x07064b50;
        private const int _fixedMinimalRecordSize = 20;
 
        // data persisted on disk 
        private UInt32 _signature = _signatureConstant;
        private UInt32 _numberOfTheDiskWithTheStartOfZip64EndOfCentralDirectory;
        private UInt64 _offsetOfStartOfZip64EndOfCentralDirectoryRecord;
        private UInt32 _totalNumberOfDisks = 1;
    }
}