File: Base\MS\Internal\IO\Zip\ZipIOBlockManager.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="ZipIoBlockManager.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.
//  10/21/2005: brucemac: Apply security mitigations
//
//-----------------------------------------------------------------------------
 
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Runtime.Serialization;
using System.Windows;
using MS.Internal.IO.Packaging;  // for PackagingUtilities
using MS.Internal.WindowsBase;
 
namespace MS.Internal.IO.Zip
{
    /// <summary>
    /// This is the main class of the actual ZIP IO implementation. It is primary responsibility
    /// is to maintain the map and status of the parsed and loaded areas(blocks) of the file.   
    /// It is also supports manipulating this map (adding and deleting blocks)
    /// </summary>                
    internal class ZipIOBlockManager : IDisposable, IEnumerable
    {
        //------------------------------------------------------
        //
        //  Public Methods  
        //
        //------------------------------------------------------
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            CheckDisposed();
 
            return _blockList.GetEnumerator();
        }
 
        //------------------------------------------------------
        //
        //  Internal Properties  
        //
        //------------------------------------------------------
        /// <summary>
        /// This property returns the status of whether Central directory is loaded or not. 
        /// This property is rarely used, as most clients will just ask for CentralDirectoryBlock
        /// and oif it isn't loaded it will be. 
        /// The only reason to use IsCentralDirectoryBlockLoaded property is to differentiate 
        /// scenarios in which some optimization is possible, if central directory isn't loaded yet. 
        /// </summary>                        
        internal bool IsCentralDirectoryBlockLoaded 
        {
            get
            {
                CheckDisposed();
                return (_centralDirectoryBlock != null);
            }
        }
 
        /// <summary>
        /// This property returns the CentralDirectoryBlock and provides lazy load 
        /// fuinctionality. This isthe only way other classes can access information 
        /// from the Central Directory Block
        /// </summary>                                
        internal ZipIOCentralDirectoryBlock CentralDirectoryBlock
        {
            get
            {
                CheckDisposed();
                if (_centralDirectoryBlock == null)
                {
                    // figure out if we are in ZIP64 mode or not 
                    if (Zip64EndOfCentralDirectoryBlock.TotalNumberOfEntriesInTheCentralDirectory > 0)
                    {
                        LoadCentralDirectoryBlock();
                    }
                    else
                    {
                        // We need to be aware of the special case of empty Zip Archive
                        // with a single record : End Of Central directory 
                        //In  such cases we should create new CentralDirectoryBlock 
                        CreateCentralDirectoryBlock();
                    }
                }
 
                return _centralDirectoryBlock;
            }
        }
 
        /// <summary>
        /// This property returns the Zip64EndOfCentralDirectoryBlock and provides lazy load 
        /// fuinctionality. This is the only way other classes can access information 
        /// from the Zip64EndOfCentralDirectoryBlock
        /// </summary>        
        internal ZipIOZip64EndOfCentralDirectoryBlock Zip64EndOfCentralDirectoryBlock 
        {
            get
            {
                CheckDisposed();
                
                if (_zip64EndOfCentralDirectoryBlock == null)
                {
                    CreateLoadZip64Blocks();
                }
 
                return _zip64EndOfCentralDirectoryBlock;
            }
        }
 
        /// <summary>
        /// This property returns the Zip64EndOfCentralDirectoryLocatorBlock and provides lazy load 
        /// fuinctionality. This is the only way other classes can access information 
        /// from the Zip64EndOfCentralDirectoryLocator Block
        /// </summary>   
        internal ZipIOZip64EndOfCentralDirectoryLocatorBlock Zip64EndOfCentralDirectoryLocatorBlock 
        {
            get
            {
                CheckDisposed();
                
                if (_zip64EndOfCentralDirectoryLocatorBlock == null)
                {
                    CreateLoadZip64Blocks();
                }
 
                return _zip64EndOfCentralDirectoryLocatorBlock;
            }
        }
 
        /// <summary>
        /// This property returns the CentralDirectoryBlock and provides lazy load 
        /// fuinctionality. This is the only way other classes can access information 
        /// from the Central Directory Block
        /// </summary>                                
        internal ZipIOEndOfCentralDirectoryBlock EndOfCentralDirectoryBlock
        {
            get
            {
                CheckDisposed();
                if (_endOfCentralDirectoryBlock == null)
                {
                    LoadEndOfCentralDirectoryBlock();
                }
 
                return _endOfCentralDirectoryBlock;
            }
        }
 
        internal Stream Stream 
        {
            get
            {
                CheckDisposed();
                return _archiveStream;
            }
        }
 
        internal bool Streaming
        {
            get
            {
                CheckDisposed();
                return _openStreaming;
            }
        }
 
        internal BinaryReader BinaryReader
        {
            get
            {
                CheckDisposed();
                Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
                if (_binaryReader == null)
                {
                    _binaryReader = new BinaryReader(Stream, Encoding);
                }
                return _binaryReader;
            }
        }
 
        internal BinaryWriter BinaryWriter
        {
            get
            {
                CheckDisposed();
                if (_binaryWriter == null)
                {
                    _binaryWriter = new BinaryWriter(Stream, Encoding);
                }
                return _binaryWriter;
            }
        }
 
        internal Encoding Encoding
        {
            get
            {
                CheckDisposed();
                return _encoding;
            }
        }
 
        internal bool DirtyFlag
        {
            set
            {
                CheckDisposed();
                _dirtyFlag = value;
            }
            get
            {
                CheckDisposed();
                return _dirtyFlag;
            }
        }
        
        static internal int MaxFileNameSize
        {
            get
            {
                return UInt16.MaxValue;
            }
        }
 
        //------------------------------------------------------
        //
        //  Internal Methods  
        //
        //------------------------------------------------------
 
        internal void  CreateEndOfCentralDirectoryBlock()  
        {
            CheckDisposed();
 
            // Prevent accidental call if underlying stream is non-empty since
            // any legal zip archive contains an EOCD block.
            Debug.Assert(_openStreaming || _archiveStream.Length == 0);
 
            // Disallow multiple calls.
            Debug.Assert(_endOfCentralDirectoryBlock == null);
 
            // construct Block find it and parse it 
            long blockOffset = 0;   // this will be updated later
            _endOfCentralDirectoryBlock = ZipIOEndOfCentralDirectoryBlock.CreateNew(this, blockOffset);
 
            // this will add a block to the tail
            AppendBlock(_endOfCentralDirectoryBlock); 
            DirtyFlag = true;
        }
 
        internal void LoadEndOfCentralDirectoryBlock()
        {
            Debug.Assert(_endOfCentralDirectoryBlock == null);
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            // construct Block find it and parse it 
            _endOfCentralDirectoryBlock = ZipIOEndOfCentralDirectoryBlock.SeekableLoad(this);
 
            //ask block manager to MAP this block 
            MapBlock(_endOfCentralDirectoryBlock);
        }
 
        internal ZipIOLocalFileBlock CreateLocalFileBlock(string zipFileName, CompressionMethodEnum compressionMethod, DeflateOptionEnum deflateOption)  
        {
            CheckDisposed();        
        
            // we are guaranteed uniqueness at this point , so let's just add a 
            // block at the end of the file, just before the central directory             
            // construct Block find it and parse it
 
            // STREAMING Mode:
            //   NOTE: _blockList is NOT in offset order except the last four blocks
            //      (CD, Zip64 EOCD, Zip64 EOCD Locator, and EOCD)
 
            ZipIOLocalFileBlock localFileBlock = ZipIOLocalFileBlock.CreateNew(this, 
                                                zipFileName, 
                                                compressionMethod, 
                                                deflateOption);
            
            InsertBlock(CentralDirectoryBlockIndex, localFileBlock); 
 
            CentralDirectoryBlock.AddFileBlock(localFileBlock);
 
            DirtyFlag = true;
            
            return localFileBlock;
        }
 
        internal ZipIOLocalFileBlock LoadLocalFileBlock(string zipFileName)  
        {
            CheckDisposed();
 
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
            Debug.Assert(CentralDirectoryBlock.FileExists(zipFileName)); // it must be in the central directory
 
            // construct Block find it and parse it 
            ZipIOLocalFileBlock localFileBlock = ZipIOLocalFileBlock.SeekableLoad(this, zipFileName);
            
            MapBlock(localFileBlock);
            return localFileBlock;
        }
 
        internal void RemoveLocalFileBlock(ZipIOLocalFileBlock localFileBlock)  
        {
            CheckDisposed();
 
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            Debug.Assert(localFileBlock != null, " At this point local File block must be preloaded");
            Debug.Assert(CentralDirectoryBlock.FileExists(localFileBlock.FileName), 
                            " At this point local File block must be mapped in central directory");
 
            
            // remove it from our list
            _blockList.Remove(localFileBlock);
 
            // remove this from Central Directory 
            CentralDirectoryBlock.RemoveFileBlock(localFileBlock.FileName);
            DirtyFlag = true;
            
            // at this point we can Dispose it to make sure that any calls 
            // to this file block through outstanding indirect references will result in object Disposed exception 
            localFileBlock.Dispose();
        }
 
        internal void MoveData(long moveBlockSourceOffset, long moveBlockTargetOffset, long moveBlockSize)
        {
            Debug.Assert(moveBlockSize >=0);
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            if ((moveBlockSize ==0) || (moveBlockSourceOffset == moveBlockTargetOffset))
            {
                //trivial empty move case 
                return;
            }
 
            checked
            {
                byte[] tempBuffer = new byte [Math.Min(moveBlockSize,0x100000)]; // min(1mb, requested block size)
                long bytesMoved = 0;
                while(bytesMoved < moveBlockSize)
                {
                    long subBlockSourceOffset;
                    long subBlockTargetOffset;
                    int subBlockSize = (int)Math.Min((long)tempBuffer.Length,  moveBlockSize - bytesMoved);
                    
                    if (moveBlockSourceOffset > moveBlockTargetOffset)
                    {
                        subBlockSourceOffset = moveBlockSourceOffset  + bytesMoved; 
                        subBlockTargetOffset = moveBlockTargetOffset  + bytesMoved; 
                    }
                    else
                    {
                        subBlockSourceOffset = moveBlockSourceOffset + moveBlockSize - bytesMoved - subBlockSize; 
                        subBlockTargetOffset =  moveBlockTargetOffset  + moveBlockSize - bytesMoved - subBlockSize;
                    }
                    
                    _archiveStream.Seek(subBlockSourceOffset, SeekOrigin.Begin);
                    int bytesRead = PackagingUtilities.ReliableRead(_archiveStream, tempBuffer, 0, subBlockSize);
 
                    if (bytesRead != subBlockSize)
                    {
                        throw new FileFormatException(SR.Get(SRID.CorruptedData));
                    }
                    
                    _archiveStream.Seek(subBlockTargetOffset, SeekOrigin.Begin);                
                    _archiveStream.Write(tempBuffer, 0, subBlockSize);
 
                    checked{bytesMoved += subBlockSize;}
                }
            }
        }
 
        /// <summary>
        /// Save - stream level
        /// </summary>
        /// <param name="blockRequestingFlush"></param>
        /// <param name="closingFlag">closing or flushing</param>
        internal void SaveStream(ZipIOLocalFileBlock blockRequestingFlush, bool closingFlag)
        {
            // Prevent recursion when propagating Flush or Disposed to our minions
            // because ZipIOFileItemStream.Flush calls us.
            if (_propagatingFlushDisposed)
                return;
            else
                _propagatingFlushDisposed = true;   // enter first time
 
            try
            {
                // redirect depending on our mode
                if (_openStreaming)
                {
                    StreamingSaveStream(blockRequestingFlush, closingFlag);
                }
                else
                    SaveContainer(false);
            }
            finally
            {
                // all done so restore state
                _propagatingFlushDisposed = false;
            }
        }
    
        /// <summary>
        /// Save - container level
        /// </summary>
        /// <param name="closingFlag">true if closing, false if flushing</param>
        internal void Save(bool closingFlag)
        {
            CheckDisposed();
 
            // Prevent recursion when propagating Flush or Disposed to our minions
            // because ZipIOFileItemStream.Flush calls us.
            if (_propagatingFlushDisposed)
                return;
            else
                _propagatingFlushDisposed = true;   // enter first time
 
            try
            {
                // redirect depending on our mode
                if (_openStreaming)
                {
                    StreamingSaveContainer(closingFlag);
                }
                else
                    SaveContainer(closingFlag);
            }
            finally
            {
                // all done so restore state
                _propagatingFlushDisposed = false;
            }
        }
 
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="archiveStream">stream we operate on</param>
        /// <param name="streaming"></param>
        /// <param name="ownStream">true if we own the stream and are expected to close it when we are disposed</param>
        internal ZipIOBlockManager(Stream archiveStream, bool streaming, bool ownStream)
        {
            Debug.Assert(archiveStream != null);
 
            _archiveStream = archiveStream;
            _openStreaming = streaming;
            _ownStream = ownStream;
 
            if (streaming)
            {
                // wrap the archive stream in a WriteTimeStream which keeps track of current position
                _archiveStream = new WriteTimeStream(_archiveStream);
            }
            else if (archiveStream.Length > 0)
            {
                // for non-empty stream we need to map the whole stream into a raw data block 
                // which helps keep track of shifts and dirty areas 
                ZipIORawDataFileBlock rawBlock = ZipIORawDataFileBlock.Assign(this, 0, archiveStream.Length);
 
                _blockList.Add(rawBlock);
            }
        }
 
        internal static UInt32 ToMsDosDateTime(DateTime dateTime)
        {
        
            UInt32 result = 0;
 
            result |= (((UInt32)dateTime.Second) /2) & 0x1F;   // seconds need to be divided by 2
                                                                                // as they stored in 5 bits
            result |= (((UInt32)dateTime.Minute) & 0x3F) << 5;   
            result |= (((UInt32)dateTime.Hour) & 0x1F) << 11;
 
            result |= (((UInt32)dateTime.Day) & 0x1F) << 16;
            result |= (((UInt32)dateTime.Month) & 0xF) << 21;
            result |= (((UInt32)(dateTime.Year - 1980)) & 0x7F) << 25;
 
            return result;
        }
 
        internal static DateTime FromMsDosDateTime(UInt32 dosDateTime)
        {
            int seconds = (int)((dosDateTime & 0x1F) << 1); // seconds need to be multiplied by 2
                                                                                       // as they stored in 5 bits
            int minutes  = (int)((dosDateTime >> 5) & 0x3F);
            int hours = (int)((dosDateTime >> 11) & 0x1F);
 
            int day = (int)((dosDateTime >> 16) & 0x1F);
            int month  =(int)((dosDateTime >> 21) & 0xF);
            int year = (int)(1980 + ((dosDateTime >> 25) & 0x7F));
 
            //this will throw if parameters are out of range 
            return new DateTime(year, month,day,hours,minutes,seconds);
        }
 
        /// <summary>
        /// This is standard way to normalize Zip File Item names. At this point we only 
        /// getting rid of the spaces. The Exists calls are responsible or making sure 
        /// that they check for uniqueness in a case insensitive manner. It is up to the 
        /// higher levels to add stricter restrictions like URI character set, and so on.
        /// </summary>   
        static internal string ValidateNormalizeFileName(string zipFileName)
        {
            // Validate parameteres 
            if (zipFileName == null)
            {
                throw new ArgumentNullException("zipFileName");
            }
 
            if (zipFileName.Length > ZipIOBlockManager.MaxFileNameSize)
            {
                throw new ArgumentOutOfRangeException("zipFileName");
            }
 
            zipFileName = zipFileName.Trim();
 
            if (zipFileName.Length < 1)//it must be at least one character 
            {
                throw new ArgumentOutOfRangeException("zipFileName");
            }
 
            //Based on the  Appnote : 
            //    << The path stored should not contain a drive or device letter, or a leading slash.  >>
           
            return zipFileName;
        }
 
        //------------------------------------------------------
        //  Internal helper CopyBytes functions for storing data into a byte[]
        // it is a similar to a BinaryWriter , but not for streams but ratrher
        // for byte[]
        // These functiona used in the Extra field parsing, as that functionality is buit
        // in terms of byte[] not streams
        //------------------------------------------------------
        internal static int CopyBytes(Int16 value, byte[] buffer, int offset)
        {
            Debug.Assert(checked(buffer.Length-offset) >= sizeof(Int16));
 
             byte[] tempBuffer = BitConverter.GetBytes(value);  
            Array.Copy(tempBuffer, 0, buffer, offset, tempBuffer.Length);
 
            return offset + tempBuffer.Length;
        }
 
        internal static int CopyBytes(Int32 value, byte[] buffer, int offset)
        {
            Debug.Assert(checked(buffer.Length-offset) >= sizeof(Int32));
 
             byte[] tempBuffer = BitConverter.GetBytes(value);  
            Array.Copy(tempBuffer, 0, buffer, offset, tempBuffer.Length);
 
            return offset + tempBuffer.Length;
        }
 
        internal static int CopyBytes(Int64 value, byte[] buffer, int offset)
        {
            Debug.Assert(checked(buffer.Length-offset) >= sizeof(Int64));
 
             byte[] tempBuffer = BitConverter.GetBytes(value);  
            Array.Copy(tempBuffer, 0, buffer, offset, tempBuffer.Length);
 
            return offset + tempBuffer.Length;
        }
        
        internal static int CopyBytes(UInt16 value, byte[] buffer, int offset)
        {
            Debug.Assert(checked(buffer.Length-offset) >= sizeof(UInt16));
 
             byte[] tempBuffer = BitConverter.GetBytes(value);  
            Array.Copy(tempBuffer, 0, buffer, offset, tempBuffer.Length);
 
            return offset + tempBuffer.Length;
        }
 
        internal static int CopyBytes(UInt32 value, byte[] buffer, int offset)
        {
            Debug.Assert(checked(buffer.Length-offset) >= sizeof(UInt32));
 
             byte[] tempBuffer = BitConverter.GetBytes(value);  
            Array.Copy(tempBuffer, 0, buffer, offset, tempBuffer.Length);
 
            return offset + tempBuffer.Length;
        }
 
        internal static int CopyBytes(UInt64 value, byte[] buffer, int offset)
        {
            Debug.Assert(checked(buffer.Length-offset) >= sizeof(UInt64));
 
             byte[] tempBuffer = BitConverter.GetBytes(value);  
            Array.Copy(tempBuffer, 0, buffer, offset, tempBuffer.Length);
 
            return offset + tempBuffer.Length;
        }
 
        internal static UInt64 ConvertToUInt64(UInt32 loverAddressValue, UInt32 higherAddressValue)
        {
            return checked((UInt64)loverAddressValue + (((UInt64)higherAddressValue)  << 32));
        }
 
        internal static ZipIOVersionNeededToExtract CalcVersionNeededToExtractFromCompression
                                                                                    (CompressionMethodEnum compression)
        {
            switch (compression)
            {
                case CompressionMethodEnum.Stored: 
                        return ZipIOVersionNeededToExtract.StoredData;
                case CompressionMethodEnum.Deflated:
                        return ZipIOVersionNeededToExtract.DeflatedData;
                default:
                        throw new NotSupportedException();    // Deflated64 this is OFF 
            }
        }
 
 
        /// <summary>
        /// This is the common Pre Save notiofication handler for 
        ///  RawDataFile Block and File Item Stream 
        /// It makes assumption that the overlap generally start coming in at the beginning of a 
        /// large disk image, so we should only try to cache cache overlaped data in the prefix 
        /// of the disk block  
        /// Block can also return a value indicating whether PreSaveNotification should be extended to the blocks that are positioned after 
        /// it in the Block List. For example, if block has completely handled PreSaveNotification in a way that it cached the whole area that 
        /// was in danger (of being overwritten) it means that no blocks need to worry about this anymore. After all no 2 blocks should have 
        /// share on disk buffers. Another scenario is when block can determine that area in danger is positioned before the block's on disk 
        /// buffers; this means that all blocks that are positioned later in the block list do not need to worry about this PreSaveNotification 
        /// as their buffers should be positioned even further alone in the file. 
        /// </summary>                
        internal static PreSaveNotificationScanControlInstruction CommonPreSaveNotificationHandler(
                                                            Stream stream,
                                                            long offset, long size,
                                                            long onDiskOffset, long onDiskSize,
                                                            ref SparseMemoryStream cachePrefixStream)
        {
            checked
            {
                Debug.Assert(size >=0);
                Debug.Assert(offset >=0);
                Debug.Assert(onDiskSize >=0);
                Debug.Assert(onDiskOffset >=0);
 
                // trivial request 
                if (size == 0)
                {
                    // The area being overwritten is of size 0 so there is no need to notify any blocks about this.
                    return PreSaveNotificationScanControlInstruction.Stop;
                }
 
                if (cachePrefixStream != null)
                {   
                    // if we have something in cache prefix buffer  we only should check whatever tail data isn't cached
                    checked{onDiskOffset += cachePrefixStream.Length;}
                    checked{onDiskSize -= cachePrefixStream.Length;}
                    Debug.Assert(onDiskSize >=0);                    
                }
 
                if (onDiskSize == 0)
                {
                    // the raw data block happened to be fully cached 
                    // in this case (onDiskSize==0) can not be used as a reliable indicator of the position of the 
                    // on disk buffer relative to the other; it is just an indicator of an empty buffer which might have a meaningless offset 
                    // that shouldn't be driving any decisions
                    return PreSaveNotificationScanControlInstruction.Continue;
                }
 
                // we need to first find out if the raw data that isn't cached yet overlaps with any disk space 
                // that is about to be overriden 
                long overlapBlockOffset;
                long  overlapBlockSize;
 
                PackagingUtilities.CalculateOverlap(onDiskOffset, onDiskSize, 
                                           offset, size ,
                                            out overlapBlockOffset, out overlapBlockSize);
                if (overlapBlockSize <= 0)
                {
                    // No overlap , we can ignore this message.
                    // In addition to that, if (onDiskOffset > offset) it means that, given the fact that all blocks after 
                    // the current one will have even larger offsets, they couldn't possibly overlap with (offset ,size ) chunk .
                    return (onDiskOffset > offset) ?
                                                PreSaveNotificationScanControlInstruction.Stop  : 
                                                PreSaveNotificationScanControlInstruction.Continue;
                }
 
                // at this point we have an overlap, we need to read the data that is overlapped 
                // and merge it with whatever we already have in cache 
                // let's figure out the part that isn't cached yet, and needs to be  
                long blockSizeToCache;
                checked
                {
                    blockSizeToCache = overlapBlockOffset + overlapBlockSize - onDiskOffset;
                }
                Debug.Assert(blockSizeToCache >0); // there must be a non empty block at this point that needs to be cached 
 
                // We need to ensure that we do have a place to store this data 
                if (cachePrefixStream == null)
                {   
                    cachePrefixStream = new SparseMemoryStream(_lowWaterMark, _highWaterMark);                
                }
                else
                {
                    // if we already have some cached prefix data we have to make sure we are 
                    // appending new data tro the tail of the already cached chunk
                    cachePrefixStream.Seek(0, SeekOrigin.End); 
                }
 
                stream.Seek(onDiskOffset, SeekOrigin.Begin);            
                long bytesCopied = PackagingUtilities.CopyStream(stream, cachePrefixStream, blockSizeToCache, 4096);
 
                if (bytesCopied  != blockSizeToCache)
                {
                    throw new FileFormatException(SR.Get(SRID.CorruptedData));
                }
 
                // if the contdition below is true it means that, given the fact that all blocks after 
                // the current one will have even larger offsets, they couldn't possibly overlap with (offset ,size ) chunk 
                return ((onDiskOffset + onDiskSize) >=  (offset + size)) ?
                                            PreSaveNotificationScanControlInstruction.Stop  : 
                                            PreSaveNotificationScanControlInstruction.Continue;
            }
        }
 
        //------------------------------------------------------
        //
        //  Protected Methods  
        //
        //------------------------------------------------------
        protected void Dispose(bool disposing)
        {
            if (disposing)
            {
                // multiple calls are fine - just ignore them
                if (!_disposedFlag)
                {
                    // Prevent recursion into Save() when propagating Flush or Disposed to our minions
                    // because ZipIOFileItemStream.Flush calls us.
                    if (_propagatingFlushDisposed)
                        return;
                    else
                        _propagatingFlushDisposed = true;   // enter first time
 
                    try
                    {
                        try
                        {
                            if (_blockList != null)
                            {
                                foreach (IZipIOBlock block in _blockList)
                                {
                                    IDisposable disposableBlock = block as IDisposable;
                                    if (disposableBlock != null)
                                    {
                                        // only some Blocks are disposable, most are not 
                                        disposableBlock.Dispose();
                                    }
                                }
                            }
                        }
                        finally
                        {
                            // If we own the stream, we should close it.
                            // If not, we cannot even close the binary reader or writer as these close the
                            // underlying stream on us.
                            if (_ownStream)
                            {
                                if (_binaryReader != null)
                                {   // this one might be null of we have been only writing 
                                    _binaryReader.Close();
                                }
 
                                if (_binaryWriter != null)
                                {   // this one might be null of we have been only reading
                                    _binaryWriter.Close();
                                }
 
                                if (_archiveStream != null)
                                {
                                    _archiveStream.Close();
                                }
                            }
                        }
                    }
                    finally
                    {
                        _blockList = null;
                        _encoding = null;
                        _endOfCentralDirectoryBlock = null;
                        _centralDirectoryBlock = null;
 
                        _disposedFlag = true;
                        _propagatingFlushDisposed = false;   // reset
                    }
                }
            }
        }
 
        //------------------------------------------------------
        //
        //  Private Properties  
        //
        //------------------------------------------------------
        /// <summary>
        /// This property returns the index of CentralDirectoryBlock within _blockList
        /// </summary>                                
        private int CentralDirectoryBlockIndex
        {
            get
            {
                Invariant.Assert(_blockList.Count >= _requiredBlockCount);
                Debug.Assert(_centralDirectoryBlock != null
                                && _endOfCentralDirectoryBlock != null
                                && _zip64EndOfCentralDirectoryBlock != null
                                && _zip64EndOfCentralDirectoryLocatorBlock != null);
 
                // We always have following blocks at the end of the block lists:
                //  CD, Zip64 EOCD, Zip64 EOCD Locator, and EOCD
                // Thus the index of CD can be calculated from the total number of blocks
                //  and _requiredBlockCount which is 4
                return _blockList.Count - _requiredBlockCount;
            }
        }
 
 
        //------------------------------------------------------
        //
        //  Private Methods  
        //
        //------------------------------------------------------
        /// <summary>
        /// Throwes exception if object already Disposed/Closed. 
        /// </summary> 
        private void CheckDisposed()
        {
            if (_disposedFlag)
            {
                throw new ObjectDisposedException(null, SR.Get(SRID.ZipArchiveDisposed));            
            }
        }
 
        /// <summary>
        /// Save - container level
        /// </summary>
        /// <param name="closingFlag">true if closing, false if flushing</param>
        private void SaveContainer(bool closingFlag)
        {
            CheckDisposed();
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
 
            if (!closingFlag && !DirtyFlag)
            {
                // we are trying to save some cycles in the case of the subsequent Flush calls
                // that do not close the container
                // if it is being closed DirtyFlag isn't reliable as the Compressed streams carry 
                // some extra bytes after flushing and only write them out on closing.  
                return;
            }
 
            // We need a separate cycle to update all the cross block references prior to saving blocks
            // specifically the central directory needs "dirty" information from blocks in order to properly 
            // update it's references, otherwise (if we call UpdateReferences and Save in the same loop) 
            // information about block shifts will be lost by the time we ask central directory to update it's 
            // references   
 
            // offset of the first block
            long currentOffset = 0; // ZIP64 review type here 
            foreach (IZipIOBlock currentBlock in _blockList)
            {
                // move block so it is positioned right after the previous block 
                currentBlock.Move(currentOffset - currentBlock.Offset);
 
                // this will update references and as well as other internal structures (size)
                // specifically for the FileItemBlock it will flush buffers of 
                // all the outstanding streams 
                currentBlock.UpdateReferences(closingFlag);
 
                //advance current stream position according to the size of the block 
                checked{currentOffset += currentBlock.Size;}
            }
 
            // save dirty blocks 
            bool dirtyBlockFound = false;
 
            int blockListCount  = _blockList.Count;
            for (int i = 0; i < blockListCount ; i++)
            {
                IZipIOBlock currentBlock = (IZipIOBlock)_blockList[i];
 
                if (currentBlock.GetDirtyFlag(closingFlag))
                {
                    dirtyBlockFound = true;
 
                    long currentBlockOffset = currentBlock.Offset;
                    long currentBlockSize = currentBlock.Size;
 
                    if (currentBlockSize > 0)
                    {
                        // before saving we need to warn all the blocks that have still some data on disk
                        // that might be overriden   
                        // second loop must start at the current position of the extrrnal loop
                        // as all the items before have been saved 
                        for (int j = i + 1; j < blockListCount; j++)
                        {
                            // This is an optimization which enabled us to stop going through the 
                            // tail blocks as soon as we find a block that returns a status indicating 
                            // that it took care of the tail of the target area or is positioned after the 
                            // target area.
                            if (((IZipIOBlock)_blockList[j]).PreSaveNotification(currentBlockOffset, currentBlockSize) == 
                                        PreSaveNotificationScanControlInstruction.Stop )
                            {
                                break;
                            }
                        }
                    }
 
                    currentBlock.Save();    // Even if currentBlockSize == 0, call Save to clear DirtyFlag
                }
            }
 
            // originally we have had an assert for the case when no changes were made to the file 
            // but calculated size didn't match the actual stream size.
            // As a result of the XPS Viewer dynamically switching streams underneath ZIP IO, we 
            // need to treat this case as a normal non-dirty scenario. So if nothing changed and 
            // nothing was written out we shouldn't even validate whether stream underneath 
            // was modified in any way or not (even such simple modifications as an unexpected 
            // Stream.Length change). If it was modified by someone we assume that the stream 
            // owner was aware of it's action.
            if (dirtyBlockFound && (Stream.Length > currentOffset))
            {
                Stream.SetLength(currentOffset);
            }
 
            Stream.Flush();
            DirtyFlag = false;
        }
 
        /// <summary>
        /// Streaming version of Save routine
        /// </summary>
        /// <param name="closingFlag">true if closing the package</param>
        private void StreamingSaveContainer(bool closingFlag)
        {
            // STREAMING Mode:
            //   NOTE: _blockList is NOT in offset order except the last four blocks
            //      (CD, Zip64 EOCD, Zip64 EOCD Locator, and EOCD)
 
            try
            {
                // save dirty blocks 
                long currentOffset = 0;
                for (int i = 0; i < _blockList.Count; i++)
                {
                    IZipIOBlock currentBlock = (IZipIOBlock)_blockList[i];
                    ZipIOLocalFileBlock localFileBlock = currentBlock as ZipIOLocalFileBlock;
 
                    if (localFileBlock == null)
                    {
                        if (closingFlag)
                        {
                            // Move block so it is positioned right after the previous block.
                            // No need for nested loops like in SaveContainer because none of these
                            // calls can cause a block to move in the Streaming case.
                            currentBlock.Move(currentOffset - currentBlock.Offset);
                            currentBlock.UpdateReferences(closingFlag);
                            if (currentBlock.GetDirtyFlag(closingFlag))
                            {
                                currentBlock.Save();
                            }
                        }
                    }
                    else if (currentBlock.GetDirtyFlag(closingFlag))
                    {
                        // no need to call UpdateReferences in streaming mode for regular
                        // local file blocks because
                        // we manually emit the local file header and the local file descriptor
                        localFileBlock.SaveStreaming(closingFlag);
                    }
                    checked{currentOffset += currentBlock.Size;}
                }
 
                Stream.Flush();
            }
            finally
            {
                // all done so restore state
                _propagatingFlushDisposed = false;
            }
        }
 
        /// <summary>
        /// Flush was called on a ZipIOFileItemStream
        /// </summary>
        /// <param name="blockRequestingFlush">block that owns the stream that Flush was called on</param>
        /// <param name="closingFlag">close or dispose</param>
        private void StreamingSaveStream(ZipIOLocalFileBlock blockRequestingFlush, bool closingFlag)
        {
            // STREAMING MODE:
            // Flush will do one of two things, depending on the currently open stream:
            // 1) If the currently open stream matches the one passed (or none is currently opened)
            //    then write will occur to the open stream.
            // 2) Otherwise, the currently opened stream will be flushed and closed, and the
            //    given stream will become the currently opened stream
            // NOTE: _blockList is NOT in offset order except the last four blocks
            //      (CD, Zip64 EOCD, Zip64 EOCD Locator, and EOCD)
 
            // different stream?
            if (_streamingCurrentlyOpenStreamBlock != blockRequestingFlush)
            {
                // need to close the currently opened stream 
                // unless its our first time through
                if (_streamingCurrentlyOpenStreamBlock != null)
                {
                    _streamingCurrentlyOpenStreamBlock.SaveStreaming(true);
                }
 
                // Now make the given stream the new "currently opened stream".
                _streamingCurrentlyOpenStreamBlock = blockRequestingFlush;
            }
 
            // this should now be flushable/closable
            _streamingCurrentlyOpenStreamBlock.SaveStreaming(closingFlag);
 
            // if closing - discard the stream because it is now closed
            if (closingFlag)
                _streamingCurrentlyOpenStreamBlock = null;
        }
 
        private void  CreateCentralDirectoryBlock()  
        {
            CheckDisposed();
            Debug.Assert(_zip64EndOfCentralDirectoryBlock != null);
 
            // It must not be loaded yet 
            Debug.Assert(!IsCentralDirectoryBlockLoaded);
 
            // The proper position is just before the Zip64EndOfCentralDirectoryRecord
            // Zip64EndOfCentralDirectoryRecord - might be of size 0 (if file is small enough)
            int blockPosition = _blockList.IndexOf(Zip64EndOfCentralDirectoryBlock);
            Debug.Assert(blockPosition >= 0);
            
            // construct Block find it and parse it 
            _centralDirectoryBlock = ZipIOCentralDirectoryBlock.CreateNew(this);
 
            //ask block manager to insert this this block             
            InsertBlock(blockPosition , _centralDirectoryBlock); 
        }
        
        private void LoadCentralDirectoryBlock()  
        {
            Debug.Assert(_centralDirectoryBlock == null);
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            // construct Block find it and parse it 
            _centralDirectoryBlock = ZipIOCentralDirectoryBlock.SeekableLoad(this);
 
            //ask block manager to MAP this block 
            MapBlock(_centralDirectoryBlock);
        }
        
        private void  CreateLoadZip64Blocks()
        {
            CheckDisposed();
 
            Debug.Assert ((_zip64EndOfCentralDirectoryBlock == null) && 
                                  (_zip64EndOfCentralDirectoryLocatorBlock == null));
 
            // determine whether we want to create it or load it 
            // this check doesn't provide us with a 100% guarantee. 
            // After discussion we have agreed that this should be sufficient 
            if (!Streaming && EndOfCentralDirectoryBlock.ContainValuesHintingToPossibilityOfZip64 &&
                ZipIOZip64EndOfCentralDirectoryLocatorBlock.SniffTheBlockSignature(this))
            {
                // attempt to sniff the header of the  
                LoadZip64EndOfCentralDirectoryLocatorBlock();
                LoadZip64EndOfCentralDirectoryBlock();
            }
            else
            {
                // We delayed validation of some values in End of Central Directory that can give possible
                //  hints for Zip64; Since there is no Zip64 structure, we need to validate them here
                _endOfCentralDirectoryBlock.ValidateZip64TriggerValues();
 
                CreateZip64EndOfCentralDirectoryLocatorBlock();
                CreateZip64EndOfCentralDirectoryBlock();
            }
        }
 
        private void  CreateZip64EndOfCentralDirectoryBlock()
        {
            Debug.Assert(_zip64EndOfCentralDirectoryBlock == null);
 
            // The proper position is just before the Zip64EndOfCentralDirectoryRecordLocator
            // Zip64EndOfCentralDirectoryRecord - might be of size 0 (if file is small enough)
            int blockPosition = _blockList.IndexOf(Zip64EndOfCentralDirectoryLocatorBlock);
 
            // construct Block find it and parse it 
            _zip64EndOfCentralDirectoryBlock = ZipIOZip64EndOfCentralDirectoryBlock.CreateNew(this);
 
            //ask block manager to insert this this block 
            InsertBlock(blockPosition, _zip64EndOfCentralDirectoryBlock);
        }
 
        private void  LoadZip64EndOfCentralDirectoryBlock()
        {
            Debug.Assert(_zip64EndOfCentralDirectoryBlock == null);
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            // construct Block find it and parse it 
            _zip64EndOfCentralDirectoryBlock = ZipIOZip64EndOfCentralDirectoryBlock.SeekableLoad(this);
 
            //ask block manager to insert this this block 
            MapBlock(_zip64EndOfCentralDirectoryBlock);
        }
        
        private void  CreateZip64EndOfCentralDirectoryLocatorBlock()
        {
            Debug.Assert(_zip64EndOfCentralDirectoryLocatorBlock == null);
 
            // The proper position is just before the EOCD 
            int blockPosition = _blockList.IndexOf(EndOfCentralDirectoryBlock);
 
            // construct Block find it and parse it 
            _zip64EndOfCentralDirectoryLocatorBlock = ZipIOZip64EndOfCentralDirectoryLocatorBlock.CreateNew(this);
 
            //ask block manager to MAP this block 
            InsertBlock(blockPosition, _zip64EndOfCentralDirectoryLocatorBlock);
        }
 
        private void  LoadZip64EndOfCentralDirectoryLocatorBlock()
        {
            Debug.Assert(_zip64EndOfCentralDirectoryLocatorBlock == null);
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            // construct Block find it and parse it 
            _zip64EndOfCentralDirectoryLocatorBlock = ZipIOZip64EndOfCentralDirectoryLocatorBlock.SeekableLoad(this);
 
            //ask block manager to MAP this block 
            MapBlock(_zip64EndOfCentralDirectoryLocatorBlock);
        }
                            
        private void MapBlock(IZipIOBlock block)
        {
            // as we map a block to existing file space it must be not dirty.
            Debug.Assert(!block.GetDirtyFlag(true)); // closingFlag==true used as a more conservative option
            Debug.Assert(!_openStreaming, "Not legal in Streaming mode");
 
            for (int blockIndex = _blockList.Count - 1; blockIndex >= 0; --blockIndex)
            {
                // if we need to find a RawDataBlock that maps to the target area 
                ZipIORawDataFileBlock rawBlock = _blockList[blockIndex] as ZipIORawDataFileBlock;
 
                //check the original loaded RawBlock size / offset against the new block 
                if ((rawBlock != null) && rawBlock.DiskImageContains(block))
                {
                    ZipIORawDataFileBlock prefixBlock, suffixBlock;
 
                    //split raw block into prefixRawBlock, SuffixRawBlock
                    rawBlock.SplitIntoPrefixSuffix(block, out prefixBlock, out suffixBlock);
 
                    _blockList.RemoveAt(blockIndex); // remove the old big raw data block
 
                    // add suffix Raw data block 
                    if (suffixBlock != null)
                    {
                        _blockList.Insert(blockIndex, suffixBlock);
                    }
 
                    // add new mapped block 
                    _blockList.Insert(blockIndex, block);
 
                    // add prefix Raw data block 
                    if (prefixBlock != null)
                    {
                        _blockList.Insert(blockIndex, prefixBlock);
                    }
 
                    return;
                }
            }
 
            // we couldn't find a raw data block for mapping this, we can only throw
            throw new FileFormatException(SR.Get(SRID.CorruptedData));
        }
 
        private void InsertBlock(int blockPosition, IZipIOBlock block)
        {
            // as we are adding a new block it must be dirty unless its size is 0
            Debug.Assert(block.GetDirtyFlag(true) ||   // closingFlag==true used as a more conservative option
                                    block.Size == 0); 
 
            _blockList.Insert(blockPosition, block);
        }
 
        private void AppendBlock(IZipIOBlock block)
        {
            // as we are adding a new block it must be dirty unless its size is 0
            Debug.Assert(block.GetDirtyFlag(true) || // closingFlag==true used as a more conservative option
                                    block.Size == 0);
 
            // CentralDirectory persistence logic relies on the fact that we always add headers in a fashion that
            // matches the order of the corresponding file items in the physical archive (currently to the end of the list).
            // If this invariant is violated, the corresponding central directory persistence logic must be updated.
            _blockList.Add(block);
        }
 
        // this flag is used for Perf reasons, it doesn't carry any additional information that isn't stored somewhere 
        // else. In order to prevent complex dirty calculations on the sequential flush calls, we are going to keep 
        // this flag which will be set to true at the end of the flush (or close). This flag will be set from the ZipArchive 
        // and CrcCalculating entry points that can potentially make our structure dirty.
        // This flag is only used for non-streaming cases. In streaming cases we do not believe there is a perf
        // penalty of that nature. 
        private bool _dirtyFlag = false;
        
        private bool _disposedFlag;
        private bool _propagatingFlushDisposed;              // if true, we ignore calls back to Save to prevent recursion
        private Stream _archiveStream;
        private bool _openStreaming;
        private bool _ownStream;                                    // true if we own the archive stream
 
        // Streaming Mode Only: stream that is currently able to write without interfering with other streams
        private ZipIOLocalFileBlock _streamingCurrentlyOpenStreamBlock;
 
        private BinaryReader _binaryReader;
        private BinaryWriter _binaryWriter;
 
        private const int _initialBlockListSize = 50;
        private ArrayList _blockList = new ArrayList(_initialBlockListSize); 
 
        private ASCIIEncoding _encoding = new ASCIIEncoding();
 
        ZipIOZip64EndOfCentralDirectoryBlock  _zip64EndOfCentralDirectoryBlock;
        ZipIOZip64EndOfCentralDirectoryLocatorBlock _zip64EndOfCentralDirectoryLocatorBlock; 
        ZipIOEndOfCentralDirectoryBlock _endOfCentralDirectoryBlock;
        ZipIOCentralDirectoryBlock _centralDirectoryBlock;
 
        private const long _lowWaterMark = 0x19000;                 // we definately would like to keep everythuing under 100 KB in memory  
        private const long _highWaterMark = 0xA00000;   // we would like to keep everything over 10 MB on disk
        private const int _requiredBlockCount = 4;      // We always have following blocks: CD, Zip64 EOCD, Zip64 EOCD Locator, and EOCD
                                                       // This value is used to calculate the index of CD within _blockList
    }
}